{"id":47561,"date":"2025-01-26T02:00:17","date_gmt":"2025-01-26T02:00:17","guid":{"rendered":"https:\/\/peraltafinancing.com\/analytics\/join-google-ads-and-ga4-data-in-google-bigquery\/"},"modified":"2025-01-26T02:00:17","modified_gmt":"2025-01-26T02:00:17","slug":"join-google-ads-and-ga4-data-in-google-bigquery","status":"publish","type":"post","link":"https:\/\/fivemor.com\/?p=47561","title":{"rendered":"Join Google Ads And GA4 Data In Google BigQuery"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<p>I am fortunate to share another <strong>guest post<\/strong> by <a href=\"https:\/\/www.linkedin.com\/in\/arben-kqiku-301457117\/\">Arben Kqiku<\/a>, Digital Analyst at <a href=\"https:\/\/www.linkedin.com\/company\/assura-ch\/\">Assura<\/a>.<\/p>\n<p>Last time, Arben graced this blog with a <a href=\"https:\/\/www.simoahava.com\/analytics\/content-analysis-using-ga4-bigquery-r-google-sheets-data-studio\/\">comprehensive love letter to R<\/a> as an example of the power of this programming language.<\/p>\n<p>This time, he\u2019ll add even more fancy tools to the toolkit to help you build a <strong>data pipeline<\/strong> in the Google Cloud Platform to join your Google Ads and Google Analytics 4 data together.<\/p>\n<p>                <span class=\"simmer\"><br \/>\n  <span class=\"close\">X<\/span><\/p>\n<p>\n    <span class=\"fa fa-md fa-bell\"\/><br \/>\n    <strong>The Simmer Newsletter<\/strong>\n  <\/p>\n<p>\n    Subscribe to the <a href=\"https:\/\/www.simoahava.com\/newsletter\/\">Simmer newsletter<\/a> to get the latest news and content from Simo Ahava into your email inbox!\n  <\/p>\n<p>  <\/span><\/p>\n<h2 id=\"goal-of-this-article\">Goal of this article<\/h2>\n<p>My goal for this article is twofold; first, I want to show my fellow R users that it is possible and fun to use R in a production environment! R is sometimes seen as a second-class citizen compared to Python, but in my experience, R has consistently proven effective in addressing all my data challenges at work.<\/p>\n<p>Second, I want to show my fellow digital analysts the power of complementing GTM and GA4 with additional tools.<\/p>\n<div style=\"aspect-ratio: 1288 \/ 775;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/Article-mage-digital-analytics.jpg\" title=\"Architecture diagram of what we'll build\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"775\" width=\"1288\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/Article-mage-digital-analytics.jpg#ZgotmplZ\" alt=\"Architecture diagram of what we'll build\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Above you can see an architectural overview of what we\u2019ll build today.<\/p>\n<h2 id=\"what-am-i-trying-to-solve\">What am I trying to solve?<\/h2>\n<p>As a digital analyst, I often need to combine data from different sources and display it in a dashboard.<\/p>\n<p>For instance, clients run campaigns on platforms like <strong>Google Ads<\/strong> and <strong>Meta Ads<\/strong>, and they want to understand the impact of each channel or even individual campaigns.<\/p>\n<p>The challenge is that each channel tends to attribute conversions mostly to itself. This isn\u2019t intentional; it\u2019s just that each channel operates independently and can\u2019t see the contributions of others.<\/p>\n<p>To address this, we usually use conversion data from a third-party source, like <strong>Google Analytics<\/strong>, and combine it with other data such as impressions, clicks, and cost from the advertising channels. This helps us calculate the cost per conversion for each channel more accurately.<\/p>\n<p>I know <em>multi-touch attribution<\/em> isn\u2019t perfect because it often doesn\u2019t consider view-through conversions. However, it\u2019s the best method we have for day-to-day operations. While <strong>Marketing Mix Modeling<\/strong> is an option, it requires a lot of data over a long period. So, for practical purposes, multi-touch attribution remains our best tool. As the saying goes, <code>all models are wrong, but some models are useful<\/code>.<\/p>\n<h2 id=\"what-are-the-requirements-of-the-solution-well-build\">What are the requirements of the solution we\u2019ll build?<\/h2>\n<p>The pipeline we\u2019ll build today needs to address the following things:<\/p>\n<p><strong>Accessibility<\/strong>: Make sure we can easily get data from different sources, such as Google Ads, Meta Ads, and GA4.<\/p>\n<p><strong>Data integration<\/strong>: Combine data from different sources accurately.<\/p>\n<p><strong>Storage<\/strong>: Create a data warehouse in <strong>Google BigQuery<\/strong> for the joined data and make it accessible to data visualization tools.<\/p>\n<p><strong>Maintenance<\/strong>: Find a way to automate these steps without needing manual intervention. That way stakeholders will have access to almost real-time data.<\/p>\n<h2 id=\"the-awesome-things-youll-learn\">The awesome things you\u2019ll learn<\/h2>\n<ol>\n<li>How to create a Google Cloud project.<\/li>\n<li>How to set up a virtual machine.<\/li>\n<li>How to access your virtual machine remotely.<\/li>\n<li>How to install <a href=\"https:\/\/mage.ai\">Mage.ai<\/a> on the virtual machine to handle the automation.<\/li>\n<li>How to retrieve data from the GA4 API in a production environment.<\/li>\n<li>How to retrieve data from the Google Ads API in a production environment.<\/li>\n<li>How to export data to Google BigQuery in a production environment.<\/li>\n<li>How to schedule a data pipeline that automatically updates every 5 minutes.<\/li>\n<\/ol>\n<h3 id=\"mageai-and-r\">Mage.ai and R<\/h3>\n<p>In my previous company, <a href=\"https:\/\/www.comtogether.com\">comtogether<\/a>, <a href=\"https:\/\/www.linkedin.com\/in\/casper-crause\/\">Casper Crause<\/a> took care of our data engineering needs, and he often did so with Python.<\/p>\n<p>Recently, I needed to automate multiple data pipelines and I had no idea how to do that in R. I thought, well, might as well learn Python. But then, before giving up, I googled one last time <code>Data Engineering in R<\/code>, and that\u2019s when I stumbled on <a href=\"https:\/\/mage.ai\">Mage.ai<\/a>!<\/p>\n<p>The screenshot below comes from Mage. Mage is a data engineering tool that allows you to build your <em>ETL<\/em> (extract, transform, and load) pipelines. What I love about Mage is that it is easy to use, you can visualize your data pipelines and it supports multiple programming languages: SQL, Python.. and R!<\/p>\n<div style=\"aspect-ratio: 1804 \/ 1156;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-ai-example.jpg\" title=\"mage-ai-example.jpg\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1156\" width=\"1804\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-ai-example.jpg#ZgotmplZ\" alt=\"mage-ai-example.jpg\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>I love R and I am so thankful that <a href=\"https:\/\/www.linkedin.com\/in\/dangtommy\/\">Tommy Dang<\/a> and his team included it in Mage.<\/p>\n<p>Many tools prefer Python since it is more widely adopted for data engineering tasks. Moreover, in a recent workshop on <a href=\"https:\/\/www.youtube.com\/watch?v=C0fNc8ZOpSI&amp;t=1324s&amp;ab_channel=DataSlinger\">Andreas Kretz\u2019s YouTube channel<\/a>, Tommy mentioned that they are going to include even more programming languages!<\/p>\n<p>In addition to building our pipeline, we\u2019ll use Mage to schedule it, as you can see in the example below.<\/p>\n<div style=\"aspect-ratio: 1151 \/ 233;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-schedule.jpg\" title=\"Mage scheduler\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"233\" width=\"1151\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-schedule.jpg#ZgotmplZ\" alt=\"Mage scheduler\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"google-cloud-platform-gcp-and-visual-studio-code\">Google Cloud Platform (GCP) and Visual Studio Code<\/h3>\n<p>You can run Mage on your local machine or in the cloud.<\/p>\n<p>Obviously, if you run it locally, your computer needs to be on all the time, which is not ideal. Therefore, we\u2019ll create a virtual machine (VM) on the <a href=\"https:\/\/cloud.google.com\/\">Google Cloud Platform<\/a> and run Mage from there.<\/p>\n<p>A virtual machine (VM) on GCP is like a computer in the cloud. It\u2019s not a physical machine you can touch; instead, it\u2019s a powerful, remote computer that you can use to run your software and store your data.<\/p>\n<p>To access the virtual machine from our computer, we\u2019ll use <a href=\"https:\/\/code.visualstudio.com\/download\">Visual Studio Code<\/a>, which is a lovely, <strong>free<\/strong> code editor that supports many programming languages.<\/p>\n<h2 id=\"walkthrough\">Walkthrough<\/h2>\n<h3 id=\"configure-the-google-cloud-platform\">Configure the Google Cloud Platform<\/h3>\n<h4 id=\"billing-and-costs\">Billing and costs<\/h4>\n<p>To use GCP, you need a payment method. But worry not, as of today, If you have never used GCP, you get a credit of $300. So, go to the Google Cloud Console and create an account: <a href=\"https:\/\/console.cloud.google.com\/welcome\">https:\/\/console.cloud.google.com\/welcome<\/a>.<\/p>\n<div style=\"aspect-ratio: 908 \/ 530;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/google-cloud-credits.jpg\" title=\"Google Cloud Credits\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"530\" width=\"908\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/google-cloud-credits.jpg#ZgotmplZ\" alt=\"Google Cloud Credits\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you have used your free credits, you need to add a credit card to your account, by going under \u201cBILLING\u201d:<\/p>\n<div style=\"aspect-ratio: 1396 \/ 1004;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/google-cloud-billing.jpg\" title=\"Google Cloud billing\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1004\" width=\"1396\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/google-cloud-billing.jpg#ZgotmplZ\" alt=\"Google Cloud billing\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h4 id=\"create-a-google-cloud-project\">Create a Google Cloud Project<\/h4>\n<p>In order to use GCP, we need a project. Later, everything that we\u2019ll do will be within this project. So, go back to <a href=\"https:\/\/console.cloud.google.com\/welcome\">https:\/\/console.cloud.google.com\/welcome<\/a> and create a new project by first clicking on the project selector in the top left.<\/p>\n<div style=\"aspect-ratio: 1070 \/ 238;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/project-selector.jpg\" title=\"Project selector\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"238\" width=\"1070\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/project-selector.jpg#ZgotmplZ\" alt=\"Project selector\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Then, click on \u201cNEW PROJECT\u201d.<\/p>\n<div style=\"aspect-ratio: 748 \/ 152;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-project.jpg\" title=\"New project\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"152\" width=\"748\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-project.jpg#ZgotmplZ\" alt=\"New project\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, name your project. I called my project <code>mage-ai-test<\/code>.<\/p>\n<div style=\"aspect-ratio: 1118 \/ 840;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-cloud-project.jpg\" title=\"New cloud project\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"840\" width=\"1118\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-cloud-project.jpg#ZgotmplZ\" alt=\"New cloud project\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Then simply wait until your project is created. Once you have selected your project, type \u201cvm instances\u201d in the search bar, and select \u201cVM instances\u201d.<\/p>\n<div style=\"aspect-ratio: 721 \/ 196;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/vm-instances.jpg\" title=\"VM instances\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"196\" width=\"721\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/vm-instances.jpg#ZgotmplZ\" alt=\"VM instances\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This will lead to the following screen:<\/p>\n<div style=\"aspect-ratio: 1208 \/ 826;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/compute-engine-api.jpg\" title=\"Compute engine API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"826\" width=\"1208\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/compute-engine-api.jpg#ZgotmplZ\" alt=\"Compute engine API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>On GCP, to use specific features, you must activate the corresponding APIs. For example, we\u2019ll enable the Google Analytics API later to get data from GA4. To make a virtual machine, we need to enable the Compute Engine API. Afterward, you\u2019ll see this screen, but we won\u2019t create a VM instance just yet\u2026<\/p>\n<div style=\"aspect-ratio: 1310 \/ 664;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-vm-instance.jpg\" title=\"Create VM instances\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"664\" width=\"1310\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-vm-instance.jpg#ZgotmplZ\" alt=\"Create VM instances\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>First, we need to create <strong>SSH keys<\/strong> that will allow us to access our virtual machine from our computer.<\/p>\n<p>SSH keys are like special keys that help your computer talk securely to another computer, such as a virtual machine.<\/p>\n<p>It\u2019s a way for your computer to prove it\u2019s really you when connecting to the virtual machine. It\u2019s like having a secret handshake between your computer and the virtual machine, making sure they can trust each other without needing to type in a password every time.<\/p>\n<h3 id=\"create-ssh-and-public-keys\">Create SSH and public keys<\/h3>\n<p>We need to create two SSH keys, a private and a public key. Think of SSH keys like a pair of magic keys for your online accounts. You have one key that you keep secret (the private key) and another key that you share with others (the public key).<\/p>\n<ol>\n<li><strong>Private Key (Secret Key):<\/strong> This is like the key to your front door that only you have. You keep it safe on your computer, and it\u2019s a secret. It\u2019s used to unlock and access your accounts securely.<\/li>\n<li><strong>Public Key (Shared Key):<\/strong> This is like a lock that matches your private key.<\/li>\n<\/ol>\n<p>When you connect to a server or service, you use your private key to prove you are who you say you are. The server then checks this with your public key to make sure it\u2019s really you. This way, even if someone gets your public key, they can\u2019t do anything without the private key, which stays safe on your computer. It\u2019s a bit like having a special lock and key where only your key can open it.<\/p>\n<p>To create your keys, hop to the terminal in your local machine and type the following code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">ssh-keygen -t rsa -f ~\/.ssh\/mage-ai-test -C arbenkqiku<\/code><\/pre>\n<\/div>\n<p>The end of the code should be your username, in my case <code>arbenkqiku<\/code>. If you don\u2019t know your user name, type <code>whoami<\/code> in the terminal and press enter. This will output your username.<\/p>\n<p>Once you enter the code mentioned above, you\u2019ll be prompted to insert your computer\u2019s password, if you have any. Once you add your password, your SSH keys will be created.<\/p>\n<div style=\"aspect-ratio: 1036 \/ 536;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-ssh-key.jpg\" title=\"Create SSH key\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"536\" width=\"1036\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-ssh-key.jpg#ZgotmplZ\" alt=\"Create SSH key\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, go to the directory where your SSH keys can be found. <code>cd<\/code> stands for \u201cchange directory\u201d:<\/p>\n<p>This is where your public private and public SSH keys are located.<\/p>\n<p>Now, type the following code to display the content of your public SSH key in the terminal.<\/p>\n<p>This will show the content of your public SSH key that we will later paste into our VM.<\/p>\n<div style=\"aspect-ratio: 1132 \/ 260;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/public-key.jpg\" title=\"Public key\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"260\" width=\"1132\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/public-key.jpg#ZgotmplZ\" alt=\"Public key\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"create-a-vm-instance-and-connect-to-it-by-using-our-ssh-keys\">Create a VM instance and connect to it by using our SSH keys<\/h3>\n<p>Now, let\u2019s go back to Google Cloud Platform and click on \u201cCREATE INSTANCE\u201d in the VM instances overview.<\/p>\n<div style=\"aspect-ratio: 1310 \/ 664;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-new-vm-instance.jpg\" title=\"Create new VM instance\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"664\" width=\"1310\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-new-vm-instance.jpg#ZgotmplZ\" alt=\"Create new VM instance\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Give a name to the VM instance and select the region closest to you:<\/p>\n<div style=\"aspect-ratio: 1414 \/ 392;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/name-and-region-of-vm-instance.jpg\" title=\"Name and region for VM instance\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"392\" width=\"1414\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/name-and-region-of-vm-instance.jpg#ZgotmplZ\" alt=\"Name and region for VM instance\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Go to the \u201cBoot disk\u201d section and click on \u201cCHANGE\u201d:<\/p>\n<div style=\"aspect-ratio: 1398 \/ 478;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/change-boot-disk.jpg\" title=\"Change boot disk\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"478\" width=\"1398\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/change-boot-disk.jpg#ZgotmplZ\" alt=\"Change boot disk\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Select the following options.<\/p>\n<div style=\"aspect-ratio: 1120 \/ 1088;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/advanced-boot-disk-options.jpg\" title=\"Advanced boot disk options\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1088\" width=\"1120\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/advanced-boot-disk-options.jpg#ZgotmplZ\" alt=\"Advanced boot disk options\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Under Firewall, select the following options:<\/p>\n<div style=\"aspect-ratio: 934 \/ 306;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/firewall-options.jpg\" title=\"Firewall options\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"306\" width=\"934\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/firewall-options.jpg#ZgotmplZ\" alt=\"Firewall options\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This is important, as otherwise we won\u2019t be able to access Mage by using the IP address of our VM, you\u2019ll understand later what I mean by this.<\/p>\n<p>Under Advanced Options &gt; Security, click on \u201cADD ITEM\u201d. Here is where we\u2019ll add our <strong>public SSH key<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1098 \/ 1032;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/add-public-key-to-vm.jpg\" title=\"Add public key to VM\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1032\" width=\"1098\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/add-public-key-to-vm.jpg#ZgotmplZ\" alt=\"Add public key to VM\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Copy the entire SSH public key and paste it.<\/p>\n<div style=\"aspect-ratio: 1052 \/ 418;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/paste-ssh-key.jpg\" title=\"Paste SSH key\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"418\" width=\"1052\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/paste-ssh-key.jpg#ZgotmplZ\" alt=\"Paste SSH key\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Finally, click on \u201cCREATE\u201d. It may take some time to create the VM.<\/p>\n<p>Once done, your new VM will appear here. Also, you\u2019ll see that your VM will have an \u201cExternal IP\u201d.<\/p>\n<div style=\"aspect-ratio: 1089 \/ 257;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/vm-external-ip.jpg\" title=\"VM with external IP\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"257\" width=\"1089\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/vm-external-ip.jpg#ZgotmplZ\" alt=\"VM with external IP\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>You can use this \u201cExternal IP\u201d and your SSH private key to connect to this VM. Let\u2019s do this!<\/p>\n<p>Go back to the terminal in your local machine and go to the directory where the SSH keys are located:<\/p>\n<p>Next, type this command:<\/p>\n<p>I\u2019ll break it down to you so you know what to replace:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">ssh -i name_of_private_key user_name@gcp_vm_instance_external_ip<\/code><\/pre>\n<\/div>\n<p>You\u2019ll likely will be prompted to enter your password again, and also to add the \u201cExternal IP\u201d as a host. Just follow the instructions and you should be able to connect to your VM.<\/p>\n<p>As you can see from the image below, we connected to the VM named <code>mage-demo-test<\/code>. And if you recall, in \u201cBoot disk\u201d options, we selected Ubuntu as our operating system!<\/p>\n<div style=\"aspect-ratio: 944 \/ 764;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ubuntu-vm-remote.jpg\" title=\"Remote connection to VM\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"764\" width=\"944\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ubuntu-vm-remote.jpg#ZgotmplZ\" alt=\"Remote connection to VM\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"install-visual-studio-code-and-access-the-vm\">Install Visual Studio Code and access the VM<\/h3>\n<p>We could do this whole process through the terminal, but it is much more user-friendly to do it through Visual Studio Code. Visual Studio Code is a very powerful code editor. Go to this link: <a href=\"https:\/\/code.visualstudio.com\/download,\">https:\/\/code.visualstudio.com\/download,<\/a> and download Visual Studio Code.<\/p>\n<p>Once you have installed it, go to \u201cExtensions\u201d and install \u201cRemote &#8211; SSH\u201d.<\/p>\n<div style=\"aspect-ratio: 1025 \/ 303;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/remote-ssh-code-extension.jpg\" title=\"Remote SSH code extension\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"303\" width=\"1025\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/remote-ssh-code-extension.jpg#ZgotmplZ\" alt=\"Remote SSH code extension\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In Visual Studio Code, go the the search bar and type <code>&gt;<\/code>, and then select the following option:<\/p>\n<div style=\"aspect-ratio: 1228 \/ 200;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/open-ssh-config.jpg\" title=\"Open SSH configuration file\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"200\" width=\"1228\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/open-ssh-config.jpg#ZgotmplZ\" alt=\"Open SSH configuration file\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In the configuration file that will open, you need to enter your details. Essentially, we\u2019re providing the details to connect to our VM.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">Host mage-demo-test <span style=\"color:#aaa;font-style:italic\"># Give a name to your host<\/span>\n  HostName 34.65.231.180 <span style=\"color:#aaa;font-style:italic\"># Replace with the External IP address in GCP<\/span>\n  User arbenkqiku <span style=\"color:#aaa;font-style:italic\"># Replace this with your user name<\/span>\n  IdentityFile \/Users\/arbenkqiku\/.ssh\/mage-ai-test <span style=\"color:#aaa;font-style:italic\"># Path to private SSH key<\/span><\/code><\/pre>\n<\/div>\n<p>Now, we still have to go back to the terminal one last time and type this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"color:#0aa\">eval<\/span> <span style=\"color:#00a\">$(<\/span>ssh-agent<span style=\"color:#00a\">)<\/span>\nssh-add \/Users\/arbenkqiku\/.ssh\/mage-ai-test <span style=\"color:#aaa;font-style:italic\"># Path to private SSH key<\/span><\/code><\/pre>\n<\/div>\n<p>Then, type your password when prompted. This basically means that you can use your password when you try to access the VM through Visual Studio Code.<\/p>\n<div style=\"aspect-ratio: 1108 \/ 156;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ssh-add-command.jpg\" title=\"The ssh-add command\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"156\" width=\"1108\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ssh-add-command.jpg#ZgotmplZ\" alt=\"The ssh-add command\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, go back to the search bar of Visual Studio Code, type <code>&gt;<\/code> and select the following option:<\/p>\n<div style=\"aspect-ratio: 1210 \/ 182;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/code-connect-to-ssh-host.jpg\" title=\"Connect to SSH host\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"182\" width=\"1210\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/code-connect-to-ssh-host.jpg#ZgotmplZ\" alt=\"Connect to SSH host\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Among others, it should suggest the host that you just created, click on that host:<\/p>\n<div style=\"aspect-ratio: 1208 \/ 174;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/choose-ssh-host.jpg\" title=\"Choose SSH host\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"174\" width=\"1208\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/choose-ssh-host.jpg#ZgotmplZ\" alt=\"Choose SSH host\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>A new window will open. In the search bar, type your password:<\/p>\n<div style=\"aspect-ratio: 1226 \/ 148;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ssh-passphrase.jpg\" title=\"Enter passphrase for SSH\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"148\" width=\"1226\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ssh-passphrase.jpg#ZgotmplZ\" alt=\"Enter passphrase for SSH\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, click on the \u201cRemote Explorer\u201d icon, and it should show that you connected to your VM:<\/p>\n<div style=\"aspect-ratio: 894 \/ 966;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/remote-explorer-vm.jpg\" title=\"Remote explorer with the VM\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"966\" width=\"894\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/remote-explorer-vm.jpg#ZgotmplZ\" alt=\"Remote explorer with the VM\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>On the top right, click this icon to display the terminal below:<\/p>\n<div style=\"aspect-ratio: 820 \/ 344;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/display-terminal-icon.jpg\" title=\"Display terminal icon\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"344\" width=\"820\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/display-terminal-icon.jpg#ZgotmplZ\" alt=\"Display terminal icon\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now click on \u201cTERMINAL\u201d. Congratulations, you have accessed your VM through Visual Studio Code!<\/p>\n<div style=\"aspect-ratio: 1010 \/ 508;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/access-terminal-success.jpg\" title=\"access VM through code terminal\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"508\" width=\"1010\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/access-terminal-success.jpg#ZgotmplZ\" alt=\"access VM through code terminal\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"install-mage\">Install Mage<\/h3>\n<p>To install mage on GCP, I largely followed <a href=\"https:\/\/www.youtube.com\/watch?v=C0fNc8ZOpSI&amp;t=696s&amp;ab_channel=DataSlinger\">this tutorial<\/a>, but I will also explain every step here. First of all, let\u2019s create a directory <strong>in our VM<\/strong> for mage:<\/p>\n<p>Now, if you type the following code, you should be able to see the newly created folder:<\/p>\n<p>Then, let\u2019s access the folder:<\/p>\n<p>Now, to install mage, we need to first install <strong>Docker<\/strong>.<\/p>\n<p>Docker is a platform for developing, shipping, and running applications. It uses containerization technology to package an application and its dependencies together into a single unit called a \u201ccontainer\u201d.<\/p>\n<p>In the <code>mage-demo<\/code> folder, let\u2019s download a GitHub repo that contains the installation for Docker:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">git clone https:\/\/github.com\/MichaelShoemaker\/DockerComposeInstall.git<\/code><\/pre>\n<\/div>\n<p>Let\u2019s access the folder that contains the Docker installation:<\/p>\n<p>Let\u2019s modify the file to make it executable:<\/p>\n<p>Then, let\u2019s run it:<\/p>\n<p>Type this to verify that Docker has been installed correctly:<\/p>\n<p>This should show the following message:<\/p>\n<div style=\"aspect-ratio: 1184 \/ 278;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/hello-docker.jpg\" title=\"Hello from Docker\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"278\" width=\"1184\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/hello-docker.jpg#ZgotmplZ\" alt=\"Hello from Docker\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, let\u2019s go back to the initial directory:<\/p>\n<p>Now, we can finally install mage with this command:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">docker run -it -p 6789:6789 -v <span style=\"color:#00a\">$(<\/span><span style=\"color:#0aa\">pwd<\/span><span style=\"color:#00a\">)<\/span>:\/home\/src --restart always mageai\/mageai \/app\/run_app.sh mage start mage-ai-test<\/code><\/pre>\n<\/div>\n<p>with the command <code>--restart always<\/code>, we\u2019re asking the VM to always restart mage whenever the VM is shut down and later restarted.<\/p>\n<p>At the end, <code>mage-ai-test<\/code> represents the name of our project.<\/p>\n<p>Now, to access mage through our External IP from GCP, we need to hop back on GCP first, as we need to create a <strong>firewall rule<\/strong>.<\/p>\n<p>This is necessary to control and regulate incoming and outgoing traffic to and from your VM on Google Cloud Platform. When you want to access mage through your External IP from GCP, a firewall rule is needed to explicitly allow the traffic to reach your VM.<\/p>\n<p>Browse to <a href=\"https:\/\/console.cloud.google.com\/net-security\/firewall-manager\/firewall-policies\/list\">Firewall<\/a> in the Google Cloud Platform.<\/p>\n<p>Click on \u201cCREATE FIREWALL RULE\u201d:<\/p>\n<div style=\"aspect-ratio: 1066 \/ 316;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-firewall-rule.jpg\" title=\"Create firewall rule\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"316\" width=\"1066\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-firewall-rule.jpg#ZgotmplZ\" alt=\"Create firewall rule\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Select the following options and click on \u201cCREATE\u201d:<\/p>\n<div style=\"aspect-ratio: 1112 \/ 962;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/firewall-options.jpg\" title=\"Firewall options\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"962\" width=\"1112\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/firewall-options.jpg#ZgotmplZ\" alt=\"Firewall options\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Basically, with this firewall rule in place, it means we can access mage via the external IP address by using port number 6789.<\/p>\n<p>Now, if you type <strong>your VM external IP<\/strong> followed by <code>:6789<\/code> in your web browser you should be able to access mage.<\/p>\n<p>For example, this is the URL I would use with my configuration: <code>http:\/\/34.65.231.180:6789<\/code>.<\/p>\n<div style=\"aspect-ratio: 1084 \/ 666;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-test.jpg\" title=\"Test mage\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"666\" width=\"1084\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-test.jpg#ZgotmplZ\" alt=\"Test mage\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As you can see, <code>mage-ai-test<\/code> was the name of our project in a previous command.<\/p>\n<p>Congrats, now you can create data pipelines that will run in the cloud!<\/p>\n<h3 id=\"create-the-data-pipeline\">Create the data pipeline<\/h3>\n<p>Now, we can finally create the pipeline. Here are the four parts that will compose our data pipeline:<\/p>\n<ul>\n<li>Retrieve data from GA4<\/li>\n<li>Retrieve data from Google Ads<\/li>\n<li>Join the data<\/li>\n<li>Export the data to BigQuery<\/li>\n<\/ul>\n<h4 id=\"load-data-from-the-ga4-api\">Load data from the GA4 API<\/h4>\n<p>To start, click on <strong>New pipeline<\/strong> &gt; <strong>Standard (batch)<\/strong>:<\/p>\n<div style=\"aspect-ratio: 1614 \/ 718;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-batch-pipeline.jpg\" title=\"New batch pipeline\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"718\" width=\"1614\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-batch-pipeline.jpg#ZgotmplZ\" alt=\"New batch pipeline\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>On the left side, you can see all your files, even the pipeline that we have just created.<\/p>\n<div style=\"aspect-ratio: 1303 \/ 756;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/files-in-pipeline.jpg\" title=\"Files in pipeline\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"756\" width=\"1303\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/files-in-pipeline.jpg#ZgotmplZ\" alt=\"Files in pipeline\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In the middle, you can see the blocks that you can use to build your pipelines. In this guide, we\u2019ll use <strong>Data loader<\/strong>, <strong>Transformer<\/strong>, and <strong>Data exporter<\/strong> blocks:<\/p>\n<div style=\"aspect-ratio: 1106 \/ 334;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-blocks.jpg\" title=\"Mage blocks\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"334\" width=\"1106\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-blocks.jpg#ZgotmplZ\" alt=\"Mage blocks\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As mentioned previously, you can use Python, SQL, and R in each block. In our case, we\u2019ll use <strong>R<\/strong>. So, click on <strong>Data Loader<\/strong> and select R:<\/p>\n<div style=\"aspect-ratio: 1094 \/ 428;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/use-r.jpg\" title=\"Use R\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"428\" width=\"1094\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/use-r.jpg#ZgotmplZ\" alt=\"Use R\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Name the block <code>ga4<\/code>, then click <strong>Save and add block<\/strong>.<\/p>\n<p>You should now see the block on the right, together with a sample R code.<\/p>\n<div style=\"aspect-ratio: 1074 \/ 455;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/sample-r-code.jpg\" title=\"Block with sample R\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"455\" width=\"1074\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/sample-r-code.jpg#ZgotmplZ\" alt=\"Block with sample R\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>To install and load packages, mage uses the <code>pacman<\/code> package. Once you load <code>pacman<\/code>, you can install packages by using:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\">pacman::<span style=\"color:#0a0\">p_load<\/span>(package1, package2, package3)<\/code><\/pre>\n<\/div>\n<p>\u200b<br \/>\nThe first time you run the <code>p_load()<\/code> function, it will install a package, and then it will simply load it. For this block, we\u2019ll install three packages:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\npacman::<span style=\"color:#0a0\">p_load<\/span>(dplyr, purrr, googleAnalyticsR)\n\nload_data function() {\n\n}<\/code><\/pre>\n<\/div>\n<p>\u200b<br \/>\nIn order to access GA4 data by using the <code>googleAnalyticsR<\/code> package, developed by <a href=\"https:\/\/code.markedmondson.me\/\">Mark Edmondson<\/a>, you need an access token.<\/p>\n<p>An access token is like your digital ID card; it confirms your identity and verifies that you truly have permission to access the GA4 properties you\u2019re attempting to retrieve data from.<\/p>\n<p>To get an access token, you can run the following function in the <strong>RStudio console<\/strong> in your local machine: <code>ga_auth()<\/code>.<\/p>\n<p>Once you run this function, you\u2019ll be redirected to a browser window where you\u2019ll select your account:<\/p>\n<div style=\"aspect-ratio: 938 \/ 606;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/select-google-account.jpg\" title=\"select google account\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"606\" width=\"938\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/select-google-account.jpg#ZgotmplZ\" alt=\"select google account\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>With this, you are basically giving permission to the <code>googleAnalyticsR<\/code> package to access your GA4 properties.<\/p>\n<p>However, the problem is that we\u2019ll run our data pipeline in a production environment where you cannot interact with the browser.<\/p>\n<p>So, we need to find another way to solve this problem.<\/p>\n<p>In fact, if I try to run the function <code>ga_auth()<\/code> on Mage, it throws an error:<\/p>\n<div style=\"aspect-ratio: 1130 \/ 1096;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-auth-error.jpg\" title=\"GA auth error\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1096\" width=\"1130\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-auth-error.jpg#ZgotmplZ\" alt=\"GA auth error\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>So, we need to generate a Google Analytics token that we can use in a production environment.<\/p>\n<p>First, let\u2019s go back to GCP and browse to <a href=\"https:\/\/console.cloud.google.com\/apis\/dashboard\">Enabled APIs &amp; services<\/a>.<\/p>\n<p>Click on \u201cENABLE APIS AND SERVICES\u201d.<\/p>\n<div style=\"aspect-ratio: 1018 \/ 634;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/enable-apis-services.jpg\" title=\"Enable APIs services\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"634\" width=\"1018\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/enable-apis-services.jpg#ZgotmplZ\" alt=\"Enable APIs services\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Search for <code>Google Analytics<\/code>, click the <strong>Google Analytics Reporting API<\/strong> result, and then choose <strong>ENABLE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1014 \/ 484;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/enable-ga-api.jpg\" title=\"Enable GA API\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"484\" width=\"1014\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/enable-ga-api.jpg#ZgotmplZ\" alt=\"Enable GA API\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>This means that our project is now eligible to use the <strong>Google Analytics Reporting API<\/strong>.<\/p>\n<p>Next, repeat these API-enabling steps for the <strong>Google Analytics Data API<\/strong>.<\/p>\n<p>Once done, we have the APIs enabled but we still haven\u2019t created the required <strong>token<\/strong>.<\/p>\n<p>Browse to <a href=\"https:\/\/console.cloud.google.com\/apis\/credentials\">Credentials<\/a> in the Google Cloud console.<\/p>\n<p>Hover over \u201cCREATE CREDENTIALS\u201d and click on <strong>Service account<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1138 \/ 604;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-service-account.jpg\" title=\"Create service account\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"604\" width=\"1138\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-service-account.jpg#ZgotmplZ\" alt=\"Create service account\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Give the service account a name and then click <strong>CREATE AND CONTINUE<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1106 \/ 706;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-and-continue-service-account.jpg\" title=\"Continue with service account\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"706\" width=\"1106\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-and-continue-service-account.jpg#ZgotmplZ\" alt=\"Continue with service account\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Give the service account the <strong>Editor<\/strong> role and then click on <strong>Continue<\/strong>.<\/p>\n<div style=\"aspect-ratio: 922 \/ 1018;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/set-editor-role.jpg\" title=\"Set editor role\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1018\" width=\"922\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/set-editor-role.jpg#ZgotmplZ\" alt=\"Set editor role\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Finally, click on <strong>DONE<\/strong>.<\/p>\n<p>Now that the service account has been created, go back to the Credentials view and you\u2019ll see the account that you just created. Click on it.<\/p>\n<div style=\"aspect-ratio: 1428 \/ 1152;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/click-service-account-edit.jpg\" title=\"Click to edit service account\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1152\" width=\"1428\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/click-service-account-edit.jpg#ZgotmplZ\" alt=\"Click to edit service account\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Then, click the <strong>KEYS<\/strong> tab and choose to <strong>Create new key<\/strong>.<\/p>\n<div style=\"aspect-ratio: 994 \/ 870;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-new-key.jpg\" title=\"Create new key\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"870\" width=\"994\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-new-key.jpg#ZgotmplZ\" alt=\"Create new key\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Select <strong>JSON<\/strong> as the key type and click <strong>Create<\/strong>.<\/p>\n<p>This should download your key as a JSON file.<\/p>\n<p>Store it in a safe place. Basically, the service account is like an account that has permission to act on your behalf. When you want your application or service to communicate with the GA4 API, it needs to prove its identity. Instead of using a user\u2019s personal Google account, which may not be appropriate for server-to-server communication, you can use a service account.<\/p>\n<p>Now, as if it were a real user, we need to go to the GA4 property and add our service account email. So, go back to <a href=\"https:\/\/console.cloud.google.com\/apis\/credentials\">Credentials<\/a> and copy your service account\u2019s <strong>email address<\/strong>:<\/p>\n<div style=\"aspect-ratio: 1016 \/ 312;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/copy-service-account-email.jpg\" title=\"Copy service account email\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"312\" width=\"1016\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/copy-service-account-email.jpg#ZgotmplZ\" alt=\"Copy service account email\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, open Google Analytics 4, go to your property, and click on <strong>Property access management<\/strong> in Admin:<\/p>\n<div style=\"aspect-ratio: 892 \/ 702;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/property-access-management.jpg\" title=\"Property access management\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"702\" width=\"892\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/property-access-management.jpg#ZgotmplZ\" alt=\"Property access management\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Add your service account email address to the list of users, give it <strong>Viewer<\/strong> permissions, and click on <strong>Add<\/strong> to add the service account as a user to the GA4 property.<\/p>\n<p>Now, before adding code to Mage, I like to test it on my local machine to make sure that everything works properly.<\/p>\n<p>So, on your local machine, open a new <strong>R script<\/strong> and try the following code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Packages ----<\/span>\n<span style=\"color:#0a0\">library<\/span>(purrr)\n<span style=\"color:#0a0\">library<\/span>(dplyr)\n<span style=\"color:#0a0\">library<\/span>(googleAnalyticsR)\n\n<span style=\"color:#aaa;font-style:italic\"># Authenticate ----  <\/span>\n\n<span style=\"color:#aaa;font-style:italic\"># path to your JSON service account that we saved earlier<\/span>\n<span style=\"color:#0a0\">ga_auth<\/span>(json_file = <span style=\"color:#a50\">\"\/Users\/arbenkqiku\/Desktop\/mage-ai\/mage-ai-test-405614-2e1e1c865c18.json\"<\/span>)  <\/code><\/pre>\n<\/div>\n<p>\u200b<br \/>\nIf everything works correctly, you should see the following message:<\/p>\n<div style=\"aspect-ratio: 880 \/ 44;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-auth-test-worked.jpg\" title=\"GA auth test worked\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"44\" width=\"880\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-auth-test-worked.jpg#ZgotmplZ\" alt=\"GA auth test worked\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>That means that your pipeline can now communicate with the GA4 Reporting API without any extra authentication flows.<\/p>\n<p>Now, what I want to retrieve from GA4 are the sessions where a lead generation conversion event happened.<\/p>\n<p>In the case of this client of mine, either someone submitted a form, clicked on the WhatsApp icon to talk to them privately, or clicked on the phone icon to call them.<\/p>\n<p>So, in the the next piece of code I want to create a filter with all the event names I am interested in, namely the event names equal to <code>form_submit_lead<\/code> or <code>whatsapp_click<\/code> or <code>phone_click<\/code>.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># GA4 property ID<\/span>\nproperty_id = <span style=\"color:#a50\">\"1234567\"<\/span>\n\n<span style=\"color:#aaa;font-style:italic\"># Create filter<\/span>\ngoals_filter = <span style=\"color:#0a0\">ga_data_filter<\/span>(<span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"form_submit_lead\"<\/span> | <span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"whatsapp_click\"<\/span> | <span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"phone_click\"<\/span>)<\/code><\/pre>\n<\/div>\n<p>In the next piece of code, we have the actual query to GA4:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Get conversions from GA4<\/span>\ngoals_data = <span style=\"color:#0a0\">ga_data<\/span>(propertyId = property_id,         \n                     date_range = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"2023-10-01\"<\/span>, <span style=\"color:#a50\">\"2023-11-08\"<\/span>),        \n                     dimensions = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>),        \n                     metrics = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"sessions\"<\/span>),        \n                     dim_filter = goals_filter) %&gt;%     \n\n<span style=\"color:#aaa;font-style:italic\"># rename sessions to goals<\/span>\n<span style=\"color:#0a0\">set_names<\/span>(<span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>, <span style=\"color:#a50\">\"goals\"<\/span>))<\/code><\/pre>\n<\/div>\n<p>Basically, we\u2019re getting the sessions from 1st October 2023 until 8th November 2023, segmented by date, and only when one of the events mentioned earlier occurred. This is what the final table looks like in my case:<\/p>\n<div style=\"aspect-ratio: 732 \/ 800;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-table-results.jpg\" title=\"GA table results\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"800\" width=\"732\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-table-results.jpg#ZgotmplZ\" alt=\"GA table results\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>It is not always easy to know what certain fields are called in the GA4 API. You can go to <a href=\"https:\/\/ga-dev-tools.google\/ga4\/dimensions-metrics-explorer\/\">this website<\/a> and look for a specific field. For example, if we look for \u201cchannel\u201d, you can see all the different fields that contain \u201cchannel\u201d and what they are called in the GA4 API.<\/p>\n<div style=\"aspect-ratio: 883 \/ 765;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-explorer-dims.jpg\" title=\"GA explorer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"765\" width=\"883\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ga-explorer-dims.jpg#ZgotmplZ\" alt=\"GA explorer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, in addition to retrieving the sessions where a conversion event occurred, I also want to retrieve the sessions segmented by day, so I\u2019ll use this query:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Get sessions from GA4<\/span>\nsessions_data = <span style=\"color:#0a0\">ga_data<\/span>(propertyId = property_id,                      \n                        date_range = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"2023-10-01\"<\/span>, <span style=\"color:#a50\">\"2023-11-08\"<\/span>),        \n                        dimensions = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>),                     \n                        metrics = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"sessions\"<\/span>))<\/code><\/pre>\n<\/div>\n<p>This returns a table of sessions segmented by date.<\/p>\n<p>Now, to join the sessions with the conversions:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Merge GA4 goals and sessions<\/span>\nsessions_goals_ga4 = sessions_data %&gt;%   \n\t<span style=\"color:#aaa;font-style:italic\"># join sessions with goals  <\/span>\n<span style=\"color:#0a0\">full_join<\/span>(goals_data) %&gt;%   \n\t<span style=\"color:#aaa;font-style:italic\"># replace all NAs with 0  <\/span>\n<span style=\"color:#0a0\">replace<\/span>(<span style=\"color:#0a0\">is.na<\/span>(.), <span style=\"color:#099\">0<\/span>)<\/code><\/pre>\n<\/div>\n<p>This is the final result:<\/p>\n<div style=\"aspect-ratio: 855 \/ 813;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/sessions-by-goals.jpg\" title=\"Sessions by goals\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"813\" width=\"855\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/sessions-by-goals.jpg#ZgotmplZ\" alt=\"Sessions by goals\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Here is the <strong>complete code<\/strong>.<\/p>\n<p>At the end of the script, I added the <code>sessions_goals_ga4<\/code> dataframe. This is because in Mage, we\u2019re using this code within a <strong>Data Loader<\/strong> block. We need to return a dataframe for the next block, otherwise the next block doesn\u2019t have any data to play with.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Packages ----<\/span>\n<span style=\"color:#0a0\">library<\/span>(purrr)\n<span style=\"color:#0a0\">library<\/span>(dplyr)\n<span style=\"color:#0a0\">library<\/span>(googleAnalyticsR)\n\n<span style=\"color:#aaa;font-style:italic\"># Authenticate ----  <\/span>\n\t<span style=\"color:#aaa;font-style:italic\"># path to your JSON service account that we save earlier<\/span>\n<span style=\"color:#0a0\">ga_auth<\/span>(json_file = <span style=\"color:#a50\">\"\/Users\/arbenkqiku\/Desktop\/mage-ai\/mage-ai-test-405614-2e1e1c865c18.json\"<\/span>)  \n\n<span style=\"color:#aaa;font-style:italic\"># GA4 property ID<\/span>\nproperty_id = <span style=\"color:#a50\">\"1234567\"<\/span>\n\n<span style=\"color:#aaa;font-style:italic\">#\u00a0Create filter<\/span>\ngoals_filter = <span style=\"color:#0a0\">ga_data_filter<\/span>(<span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"form_submit_lead\"<\/span> | <span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"whatsapp_click\"<\/span> | <span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"phone_click\"<\/span>)\n\n<span style=\"color:#aaa;font-style:italic\"># Get conversions from GA4<\/span>\ngoals_data = <span style=\"color:#0a0\">ga_data<\/span>(propertyId = property_id,         \n                     date_range = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"2023-10-01\"<\/span>, <span style=\"color:#a50\">\"2023-11-08\"<\/span>),        \n                     dimensions = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>),        \n                     metrics = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"sessions\"<\/span>),        \n                     dim_filter = goals_filter) %&gt;%     \n\n\t<span style=\"color:#aaa;font-style:italic\"># rename sessions to goals<\/span>\n<span style=\"color:#0a0\">set_names<\/span>(<span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>, <span style=\"color:#a50\">\"goals\"<\/span>))\n\n<span style=\"color:#aaa;font-style:italic\"># Get sessions from GA4<\/span>\nsessions_data = <span style=\"color:#0a0\">ga_data<\/span>(propertyId = property_id,                      \n                        date_range = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"2023-10-01\"<\/span>, <span style=\"color:#a50\">\"2023-11-08\"<\/span>),        \n                        dimensions = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>),                     \n                        metrics = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"sessions\"<\/span>))\n\n<span style=\"color:#aaa;font-style:italic\"># Merge GA4 goals and sessions<\/span>\nsessions_goals_ga4 = sessions_data %&gt;%   \n\t<span style=\"color:#aaa;font-style:italic\"># join sessions with goals  <\/span>\n<span style=\"color:#0a0\">full_join<\/span>(goals_data) %&gt;%   \n\t<span style=\"color:#aaa;font-style:italic\"># replace all NAs with 0  <\/span>\n<span style=\"color:#0a0\">replace<\/span>(<span style=\"color:#0a0\">is.na<\/span>(.), <span style=\"color:#099\">0<\/span>)\n\n<span style=\"color:#aaa;font-style:italic\"># Final data frame for next block in mage.ai<\/span>\nsessions_goals_ga4<\/code><\/pre>\n<\/div>\n<p>Now, before we copy this code to Mage, we need to make our <strong>JSON service account<\/strong> key accessible to Mage, as for now it is only available on our local machine.<\/p>\n<p>Remember, Mage is installed on our <strong>virtual machine<\/strong>. We need to paste the JSON service account key there.<\/p>\n<p>Open <strong>Visual Studio Code<\/strong> and click on \u201cOpen\u201d.<\/p>\n<div style=\"aspect-ratio: 830 \/ 954;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/visual-studio-code-open.jpg\" title=\"Visual studio code open\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"954\" width=\"830\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/visual-studio-code-open.jpg#ZgotmplZ\" alt=\"Visual studio code open\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Go to the path where your JSON service account key is located in your local machine. You should be able to see your service account key in the left panel.<\/p>\n<div style=\"aspect-ratio: 722 \/ 790;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/vs-code-json-path.jpg\" title=\"VS code JSON path\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"790\" width=\"722\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/vs-code-json-path.jpg#ZgotmplZ\" alt=\"VS code JSON path\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Right-click and <strong>copy<\/strong> it.<\/p>\n<p>Next, go to the search bar, type <code>&gt;<\/code> and connect to your virtual machine.<\/p>\n<div style=\"aspect-ratio: 1300 \/ 310;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/connect-to-vm.jpg\" title=\"Connect to VM\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"310\" width=\"1300\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/connect-to-vm.jpg#ZgotmplZ\" alt=\"Connect to VM\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Once you are in the VM, click on \u201cOpen\u2026\u201d and access the folder where Mage is installed. Click on \u201cOK\u201d.<\/p>\n<div style=\"aspect-ratio: 1686 \/ 736;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/open-file-folder.jpg\" title=\"Open file or folder\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"736\" width=\"1686\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/open-file-folder.jpg#ZgotmplZ\" alt=\"Open file or folder\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>On the left side you should now see the files contained in that folder.<\/p>\n<p>Right-click in that area and choose <strong>Paste<\/strong> to paste your service account JSON file into the project.<\/p>\n<p>You should see your service account file now successfully added to the files in your VM.<\/p>\n<div style=\"aspect-ratio: 780 \/ 615;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/service-account-in-vm.jpg\" title=\"service account in VM\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"615\" width=\"780\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/service-account-in-vm.jpg#ZgotmplZ\" alt=\"service account in VM\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In Mage, you can use the function <code>list.files()<\/code> to see that the service account key is available.<\/p>\n<div style=\"aspect-ratio: 1158 \/ 1232;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/service-account-key-available.jpg\" title=\"Service account key available\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1232\" width=\"1158\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/service-account-key-available.jpg#ZgotmplZ\" alt=\"Service account key available\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now, take the code that we previously played with in RStudio and paste it into Mage. You need to make some adjustments, though.<\/p>\n<p>The main change is that the bulk of the code is now within the <code>load_data()<\/code> function. The only code that\u2019s run outside that function are the library loads.<\/p>\n<p>Another thing that changes is the path to the service account key. This now needs to reference the path to the file in your VM. As it should be in the root of your project, you just need to add the filename.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\npacman::<span style=\"color:#0a0\">p_load<\/span>(dplyr, purrr, googleAnalyticsR)\n\nload_data function() {\n    <span style=\"color:#aaa;font-style:italic\"># Specify your data loading logic here<\/span>\n    <span style=\"color:#aaa;font-style:italic\"># Return value: loaded dataframe<\/span>\n\n    <span style=\"color:#aaa;font-style:italic\"># Retrieve data ----<\/span>\n    <span style=\"color:#aaa;font-style:italic\"># path to your JSON service account<\/span>\n    <span style=\"color:#0a0\">ga_auth<\/span>(json_file = <span style=\"color:#a50\">\"mage-ai-test-405614-2e1e1c865c18.json\"<\/span>)\n\n    <span style=\"color:#aaa;font-style:italic\"># GA4 property ID<\/span>\n    property_id = <span style=\"color:#a50\">\"1234567\"<\/span>\n\n    <span style=\"color:#aaa;font-style:italic\"># Create filter<\/span>\n    goals_filter = <span style=\"color:#0a0\">ga_data_filter<\/span>(<span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"form_submit_lead\"<\/span> | <span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"whatsapp_click\"<\/span> | <span style=\"color:#a50\">\"eventName\"<\/span> == <span style=\"color:#a50\">\"phone_click\"<\/span>)\n\n    <span style=\"color:#aaa;font-style:italic\"># Get conversions from GA4<\/span>\n    goals_data = <span style=\"color:#0a0\">ga_data<\/span>(propertyId = property_id, \n                         date_range = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"2023-10-01\"<\/span>, <span style=\"color:#a50\">\"2023-11-08\"<\/span>),\n                         dimensions = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>),\n                         metrics = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"sessions\"<\/span>),\n                         dim_filter = goals_filter,\n    ) %&gt;% \n    \n    <span style=\"color:#0a0\">set_names<\/span>(<span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>, <span style=\"color:#a50\">\"goals\"<\/span>))\n\n    <span style=\"color:#aaa;font-style:italic\"># Get sessions from GA4<\/span>\n    sessions_data = <span style=\"color:#0a0\">ga_data<\/span>(propertyId = property_id, \n                            date_range = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"2023-10-01\"<\/span>, <span style=\"color:#a50\">\"2023-11-08\"<\/span>),\n                            dimensions = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span>),\n                            metrics = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"sessions\"<\/span>))\n\n    <span style=\"color:#aaa;font-style:italic\"># Merge GA4 goals and sessions<\/span>\n    sessions_goals_ga4 = sessions_data %&gt;% \n    <span style=\"color:#aaa;font-style:italic\"># join sessions with goals<\/span>\n    <span style=\"color:#0a0\">full_join<\/span>(goals_data) %&gt;% \n    <span style=\"color:#aaa;font-style:italic\"># replace all NAs with 0<\/span>\n    <span style=\"color:#0a0\">replace<\/span>(<span style=\"color:#0a0\">is.na<\/span>(.), <span style=\"color:#099\">0<\/span>)\n\n    <span style=\"color:#aaa;font-style:italic\"># Final data frame<\/span>\n    sessions_goals_ga4\n}<\/code><\/pre>\n<\/div>\n<p>If everything worked properly, Mage will provide a preview of the data retrieved:<\/p>\n<div style=\"aspect-ratio: 779 \/ 571;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-preview.jpg\" title=\"Mage preview\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"571\" width=\"779\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-preview.jpg#ZgotmplZ\" alt=\"Mage preview\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>As you can see, our Data loader block has a green tick next to it, which means that it was able to run successfully.<\/p>\n<div style=\"aspect-ratio: 941 \/ 369;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/data-loader-worked.jpg\" title=\"Data loader worked\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"369\" width=\"941\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/data-loader-worked.jpg#ZgotmplZ\" alt=\"Data loader worked\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Later, we can use this data that we retrieved from GA4 for whatever purpose we want. However, before playing around with it, let\u2019s download some data from Google Ads!<\/p>\n<h4 id=\"load-data-from-the-google-ads-api\">Load data from the Google Ads API<\/h4>\n<p>To retrieve data from the Google Ads API, we\u2019ll use the R package <a href=\"https:\/\/selesnow.github.io\/rgoogleads\/\"><code>rgoogleads<\/code><\/a>, developed by <a href=\"https:\/\/github.com\/selesnow\/rgoogleads#author\">Alexey Seleznev<\/a>. Unfortunately, with this package it is not possible to use a service account key. Instead, we\u2019ll have to generate an access token by using the <a href=\"https:\/\/gargle.r-lib.org\/\"><code>gargle<\/code><\/a> package. The goal of <code>gargle<\/code>, as explained on their website, is to \u201ctake some of the agonizing pain out of working with Google APIs\u201d.<\/p>\n<p>First of all, you need to browse to the <a href=\"https:\/\/console.cloud.google.com\/marketplace\/product\/google\/googleads.googleapis.com\">Google Ads API<\/a> in Google Cloud Platform and click to <strong>Enable<\/strong> it.<\/p>\n<p>To access Google Ads data, we need to create the credentials for an application, because when we use the <code>rgoogleads<\/code> package to get data, Google sees it as an external app trying to access our ads info.<\/p>\n<p>So, when we attempt to fetch our Google Ads data, Google asks for our permission to let this app access our ads data. If we say yes, Google gives us an access token. This token then lets our computer talk to the Google Ads API without having to interact each time.<\/p>\n<p>Before doing anything, GCP will ask you to set up a \u201cconsent screen\u201d. This screen is like a friendly message to users, letting them know that our app wants to look at their Google Ads data.<\/p>\n<p>It\u2019s a way to make sure users are aware and agree to let our app access their information. To get started, browse to the <a href=\"https:\/\/console.cloud.google.com\/apis\/credentials\/consent\">OAuth consent screen<\/a> section of your GCP project.<\/p>\n<p>Here, click on \u201cCONFIGURE CONSENT SCREEN\u201d.<\/p>\n<div style=\"aspect-ratio: 1366 \/ 209;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/configure-consent-screen.jpg\" title=\"Configure consent screen\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"209\" width=\"1366\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/configure-consent-screen.jpg#ZgotmplZ\" alt=\"Configure consent screen\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Select <strong>External<\/strong> as the User Type and then click \u201cCREATE\u201d.<\/p>\n<p>Give your app a name and add your email address.<\/p>\n<div style=\"aspect-ratio: 1100 \/ 546;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/name-and-email-consent.jpg\" title=\"Name and email for consent\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"546\" width=\"1100\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/name-and-email-consent.jpg#ZgotmplZ\" alt=\"Name and email for consent\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Add your email to the <strong>Developer email address<\/strong>, too, and then click \u201cSAVE AND CONTINUE\u201d.<\/p>\n<p>In the next screen, click on \u201cADD OR REMOVE SCOPES\u201d. Scopes govern what your app is allowed to do with the APIs.<\/p>\n<p>Search for <code>google ads<\/code> and select the <strong>Google Ads API<\/strong>. Click UPDATE when done.<\/p>\n<div style=\"aspect-ratio: 1470 \/ 1190;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/add-google-ads-scope.jpg\" title=\"Add google ads scope\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1190\" width=\"1470\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/add-google-ads-scope.jpg#ZgotmplZ\" alt=\"Add google ads scope\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Then, click \u201cSAVE AND CONTINUE\u201d to proceed to the \u201cTest users\u201d step.<\/p>\n<p>Here, click \u201cADD USERS\u201d. Add <strong>your email address<\/strong> and click \u201cADD\u201d.<\/p>\n<div style=\"aspect-ratio: 1108 \/ 280;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/add-test-user.jpg\" title=\"Add test user\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"280\" width=\"1108\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/add-test-user.jpg#ZgotmplZ\" alt=\"Add test user\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Make sure to include your email because our app is currently in the \u201cTesting\u201d phase. During this phase, only the emails that are added can be used by the app. So, adding your email is crucial to get your Google Ads data.<\/p>\n<p>Click on \u201cSAVE AND CONTINUE\u201d to proceed to the Summary step, and then \u201cBACK TO DASHBOARD\u201d when done with configuring the consent screen.<\/p>\n<p>Now that the consent screen has been configured, you can browse to <a href=\"https:\/\/console.cloud.google.com\/apis\/credentials\">Credentials<\/a> again.<\/p>\n<p>Here, click on \u201cCREATE CREDENTIALS\u201d and this time choose <strong>OAuth client ID<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1218 \/ 702;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/oauth-client-id.jpg\" title=\"oauth client id\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"702\" width=\"1218\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/oauth-client-id.jpg#ZgotmplZ\" alt=\"oauth client id\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Under <strong>Application type<\/strong>, select <strong>Desktop app<\/strong>, give a name to your OAuth client ID, and click on \u201cCREATE\u201d:<\/p>\n<div style=\"aspect-ratio: 1090 \/ 818;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-oauth-id.jpg\" title=\"Create the oauth id\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"818\" width=\"1090\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-oauth-id.jpg#ZgotmplZ\" alt=\"Create the oauth id\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Download your client ID as a JSON file and click on OK.<\/p>\n<div style=\"aspect-ratio: 1050 \/ 1132;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/download-oauth-json.jpg\" title=\"Download JSON\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1132\" width=\"1050\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/download-oauth-json.jpg#ZgotmplZ\" alt=\"Download JSON\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Save it in a secure location. Now, let\u2019s go back to <strong>RStudio<\/strong> on our local machine. Open a new script and load these packages:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Packages<\/span>\n<span style=\"color:#0a0\">library<\/span>(gargle)\n<span style=\"color:#0a0\">library<\/span>(rgoogleads)<\/code><\/pre>\n<\/div>\n<p>\u200b<\/p>\n<p>Then, we\u2019ll import the OAuth Client ID credentials that we just created by using the function <code>gargle_oauth_client_from_json()<\/code>. The name of your client can be whatever you prefer:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Create gargle client<\/span>\nmy_client = <span style=\"color:#0a0\">gargle_oauth_client_from_json<\/span>(path = <span style=\"color:#a50\">\"\/Users\/arbenkqiku\/Desktop\/mage-ai\/mage-demo-client-id.json\"<\/span>,\n                                          name = <span style=\"color:#a50\">\"Google Ads App\"<\/span>)<\/code><\/pre>\n<\/div>\n<p>\u200b<\/p>\n<p>Then, we can add the following scope and email to our token request:<\/p>\n<p>\u200b<br \/>\nFinally, we can go through the process of acquiring a token by running this function:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Create a token by using Gargle<\/span>\nmy_token = <span style=\"color:#0a0\">gargle2.0_token<\/span>(email = email,  \n                           package = <span style=\"color:#a50\">\"rgoogleads\"<\/span>,  \n                           scope = scopes,  \n                           client = my_client)<\/code><\/pre>\n<\/div>\n<p>\u200b<\/p>\n<p>This will open a browser window.<\/p>\n<p>Do you recognize the name of the App? That\u2019s the name of our application! We\u2019re now going through the process of authorizing our app to access our Google Ads data. Now, select your email.<\/p>\n<div style=\"aspect-ratio: 926 \/ 638;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/authorize-app.jpg\" title=\"Authorize app\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"638\" width=\"926\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/authorize-app.jpg#ZgotmplZ\" alt=\"Authorize app\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Google will tell you that this app isn\u2019t verified, as its status is still \u201ctesting\u201d.<\/p>\n<p>However, it is our own app, so we can safely click on \u201cContinue\u201d.<\/p>\n<p>Authorize the app to \u201cSee, edit, create and delete your Google Ads accounts and data\u2026\u201d and click on \u201cContinue\u201d.<\/p>\n<div style=\"aspect-ratio: 838 \/ 1492;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/authorize-google-ads-access.jpg\" title=\"Authorize google ads access\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1492\" width=\"838\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/authorize-google-ads-access.jpg#ZgotmplZ\" alt=\"Authorize google ads access\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>If everything worked correctly, you should see a message saying that <code>Authentication complete. Please close this page and return to R<\/code>.<\/p>\n<p>Now, if we review the content of the variable <code>my_token<\/code>, which contains our access token, we can review the information again, for example the email associated with the token, the scopes, and so forth.<\/p>\n<div style=\"aspect-ratio: 1560 \/ 366;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/review-token.jpg\" title=\"Review token\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"366\" width=\"1560\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/review-token.jpg#ZgotmplZ\" alt=\"Review token\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>We can now test if the token works properly by running the <code>gads_auth()<\/code> function. Nothing should really happen here, as with the token we can authenticate non-interactively.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Authenticate by using the previously created token<\/span>\n<span style=\"color:#0a0\">gads_auth<\/span>(token = my_token)<\/code><\/pre>\n<\/div>\n<p>Let\u2019s run a simple function of the <code>rgoogleads<\/code> package to see if we can access our data:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># get list of accessible accounts<\/span>\n<span style=\"color:#0a0\">gads_get_accessible_customers<\/span>()<\/code><\/pre>\n<\/div>\n<p>Yes, I am able to retrieve the accounts that I have access to!<\/p>\n<div style=\"aspect-ratio: 861 \/ 597;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ads-accounts-listed.jpg\" title=\"Ads accounts listed\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"597\" width=\"861\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ads-accounts-listed.jpg#ZgotmplZ\" alt=\"Ads accounts listed\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>However, we are not ready for production yet. In fact, if we type this code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># where is the cache of the token located<\/span>\nmy_token$cache_path<\/code><\/pre>\n<\/div>\n<p>We\u2019ll get the result that the token is cached in a local directory, such as <code>~\/Library\/Caches\/gargle<\/code>.<\/p>\n<p>This means that when we try to load <code>my_token<\/code> in production, it will look for the local path instead of a path on the VM.<\/p>\n<p>So, we need to change the cache path to our Mage directory on the VM. This is how you\u2019d do it:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># change path of cache to mage main directory<\/span>\nmy_token$cache_path = <span style=\"color:#a50\">\"\/home\/src\"<\/span>\n\n<span style=\"color:#aaa;font-style:italic\"># save token again with changed directory<\/span>\n<span style=\"color:#0a0\">saveRDS<\/span>(my_token, file = <span style=\"color:#a50\">\"google_ads_token_mage_demo.RDS\"<\/span>)<\/code><\/pre>\n<\/div>\n<p>Here is the full code to generate, test, and save the token:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Packages<\/span>\n<span style=\"color:#0a0\">library<\/span>(gargle)\n<span style=\"color:#0a0\">library<\/span>(rgoogleads)\n\n<span style=\"color:#aaa;font-style:italic\"># Create gargle client<\/span>\nmy_client = <span style=\"color:#0a0\">gargle_oauth_client_from_json<\/span>(path = <span style=\"color:#a50\">\"\/Users\/arbenkqiku\/Desktop\/mage-ai\/mage-demo-client-id.json\"<\/span>,\n                                          name = <span style=\"color:#a50\">\"Google Ads App\"<\/span>)\n\n<span style=\"color:#aaa;font-style:italic\"># Define scope and email<\/span>\nscopes = <span style=\"color:#a50\">\"https:\/\/www.googleapis.com\/auth\/adwords\"<\/span>\nemail = <span style=\"color:#a50\">\"<a href=\"https:\/\/www.simoahava.com\/cdn-cgi\/l\/email-protection\" class=\"__cf_email__\" data-cfemail=\"bfdecddddad191d4ced6d4caffd8d2ded6d391dcd0d2\">[email\u00a0protected]<\/a>\"<\/span>\n\n<span style=\"color:#aaa;font-style:italic\"># Create a token by using Gargle<\/span>\nmy_token = <span style=\"color:#0a0\">gargle2.0_token<\/span>(email = email,  \n                           package = <span style=\"color:#a50\">\"rgoogleads\"<\/span>,  \n                           scope = scopes,  \n                           client = my_client)\n\n<span style=\"color:#aaa;font-style:italic\"># Authenticate by using the previously created token<\/span>\n<span style=\"color:#0a0\">gads_auth<\/span>(token = my_token)\n\n<span style=\"color:#aaa;font-style:italic\"># Test token by getting the list of accessible accounts<\/span>\n<span style=\"color:#0a0\">gads_get_accessible_customers<\/span>()\n\n<span style=\"color:#aaa;font-style:italic\"># Change path of cache to mage main directory, so you can use the token in production<\/span>\nmy_token$cache_path = <span style=\"color:#a50\">\"\/home\/src\"<\/span>\n\n<span style=\"color:#aaa;font-style:italic\"># Save token with changed directory<\/span>\n<span style=\"color:#0a0\">saveRDS<\/span>(my_token, file = <span style=\"color:#a50\">\"google_ads_token_mage_demo.RDS\"<\/span>)<\/code><\/pre>\n<\/div>\n<p>Now that we have generated the access token, you can copy-paste the JSON file from your local machine to the VM directory by using Visual Studio Code. Follow the exact steps you took to copy-paste the service account JSON file before.<\/p>\n<p>Next, we can go back to Mage, add a Data loader block, and select R as the programming language.<\/p>\n<div style=\"aspect-ratio: 967 \/ 227;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-data-loader-with-r.jpg\" title=\"New data loader with R\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"227\" width=\"967\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/new-data-loader-with-r.jpg#ZgotmplZ\" alt=\"New data loader with R\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Name the block <code>google_ads<\/code> and click on \u201cSave and add block\u201d.<\/p>\n<p>In the block code, we need to first load the necessary packages.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\n<span style=\"color:#0a0\">p_load<\/span>(rgoogleads)\n<span style=\"color:#0a0\">p_load<\/span>(dplyr)\n<span style=\"color:#0a0\">p_load<\/span>(purrr)\n\nload_data function() {\n\n}<\/code><\/pre>\n<\/div>\n<p>Then, we need to load our access token, authenticate with it, and set the Google Ads account ID we want to get the data from.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># load Google Ads access token<\/span>\nmy_token = <span style=\"color:#0a0\">readRDS<\/span>(file = <span style=\"color:#a50\">\"google_ads_token_mage_demo.RDS\"<\/span>)\n    \n<span style=\"color:#aaa;font-style:italic\"># Authenticate with the token<\/span>\n<span style=\"color:#0a0\">gads_auth<\/span>(token = my_token)\n\n<span style=\"color:#aaa;font-style:italic\"># Set the Google Ads account id you want to get data from<\/span>\n<span style=\"color:#0a0\">gads_set_customer_id<\/span>(<span style=\"color:#a50\">'123-123-1234'<\/span>)<\/code><\/pre>\n<\/div>\n<p>Here is the query that we\u2019re using to retrieve our data. We\u2019ll retrieve impressions, clicks, and cost segmented by date, from \u201c2023-10-19\u201d until \u201c2023-11-01\u201d.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># run query<\/span>\ngoogle_ads_account_data = <span style=\"color:#0a0\">gads_get_report<\/span>(\n  resource    = <span style=\"color:#a50\">\"customer\"<\/span>,\n  fields      = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"segments.date\"<\/span>,\n                  <span style=\"color:#a50\">\"metrics.impressions\"<\/span>,\n                  <span style=\"color:#a50\">\"metrics.clicks\"<\/span>,\n                  <span style=\"color:#a50\">\"metrics.cost_micros\"<\/span>),\n  date_from   = <span style=\"color:#a50\">\"2023-10-19\"<\/span>,\n  date_to     = <span style=\"color:#a50\">\"2023-11-01\"<\/span>\n)<\/code><\/pre>\n<\/div>\n<p>The first argument you need to define is the <strong>resource<\/strong> you are getting the data from, in our case <code>customer<\/code>.<\/p>\n<p>You can find <a href=\"https:\/\/developers.google.com\/google-ads\/api\/fields\/v13\/overview#list-of-all-resources\">here<\/a> the list of all available resources.<\/p>\n<p>For example, if you would like to retrieve data at the ad group level, you should define the resource as <a href=\"https:\/\/developers.google.com\/google-ads\/api\/fields\/v13\/ad_group\"><code>ad_group<\/code><\/a>.<\/p>\n<p>To build our query, we can use the <a href=\"https:\/\/developers.google.com\/google-ads\/api\/fields\/v13\/customer_query_builder\">Google Ads query builder<\/a>, which can be used for any resource, in our case <code>customer<\/code>.<\/p>\n<div style=\"aspect-ratio: 890 \/ 390;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/build-customer-query.jpg\" title=\"Build customer query\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"390\" width=\"890\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/build-customer-query.jpg#ZgotmplZ\" alt=\"Build customer query\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Below you can select attributes, segments, or metrics:<\/p>\n<div style=\"aspect-ratio: 885 \/ 409;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/select-attributes-segments-metrics.jpg\" title=\"Select attributes, segments, metrics\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"409\" width=\"885\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/select-attributes-segments-metrics.jpg#ZgotmplZ\" alt=\"Select attributes, segments, metrics\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When you select fields, it will start populating the query in the user interface of the builder.<\/p>\n<p>This is very useful to know what the metrics and dimensions are called in the Google Ads API.<\/p>\n<p>Here is the final part of our Data loader block, which should always be a variable containing data, as we have to pass something to the next block.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># return data<\/span>\ngoogle_ads_account_data<\/code><\/pre>\n<\/div>\n<p>Here is the complete code block we\u2019re working with:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\n<span style=\"color:#0a0\">p_load<\/span>(rgoogleads)\n<span style=\"color:#0a0\">p_load<\/span>(dplyr)\n<span style=\"color:#0a0\">p_load<\/span>(purrr)\n\nload_data function() {\n    <span style=\"color:#aaa;font-style:italic\"># Specify your data loading logic here<\/span>\n    <span style=\"color:#aaa;font-style:italic\"># Return value: loaded dataframe<\/span>\n    \n    <span style=\"color:#aaa;font-style:italic\"># load Google Ads access token<\/span>\n    my_token = <span style=\"color:#0a0\">readRDS<\/span>(file = <span style=\"color:#a50\">\"google_ads_token_mage_demo.RDS\"<\/span>)\n    \n    <span style=\"color:#aaa;font-style:italic\"># Authenticate with the token<\/span>\n    <span style=\"color:#0a0\">gads_auth<\/span>(token = my_token)\n    \n    <span style=\"color:#aaa;font-style:italic\"># Set the Google Ads account id you want to get data from<\/span>\n    <span style=\"color:#0a0\">gads_set_customer_id<\/span>(<span style=\"color:#a50\">'123-123-1234'<\/span>)\n\n    <span style=\"color:#aaa;font-style:italic\"># run query<\/span>\n    google_ads_account_data = <span style=\"color:#0a0\">gads_get_report<\/span>(\n      resource    = <span style=\"color:#a50\">\"customer\"<\/span>,\n      fields      = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"segments.date\"<\/span>,\n                      <span style=\"color:#a50\">\"metrics.impressions\"<\/span>,\n                      <span style=\"color:#a50\">\"metrics.clicks\"<\/span>,\n                      <span style=\"color:#a50\">\"metrics.cost_micros\"<\/span>),\n      date_from   = <span style=\"color:#a50\">\"2023-10-19\"<\/span>,\n      date_to     = <span style=\"color:#a50\">\"2023-11-01\"<\/span>\n    )\n\n<span style=\"color:#aaa;font-style:italic\"># return data for next block<\/span>\ngoogle_ads_account_data\n}<\/code><\/pre>\n<\/div>\n<p>If you run this code, you should be able to see clicks, cost, and impressions segmented by date.<\/p>\n<div style=\"aspect-ratio: 1410 \/ 828;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ads-data-table.jpg\" title=\"Ads data in a table\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"828\" width=\"1410\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/ads-data-table.jpg#ZgotmplZ\" alt=\"Ads data in a table\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>We\u2019re now done with this Data Loader block. Next, let\u2019s move on to <strong>transformers<\/strong>.<\/p>\n<h4 id=\"join-ga4-and-google-ads-data\">Join GA4 and Google Ads data<\/h4>\n<p>In Mage, add a new <strong>Transformer<\/strong> block and select <strong>R<\/strong> as the programming language.<\/p>\n<p>Give the block a name like <code>join_ga4_google_ads<\/code> and click on \u201cSave and add block\u201d.<\/p>\n<p>In the Tree, we can now see that the Transformer block named <code>join_ga4_google_ads<\/code> only receives data from the Data Loader block <code>google_ads<\/code>. We need to also link the Data Loader <code>ga4<\/code> with the Transformer.<\/p>\n<p>To do this, you simply need to drag and drop the arrow from the <code>ga4<\/code> block to the <code>join_ga4_google_ads<\/code> Transformer.<\/p>\n<div style=\"aspect-ratio: 1148 \/ 782;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/join-ga4-transformer.jpg\" title=\"Join ga4 with transformer\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"782\" width=\"1148\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/join-ga4-transformer.jpg#ZgotmplZ\" alt=\"Join ga4 with transformer\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The first thing that we\u2019ll do in the Transformer block is to add the final variables from the previous Data loader blocks to the <code>transform()<\/code> function.<\/p>\n<div style=\"aspect-ratio: 1460 \/ 774;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/transform-data-loader-functions.jpg\" title=\"Transform data loader functions\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"774\" width=\"1460\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/transform-data-loader-functions.jpg#ZgotmplZ\" alt=\"Transform data loader functions\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Next, we can add the following packages on top of the <code>transform()<\/code> function:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\n<span style=\"color:#0a0\">p_load<\/span>(tibble, dplyr, purrr, stringr, lubridate)<\/code><\/pre>\n<\/div>\n<p>The first piece of code that we\u2019re adding is this:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Build a row with the exact time<\/span>\ncheck_time = <span style=\"color:#0a0\">tibble<\/span>(Date = <span style=\"color:#0a0\">Sys.time<\/span>(),\n                    Impressions = <span style=\"color:#099\">0<\/span>,\n                    Sessions = <span style=\"color:#099\">0<\/span>,\n                    Clicks = <span style=\"color:#099\">0<\/span>,\n                    Cost = <span style=\"color:#099\">0<\/span>,\n                    Goals = <span style=\"color:#099\">0<\/span>)<\/code><\/pre>\n<\/div>\n<p>I am creating this tibble called <code>check_time<\/code> only so that later in BigQuery we can verify whether our schedule from Mage is working correctly.<\/p>\n<p>Then, we can finally join the Google Ads data with the GA4 data, and also return the <code>merged_data<\/code> variable for the next block:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Merge Google Ads with GA4 Data<\/span>\nmerged_data = google_ads_account_data %&gt;%\n  \n  <span style=\"color:#0a0\">left_join<\/span>(sessions_goals_ga4, by = <span style=\"color:#0a0\">c<\/span>(<span style=\"color:#a50\">\"date\"<\/span> = <span style=\"color:#a50\">\"date\"<\/span>)) %&gt;%\n  \n  <span style=\"color:#aaa;font-style:italic\"># reorder and capitalise columns<\/span>\n  <span style=\"color:#0a0\">select<\/span>(date, impressions, sessions, clicks, cost, goals) %&gt;% \n  <span style=\"color:#0a0\">set_names<\/span>(<span style=\"color:#0a0\">names<\/span>(.) %&gt;% <span style=\"color:#0a0\">str_to_title<\/span>()) %&gt;%\n  \n  <span style=\"color:#aaa;font-style:italic\"># add check_time variable to verify schedule<\/span>\n  <span style=\"color:#0a0\">mutate<\/span>(Date = Date %&gt;% <span style=\"color:#0a0\">as_datetime<\/span>()) %&gt;%\n  <span style=\"color:#0a0\">bind_rows<\/span>(check_time) %&gt;%\n\n  <span style=\"color:#aaa;font-style:italic\"># replace NAs with 0<\/span>\n  <span style=\"color:#0a0\">replace<\/span>(<span style=\"color:#0a0\">is.na<\/span>(.), <span style=\"color:#099\">0<\/span>) %&gt;%\n  \n  <span style=\"color:#0a0\">arrange<\/span>(<span style=\"color:#0a0\">desc<\/span>(Date))\n\n<span style=\"color:#aaa;font-style:italic\"># Return merged_data variable for next block<\/span>\nmerged_data<\/code><\/pre>\n<\/div>\n<p>If everything worked properly, you should get something similar to this:<\/p>\n<div style=\"aspect-ratio: 987 \/ 398;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/joined-data-after-transformation.jpg\" title=\"Joined data after transformation\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"398\" width=\"987\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/joined-data-after-transformation.jpg#ZgotmplZ\" alt=\"Joined data after transformation\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>I am aware that we\u2019re joining Google Ads data with GA4 data from all sources, and we should actually only join GA4 data coming from Google Ads. However, the goal of this guide is simply to show how to perform data engineering tasks with digital data.<\/p>\n<h4 id=\"export-data-to-bigquery\">Export data to BigQuery<\/h4>\n<p>Now that we joined data successfully from Google Ads and GA4, we\u2019re ready to export the data to BigQuery.<\/p>\n<p>Browse to the <a href=\"https:\/\/console.cloud.google.com\/bigquery\/\">BigQuery console<\/a> in your Google Cloud Platform project.<\/p>\n<p>BigQuery has the following data hierarchy: <strong>project<\/strong> -&gt; <strong>dataset<\/strong> -&gt; <strong>table<\/strong>.<\/p>\n<p>We already have a project, so now we need to create a dataset where our tables will reside. Click on the three dots on the right of your project, and then on \u201cCreate data set\u201d:<\/p>\n<div style=\"aspect-ratio: 1024 \/ 450;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-bq-dataset.jpg\" title=\"Create BQ dataset\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"450\" width=\"1024\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/create-bq-dataset.jpg#ZgotmplZ\" alt=\"Create BQ dataset\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Give a name to your data set, select a region, and click on \u201cCREATE DATA SET\u201d:<\/p>\n<div style=\"aspect-ratio: 1120 \/ 1294;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/configure-bq-dataset.jpg\" title=\"Configure BQ dataset\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1294\" width=\"1120\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/configure-bq-dataset.jpg#ZgotmplZ\" alt=\"Configure BQ dataset\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Back in Mage, add a <strong>Data Exporter<\/strong> block and choose <strong>R<\/strong> as the programming language again.<\/p>\n<p>Name the block <code>biq_query_export<\/code> and click on \u201cSave and add block\u201d.<\/p>\n<p>This is what your data tree should look like.<\/p>\n<div style=\"aspect-ratio: 1082 \/ 1000;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-data-tree.jpg\" title=\"Mage data tree\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1000\" width=\"1082\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-data-tree.jpg#ZgotmplZ\" alt=\"Mage data tree\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Go the the <code>big_query_export<\/code> block, and add <code>merged_data<\/code> as the argument of the function <code>export_data()<\/code>. Also, let\u2019s load the <code>bigrquery<\/code> package.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\n<span style=\"color:#0a0\">p_load<\/span>(bigrquery)\n\nexport_data function(merged_data) {\n    <span style=\"color:#aaa;font-style:italic\"># Specify your data exporting logic here<\/span>\n    <span style=\"color:#aaa;font-style:italic\"># Return value: exported dataframe<\/span>\n    \n}<\/code><\/pre>\n<\/div>\n<p>To authenticate with BigQuery, we can actually use the service account key that we previously created for GA4.<\/p>\n<p>The only thing that changes is the function <code>bq_auth()<\/code> instead of <code>ga_auth()<\/code>.<\/p>\n<p>This is great news as it means we don\u2019t have to go through yet another cumbersome authentication process:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Authenticate<\/span>\n<span style=\"color:#0a0\">bq_auth<\/span>(path = <span style=\"color:#a50\">\"mage-ai-test-405614-2e1e1c865c18.json\"<\/span>)<\/code><\/pre>\n<\/div>\n<p>In fact, you can use the same service account key to authenticate with multiple Google services such as Google Drive or Google Sheets.<\/p>\n<p>There are different R packages for these services, such as <a href=\"https:\/\/googledrive.tidyverse.org\/reference\/drive_auth.html\"><code>googledrive<\/code><\/a> and <a href=\"https:\/\/www.rdocumentation.org\/packages\/googlesheets4\/versions\/0.1.0\/topics\/sheets_auth\"><code>googlesheets4<\/code><\/a>.<\/p>\n<p>Granted, you need to authorize the respective APIs in the Google Cloud Platform as shown previously, but this is a great time saver!<\/p>\n<p>The next thing to do is to create a table reference for BigQuery.<\/p>\n<p>As you may remember, we previously created only a data set, so we now need to create a placeholder for our table.<\/p>\n<p>To do so, we need to define our project name, data set, and table. The project name and data set are already defined and we can retrieve these from BigQuery. The table name is up to you.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#aaa;font-style:italic\"># Define Big Query Project data<\/span>\nproject = <span style=\"color:#a50\">\"mage-ai-test-405614\"<\/span>\ndata_set = <span style=\"color:#a50\">\"mage_demo\"<\/span>\ntable = <span style=\"color:#a50\">\"merged_data\"<\/span>\n\n<span style=\"color:#aaa;font-style:italic\"># Define table<\/span>\ntable = <span style=\"color:#0a0\">bq_table<\/span>(project = project, dataset = data_set, table = table)<\/code><\/pre>\n<\/div>\n<p>To find the right project and data set name, go to BigQuery and click on the data set you created.<\/p>\n<p>To the right, you should see the <strong>Data set ID<\/strong>, which comprises <code>project_name.data_set_name<\/code>. You can separate and copy those values to insert them into the code above.<\/p>\n<div style=\"aspect-ratio: 1390 \/ 536;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/dataset-info.jpg\" title=\"data set info\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"536\" width=\"1390\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/dataset-info.jpg#ZgotmplZ\" alt=\"data set info\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>In the following code, if the table exists, we delete and recreate it before uploading data.<\/p>\n<p>I\u2019m doing this every 5 minutes for demonstration, but in real production, I\u2019d likely run it less frequently, adding only the new data instead of recreating the whole table.<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">if<\/span>(<span style=\"color:#0a0\">bq_table_exists<\/span>(table)){\n  <span style=\"color:#aaa;font-style:italic\"># if table already exists, delete it<\/span>\n  <span style=\"color:#0a0\">bq_table_delete<\/span>(table)\n\t\n\t<span style=\"color:#aaa;font-style:italic\"># recreate table so that we can fill it out<\/span>\n  <span style=\"color:#0a0\">bq_table_create<\/span>(table, merged_data)\n\t\n\t<span style=\"color:#aaa;font-style:italic\"># fill out table<\/span>\n  <span style=\"color:#0a0\">bq_table_upload<\/span>(table, merged_data)\n}else{\n  <span style=\"color:#0a0\">bq_table_create<\/span>(table, merged_data)\n  <span style=\"color:#0a0\">bq_table_upload<\/span>(table, merged_data)\n}<\/code><\/pre>\n<\/div>\n<p>Here is the final code:<\/p>\n<div class=\"highlight\">\n<pre style=\"background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-r\" data-lang=\"r\"><span style=\"color:#0a0\">library<\/span>(<span style=\"color:#a50\">\"pacman\"<\/span>)\n<span style=\"color:#0a0\">p_load<\/span>(bigrquery)\n\nexport_data function(merged_data) {\n    <span style=\"color:#aaa;font-style:italic\"># Specify your data exporting logic here<\/span>\n    <span style=\"color:#aaa;font-style:italic\"># Return value: exported dataframe<\/span>\n\n    <span style=\"color:#aaa;font-style:italic\"># Authenticate<\/span>\n    <span style=\"color:#0a0\">bq_auth<\/span>(path = <span style=\"color:#a50\">\"mage-ai-test-405614-2e1e1c865c18.json\"<\/span>)\n\n    <span style=\"color:#aaa;font-style:italic\"># Define Big Query Project data<\/span>\n    project = <span style=\"color:#a50\">\"mage-ai-test-405614\"<\/span>\n    data_set = <span style=\"color:#a50\">\"mage_demo\"<\/span>\n    table = <span style=\"color:#a50\">\"merged_data\"<\/span>\n\n    <span style=\"color:#aaa;font-style:italic\"># Create table reference<\/span>\n    table = <span style=\"color:#0a0\">bq_table<\/span>(project = project, dataset = data_set, table = table)\n\n    <span style=\"color:#0a0\">if<\/span>(<span style=\"color:#0a0\">bq_table_exists<\/span>(table)){\n      <span style=\"color:#aaa;font-style:italic\"># if table already exists, delete it<\/span>\n      <span style=\"color:#0a0\">bq_table_delete<\/span>(table)\n\t\n\t    <span style=\"color:#aaa;font-style:italic\"># recreate table so that we can fill it out<\/span>\n      <span style=\"color:#0a0\">bq_table_create<\/span>(table, merged_data)\n\t\n\t    <span style=\"color:#aaa;font-style:italic\"># fill out table<\/span>\n      <span style=\"color:#0a0\">bq_table_upload<\/span>(table, merged_data)\n    }else{\n      <span style=\"color:#0a0\">bq_table_create<\/span>(table, merged_data)\n      <span style=\"color:#0a0\">bq_table_upload<\/span>(table, merged_data)\n    }\n}<\/code><\/pre>\n<\/div>\n<p>If you run the code, you should have a new table called <code>merged_data<\/code> in BigQuery. If you click PREVIEW, you should be able to see data within.<\/p>\n<div style=\"aspect-ratio: 1113 \/ 231;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/bigquery-preview.jpg\" title=\"BQ preview\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"231\" width=\"1113\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/bigquery-preview.jpg#ZgotmplZ\" alt=\"BQ preview\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Our pipeline is complete, as you can see all the blocks have a green tick:<\/p>\n<div style=\"aspect-ratio: 1134 \/ 1036;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-pipeline-complete.jpg\" title=\"Mage pipeline complete\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"1036\" width=\"1134\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-pipeline-complete.jpg#ZgotmplZ\" alt=\"Mage pipeline complete\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"test-the-entire-pipeline\">Test the entire pipeline<\/h3>\n<p>Only because each block ran successfully, there is no guarantee that the entire pipeline will run smoothly. So, we have to run the entire pipeline before creating a schedule.<\/p>\n<p>In Mage, click on \u201cTriggers\u201d:<\/p>\n<div style=\"aspect-ratio: 1208 \/ 280;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-triggers.jpg\" title=\"Mage triggers\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"280\" width=\"1208\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-triggers.jpg#ZgotmplZ\" alt=\"Mage triggers\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>At the top, click on <strong>Run @once<\/strong>.<\/p>\n<p>This will produce a trigger, and you\u2019ll see that its status will change to <code>running<\/code>:<\/p>\n<div style=\"aspect-ratio: 908 \/ 94;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/trigger-running.jpg\" title=\"Trigger running\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"94\" width=\"908\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/trigger-running.jpg#ZgotmplZ\" alt=\"Trigger running\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>When done, it should say <code>completed<\/code> and switch to inactive state.<\/p>\n<p>If we now refresh the BigQuery table, we can see that it has an updated date\/time for the rows. This means that our pipeline ran successfully!<\/p>\n<div style=\"aspect-ratio: 1090 \/ 237;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/date-time-bigquery-updated.jpg\" title=\"Date time updated in BQ\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"237\" width=\"1090\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/date-time-bigquery-updated.jpg#ZgotmplZ\" alt=\"Date time updated in BQ\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<h3 id=\"create-a-schedule\">Create a schedule<\/h3>\n<p>Now that we know that our pipeline works properly, let\u2019s create a trigger that runs every 5 minutes.<\/p>\n<p>In Mage\u2019s Triggers view, click on <strong>New trigger<\/strong>.<\/p>\n<p>Select <strong>Schedule<\/strong> as the trigger type.<\/p>\n<div style=\"aspect-ratio: 978 \/ 218;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-new-schedule-trigger.jpg\" title=\"New schedule trigger\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"218\" width=\"978\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/mage-new-schedule-trigger.jpg#ZgotmplZ\" alt=\"New schedule trigger\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Given that the trigger will run every 5 minutes, let\u2019s name it <code>every_5_minutes<\/code>.<\/p>\n<p>Select <strong>custom<\/strong> as frequency and give the following <em>cron<\/em> expression: <code>*\/5 * * * *<\/code><\/p>\n<div style=\"aspect-ratio: 950 \/ 581;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/every-five-minutes.jpg\" title=\"Every five minutes\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"581\" width=\"950\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/every-five-minutes.jpg#ZgotmplZ\" alt=\"Every five minutes\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>A <em>cron<\/em> expression is like a schedule for your computer tasks.<\/p>\n<p>It\u2019s a simple set of instructions that tells your system when to run a specific job.<\/p>\n<p>The expression consists of five parts, representing minutes, hours, days, months, and days of the week. For example, <code>*\/15 * * * *<\/code> means \u201cevery 15 minutes, every hour, every day, every month, every day of the week\u201d.<\/p>\n<p>When ready with the trigger, click on <strong>Save changes<\/strong>.<\/p>\n<div style=\"aspect-ratio: 965 \/ 556;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/save-schedule-trigger.jpg\" title=\"Save schedule trigger\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"556\" width=\"965\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/save-schedule-trigger.jpg#ZgotmplZ\" alt=\"Save schedule trigger\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Now you have created your trigger, but as you can see its status is <code>inactive<\/code>. To start it, click on <strong>Start trigger<\/strong>.<\/p>\n<div style=\"aspect-ratio: 1398 \/ 826;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/start-schedule-trigger.jpg\" title=\"Start schedule trigger\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"826\" width=\"1398\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/start-schedule-trigger.jpg#ZgotmplZ\" alt=\"Start schedule trigger\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>The status switches to <code>active<\/code>. If you browse back to the Triggers view, it will show you when it\u2019s set to trigger next.<\/p>\n<div style=\"aspect-ratio: 934 \/ 211;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/trigger-next-run-date.jpg\" title=\"Next run date\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"211\" width=\"934\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/trigger-next-run-date.jpg#ZgotmplZ\" alt=\"Next run date\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Be mindful of the fact that the time zone in Mage is in UTC.<\/p>\n<p>Once the timer is set to go off, its status should change to <code>running<\/code>.<\/p>\n<p>After it\u2019s run, you can now refresh the BigQuery table and see that the data has now been updated again.<\/p>\n<div style=\"aspect-ratio: 1102 \/ 281;\" class=\"figure nocaption\">\n<p>    <a href=\"https:\/\/www.simoahava.com\/images\/2023\/11\/bigquery-schedule-updated.jpg\" title=\"BigQuery schedule updated\"><\/p>\n<p>    <img decoding=\"async\" class=\"fig-img\" height=\"281\" width=\"1102\" loading=\"lazy\" src=\"https:\/\/www.simoahava.com\/images\/2023\/11\/bigquery-schedule-updated.jpg#ZgotmplZ\" alt=\"BigQuery schedule updated\"\/><\/p>\n<p>    <\/a><\/p>\n<\/div>\n<p>Congratulations! Our journey is complete. I hope you had fun and learned something useful.<\/p>\n<p>If you have any comments, please post them below. If you want to connect with me, Arben, here is my <a href=\"https:\/\/www.linkedin.com\/in\/arben-kqiku-301457117\/\">LinkedIn<\/a>!<\/p>\n<h2 id=\"summary-by-simo\">Summary (by Simo)<\/h2>\n<p>Thank you so much, Arben!<\/p>\n<p>There\u2019s something just <em>magical<\/em> about automation platforms like Mage.ai.<\/p>\n<p>Being able to plug different components and services into a directed graph is such a powerful asset in today\u2019s data engineering world.<\/p>\n<p>Even though this guide was long and comprehensive, hopefully you got the idea that data engineering on this level is not beyond the reach of anyone.<\/p>\n<p>Arben was using some very basic building blocks: API connectors, service accounts, and queries, all wrapped up with the R programming language.<\/p>\n<p>Even if you didn\u2019t understand all the individual parts of this machine, hopefully you can appreciate how they all work together to generate the joined data export in your data warehouse (BigQuery in this case) as a result.<\/p>\n<p>As always, please share your thoughts in the comments below. Don\u2019t hesitate to contact <a href=\"https:\/\/www.linkedin.com\/in\/arben-kqiku-301457117\/\">Arben<\/a> if you want to shower him with praise for this excellent guide!<\/p>\n<\/p><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>I am fortunate to share another guest post by Arben Kqiku, Digital Analyst at Assura. Last time, Arben graced this blog with a comprehensive love letter to R as an example of the power of this programming language. This time, he\u2019ll add even more fancy tools to the toolkit to help you build a data [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":47562,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12033],"tags":[14239,28608,11603,18473,1531,7034],"dealstore":[],"offerexpiration":[],"class_list":["post-47561","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics","tag-ads","tag-bigquery","tag-data","tag-ga4","tag-google","tag-join"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Join Google Ads And GA4 Data In Google BigQuery - Som2ny Network<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/fivemor.com\/?p=47561\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Join Google Ads And GA4 Data In Google BigQuery - Som2ny Network\" \/>\n<meta property=\"og:description\" content=\"I am fortunate to share another guest post by Arben Kqiku, Digital Analyst at Assura. Last time, Arben graced this blog with a comprehensive love letter to R as an example of the power of this programming language. This time, he\u2019ll add even more fancy tools to the toolkit to help you build a data [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/fivemor.com\/?p=47561\" \/>\n<meta property=\"og:site_name\" content=\"Som2ny Network\" \/>\n<meta property=\"article:published_time\" content=\"2025-01-26T02:00:17+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"960\" \/>\n\t<meta property=\"og:image:height\" content=\"540\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"38 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/fivemor.com\/?p=47561#article\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/?p=47561\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\"},\"headline\":\"Join Google Ads And GA4 Data In Google BigQuery\",\"datePublished\":\"2025-01-26T02:00:17+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=47561\"},\"wordCount\":6161,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=47561#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg\",\"keywords\":[\"ads\",\"BigQuery\",\"Data\",\"GA4\",\"Google\",\"Join\"],\"articleSection\":[\"Analytics\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/fivemor.com\/?p=47561#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/fivemor.com\/?p=47561\",\"url\":\"https:\/\/fivemor.com\/?p=47561\",\"name\":\"Join Google Ads And GA4 Data In Google BigQuery - Som2ny Network\",\"isPartOf\":{\"@id\":\"https:\/\/fivemor.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/fivemor.com\/?p=47561#primaryimage\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/?p=47561#primaryimage\"},\"thumbnailUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg\",\"datePublished\":\"2025-01-26T02:00:17+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/fivemor.com\/?p=47561#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/fivemor.com\/?p=47561\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/?p=47561#primaryimage\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg\",\"width\":960,\"height\":540},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/fivemor.com\/?p=47561#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/fivemor.com\/?bp_activities=1\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Join Google Ads And GA4 Data In Google BigQuery\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/fivemor.com\/#website\",\"url\":\"https:\/\/fivemor.com\/\",\"name\":\"Som2ny Network\",\"description\":\"Daily Deals\",\"publisher\":{\"@id\":\"https:\/\/fivemor.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/fivemor.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/fivemor.com\/#organization\",\"name\":\"Som2ny Network\",\"url\":\"https:\/\/fivemor.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"contentUrl\":\"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png\",\"width\":300,\"height\":86,\"caption\":\"Som2ny Network\"},\"image\":{\"@id\":\"https:\/\/fivemor.com\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/fivemor.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png\",\"caption\":\"admin\"},\"sameAs\":[\"https:\/\/fivemor.com\"],\"url\":\"https:\/\/fivemor.com\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Join Google Ads And GA4 Data In Google BigQuery - Som2ny Network","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/fivemor.com\/?p=47561","og_locale":"en_US","og_type":"article","og_title":"Join Google Ads And GA4 Data In Google BigQuery - Som2ny Network","og_description":"I am fortunate to share another guest post by Arben Kqiku, Digital Analyst at Assura. Last time, Arben graced this blog with a comprehensive love letter to R as an example of the power of this programming language. This time, he\u2019ll add even more fancy tools to the toolkit to help you build a data [&hellip;]","og_url":"https:\/\/fivemor.com\/?p=47561","og_site_name":"Som2ny Network","article_published_time":"2025-01-26T02:00:17+00:00","og_image":[{"width":960,"height":540,"url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg","type":"image\/jpeg"}],"author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"38 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/fivemor.com\/?p=47561#article","isPartOf":{"@id":"https:\/\/fivemor.com\/?p=47561"},"author":{"name":"admin","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371"},"headline":"Join Google Ads And GA4 Data In Google BigQuery","datePublished":"2025-01-26T02:00:17+00:00","mainEntityOfPage":{"@id":"https:\/\/fivemor.com\/?p=47561"},"wordCount":6161,"commentCount":0,"publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"image":{"@id":"https:\/\/fivemor.com\/?p=47561#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg","keywords":["ads","BigQuery","Data","GA4","Google","Join"],"articleSection":["Analytics"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/fivemor.com\/?p=47561#respond"]}]},{"@type":"WebPage","@id":"https:\/\/fivemor.com\/?p=47561","url":"https:\/\/fivemor.com\/?p=47561","name":"Join Google Ads And GA4 Data In Google BigQuery - Som2ny Network","isPartOf":{"@id":"https:\/\/fivemor.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/fivemor.com\/?p=47561#primaryimage"},"image":{"@id":"https:\/\/fivemor.com\/?p=47561#primaryimage"},"thumbnailUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg","datePublished":"2025-01-26T02:00:17+00:00","breadcrumb":{"@id":"https:\/\/fivemor.com\/?p=47561#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/fivemor.com\/?p=47561"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/?p=47561#primaryimage","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2025\/01\/join-ga4-google-ads-data-in-google-bigquery.jpg","width":960,"height":540},{"@type":"BreadcrumbList","@id":"https:\/\/fivemor.com\/?p=47561#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/fivemor.com\/?bp_activities=1"},{"@type":"ListItem","position":2,"name":"Join Google Ads And GA4 Data In Google BigQuery"}]},{"@type":"WebSite","@id":"https:\/\/fivemor.com\/#website","url":"https:\/\/fivemor.com\/","name":"Som2ny Network","description":"Daily Deals","publisher":{"@id":"https:\/\/fivemor.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/fivemor.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/fivemor.com\/#organization","name":"Som2ny Network","url":"https:\/\/fivemor.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/","url":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","contentUrl":"https:\/\/fivemor.com\/wp-content\/uploads\/2026\/07\/4a0953c4-logo-300x86-1.png","width":300,"height":86,"caption":"Som2ny Network"},"image":{"@id":"https:\/\/fivemor.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/fivemor.com\/#\/schema\/person\/b85e3c3dc0e1daea076524dc8810c371","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/fivemor.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/729ae85bf62b9917e93538db2f2688ca?s=96&r=g&default=https%3A%2F%2Ffivemor.com%2Fwp-content%2Fplugins%2Fbuddypress-first-letter-avatar%2Fimages%2Fdefault%2F96%2Flatin_a.png","caption":"admin"},"sameAs":["https:\/\/fivemor.com"],"url":"https:\/\/fivemor.com\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/47561","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=47561"}],"version-history":[{"count":0,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/posts\/47561\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=\/wp\/v2\/media\/47562"}],"wp:attachment":[{"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=47561"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=47561"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=47561"},{"taxonomy":"dealstore","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fdealstore&post=47561"},{"taxonomy":"offerexpiration","embeddable":true,"href":"https:\/\/fivemor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fofferexpiration&post=47561"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}