GA4で実現するクロスプラットフォームのデータ計測と分析

この記事は[MICIN Advent Calendar 2022](https://adventar.org/calendars/7833)の15日目の記事です。
前回は竹内さんの[「RxJSでFRP入門」](https://qiita.com/riku_takeuchi/items/35bf2f95542a3511a518)でした。

---

MICINのオンライン医療事業部でエンジニアをしている外山です。前職でインハウスマーケターとしてメディアサービスのSEO対策やアクセス解析を経験しており，MICINではアプリケーション開発だけでなく，デジタルマーケティングに関する技術的な施策や実装も担当しています。今回は，弊社のオンライン診療サービスcuronを対象に，Google Analytics 4（GA4）を用いて行なっているクロスプラットフォームのデータ計測と分析について紹介します。

# Google Analytics 4とは
Webサービスを提供するプラットフォームは年々増え続けています。Webアプリ（Chrome），スマホアプリ（iOS・Android），デスクトップアプリ（mac・Windows）など，様々なプラットフォーム上でのサービス提供がスタンダードな流れになりつつあります。

システム開発の世界では，異なるプラットフォーム上で動作するシステムを単一のプログラムで実行するクロスプラットフォーム開発の概念がいち早く登場しました。React NativeやFlutterはその代表的なフレームワークとして人気を博しています。一方でアクセス解析やデータ分析の世界では，プラットフォームを横断した分析の普及・活用は，まだまだこれからの段階です。WebアプリはGoogle Analytics，スマホアプリはFirebaseやMixPanelといった具合に，異なるツールで分析が行われていました。しかし，世の中的にはクロスプラットフォームでのサービス開発が広がり，プラットフォーム横断での統合的な分析機運が高まっていました。

そんな中，2019年8月15日にGoogleから発表[^1]されたのが，プロパティ タイプ「アプリ + ウェブ」というGoogle Analytics 4のβ版です。その1年後，2020年10月21日に正式版としてGoogle Analytics 4がリリース[^2]されました。また，これまでWebアプリ向けに提供されていたユニバーサルアナリティクス（旧Google Analytics）については2023年7月1日にサービスの終了が発表[^3]されています。

MICINでもオンライン診療サービスcuronは患者向けにスマホアプリ（iOS/Android）とWebアプリを提供しています。元々スマホアプリはFirebase，WebアプリはGoogle Analyticsで計測を実施していましたが，上記の流れを受けてGA4での計測に一本化を進めています。

# 計測イベントの定義
GA4のイベント計測は大きく4つのタイプに分類されます。

|タイプ  |概要  |イベント例  |
|---|---|---|
|[自動収集イベント](https://support.google.com/analytics/answer/9234069?hl=ja)  |GA4により自動で収集されるイベント<br>（※デフォルトで設定がON）  |first_open，first_visit，screen_view，scroll  |
|[拡張計測機能イベント](https://support.google.com/analytics/answer/9216061)  |GA4により自動で収集されるイベント<br>（※デフォルトで設定がOFF）  |page_view，file_download  |
|[推奨イベント](https://support.google.com/analytics/answer/9267735)  |GA4でイベント名が定義済みかつ，計測設定が必要なイベント  |login，refund，search，sign_up  |
|[カスタムイベント](https://support.google.com/analytics/answer/12229021)  |GA4でイベント名が未定義かつ，計測設定が必要なイベント  |examine_reserved，join_video_call_room  |


イベント名が同一のイベントについては，プラットフォームに関わらず同一のイベントとして扱われます。特定のプラットフォームやURLからのイベントだけを見たい場合は，ディメンションを追加したり，管理画面上部のフィルターでhostnameやstreamの条件を絞り込みます。

以下はloginのイベントに対して，Platformのディメンション追加を行った画面です。
![ga1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/5c44d019-971d-8f3a-3b90-65b4fc23359d.png)
上記を踏まえて，推奨イベントやカスタムイベントについて，イベント名と付帯するパラメータを以下のように定義していきます。任意のイベントについてWebアプリ，もしくはスマホアプリの片方のみでしか取得していないイベントを考慮して，以下のように表形式でイベント定義を整理します。

|イベント内容  |タイプ  |イベント名  |付帯パラメータ  |Web  |iOS/Android  |
|---|---|---|---|:-:|:-:|
|ログイン  |推奨イベント  |login  |  |○  |○  |
|診療予約完了  |カスタムイベント  |examine_reserved  |clinic_uid: string  |○  |○  |
|ビデオ通話開始  |カスタムイベント  |join_video_call_room  |clinic_uid: string  |○  |○  |
|子アカウント追加  |カスタムイベント  |add_family_account  |clinic_uid: string  |○  |×  |
|...  |...  |...  |...  |...  |...  |



理想的には上記の内容に加えて，イベントがいつどこで（URLパスやクリック等）計測されるのか，各サービスごとに一目で分かるように整理しておきたいところです。

# 計測イベントの実装

推奨イベントとカスタムイベントについては，計測のためにプロダクトコードの実装が必要になります。実装方法は複数手段が用意されていますが，最終的に取得できるイベントに違いはありません。

## Webでの実装
GA4では独自に定義したイベント計測方法が大きく2つに分かれます。
### [gtag.js API](https://developers.google.com/tag-platform/gtagjs/reference)
gtag()はjavascript関数として提供されているAPIです。
例えば診療の予約完了のイベントと，付帯パラメータとして予約した医療機関のIDを一緒に計測したい場合

- イベント名：examination_reserved
- 付帯パラメータ：{ clinic_id: '0001' }

のようにイベントを定義すると

```js
<script>
  gtag('event', 'examination_reserved', {clinic_id: '0001'});
</script>
```

という記述を該当の処理の中で実装します。

TypeScriptを用いる場合，型情報がないため以下のように型を定義してAPIを呼び出します。

```ts
# gtagの型定義
declare global {
  interface Window {
    gtag: any[]
  }
}

# イベントの定義（イベントにも型を設定することで，イベント名のtypoを避けられる）
type EventPayload =
  | {
      event: 'examination_reserved';
      params: {
        clinic_id: string
      };
    }
  | {
      event: 'login';
      params: {};
    }

# イベント計測関数の定義
export const trackEvent = (payload: EventPayload) => {
  const { event, params } = payload;
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', event, params)
  }
}

# 呼び出し先
trackEvent('examination_reserved', {clinic_id: '0001'})
```

### [Google Tag Manager](https://developers.google.com/tag-platform/tag-manager/web)

Google Tag Manager（GTM）は計測タグや広告タグなど，サービスのHTMLコードにハードコードすることなく，タグの追加・削除を制御できるツールです。アカウト作成後に発行されるJavaScriptコードを<head>と<body>内に記載しておくことで，管理画面上から自由にタグの配信設定を行えます。GTMを用いることで，エンジニアによる実装・デプロイが最小限に抑えられ，素早く施策の実行・改善が行えます。

GTMを用いてイベントを取得する場合，タグとトリガーの設定が必要です。タグではイベント名やパラメーターなどイベントの詳細設定を行います。トリガーは作成したタグを，どんなURLでどのクリック時などに実行するかを設定します。

以下のイベントをGTMで計測する場合，管理画面上でのタグ設定は以下のようになります。

- イベント名：examination_reserved
- 付帯パラメータ：{ clinic_id: '0001' }

![ga2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/c096f747-423a-5cb0-1609-bdbe5f59d92a.png)

上記の画面ではトリガーが設定されていないため，トリガーについても設定します。トリガーに設定できる条件についてはPreviewモード[^4]で確認できます。

![ga3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/ae26b7ff-cba2-14bb-f90c-fa9e5688a354.png)

画面左に記載されているのがGTMが検知しているtrigger typeです。例えば上記画像の「Click > Variable」を選択すると，このtrigger typeに紐づく変数が確認できます。

![ga4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/fc816090-d426-e644-e80d-ec6076443133.png)

上記画像ではクリックを実施したページURL，クリックしたHTML要素のテキスト情報等が確認できます。この情報を条件にトリガーを作成すると以下のようになります。

![ga5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/93078d0f-e595-ac68-98a9-9b5958ed7d10.png)


上記ではForm TextとPage Pathを条件にトリガーを作成しています。最後に作成したトリガーをタグに設定します。

![ga6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/04e41ed9-f41c-92d8-9adc-c65a8bc1f16a.png)


設定を保存後に再度プレビューモードを実行します。計測タグが正しく設定されていると，該当の処理を実行したタイミングでTags Firedに設定したタグが表示されます。

![ga7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/396fa8b0-f859-c52b-aa2b-a6b780dd072c.png)


### データレイヤーの活用

GTMにはデータレイヤー[^5]と呼ばれるJavaScriptオブジェクトを定義する機能が備わっています。データレイヤーをプロダクトコード内に実装することで，サーバーからのコールバックのタイミングでトリガーを設定したり，プロダクト独自のデータ（医療機関の名前，診療のID，etc）をGTM上で取得可能になります。

```ts
# gtagの型定義
declare global {
  interface Window {
    dataLayer: any[]
  }
}

# イベントの定義（イベントにも型を設定することで，イベント名のtypoを避けられる）
type EventPayload =
  | {
      event: 'select_clinic_menu'
      clinic_uid: string
    }
  | {
      event: 'examine_reserved'
      clinic_uid: string
    }

# イベント計測関数の定義
export const trackEvent = (payload: EventPayload) => {
  if (typeof window !== 'undefined' && window.dataLayer) {
    window.dataLayer.push(payload)
  }
}

# 呼び出し先
trackEvent({
  event: 'select_clinic_menu',
  clinic_uid: "{CLINIC_ID}",
})
```

弊社ではReactコード内で上記のようにデータレイヤを定義して，GTM上でイベント取得を行なっています。上記で定義したデータレイヤーをトリガーとして設定するには，トリガーの作成からCustom Eventを選択，データレイヤー内のeventの値をEvent Nameに定義します。

![ga8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/1dcf1135-e7b5-0913-cb26-ce4828a21399.png)


トリガーが作成されると，以下のようにプレビューモード内で識別可能になります。

![ga7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/a0814b20-8d8d-5abb-fb5c-e7fde179a388.png)

また，データレイヤー変数内で合わせて送信される変数（上記のclinic_uid等）はGTMの変数の作成から定義を行うことで，上記のように変数を識別できます。


## スマホアプリでの実装

弊社ではReact Nativeにより患者向けのスマホアプリを開発しており，Firebase SDKを用いてイベント計測を実装しています。

```ts
import Config from 'react-native-config';
import analytics from '@react-native-firebase/analytics';

# イベントの定義（イベントにも型を設定することで，イベント名のtypoを避けられる）
type EventPayload =
  | {
      event: 'answer_questionnaire';
      params: {
        clinic_uid: string;
      };
    }
  | {
      event: 'select_clinic_menu';
      params: {
        clinic_uid: string;
      };
    }

# イベント計測関数の定義
export const trackEvent = (payload: EventPayload) => {
  if (Config.ENV === 'DEV') {
    return;
  }

  const { event, params } = payload;
  analytics().logEvent(event, params);
};


# 呼び出し先
trackEvent({
  event: 'select_clinic_menu',
  params: {
    clinic_uid: clinic.uid,
  },
});
```

実装はgtag.js APIと非常に似ており，react-native-firebaseというnpmライブラリを活用し，GA4に直接イベントを送信する形で計測を行なっています。

## user_pseudo_idとuser_id[^6][^7]

GA4はユーザーの識別子としてuser_pseudo_idとuser_idが利用可能です。デフォルトではuser_pseudo_idのみが自動で計測されており，user_idについては自身のサービスで発行したIDをGA4に送信しなければなりません。クロスプラットフォーム分析を行いたい場合，プラットフォーム横断で識別子の共有が必要なため，user_idの設定が必須です。

## クロスドメイントラッキング

クロスドメイントラッキングは複数のドメイン間で，ユーザーのセッション情報を引き継ぐデータ計測手法です。複数ドメインから構成されるWebサービスやWebサイトを効率的に分析するために，旧GAにも備わっていた機能です。クロスプラットフォーム分析を行うにあたって，複数ドメインで構成されるWebサービスを横断的に計測・分析するために必要になるため，弊社事例を紹介します。

curonの患者向けサービスは以下のような構成となっています。

オンライン診療： [app.curon.co](https://app.curon.co)
オンライン服薬指導： [pha.curon.co](https://pha.curon.co/)
認証サービス：id.curon.co （※オンライン服薬指導のみ）
決済サービス： [pass.curon.co](http://pass.curon.co/)

例えばapp.curon.coからpha.curon.coに遷移が発生した場合，セッション情報をドメイン間で引き継ぎ，同一のユーザーの遷移として取り扱うことが可能です。GA4にはData Streamという概念でプラットフォームごとにデータがまとめられています。プロパティに対して複数のデータストリームを持つことも可能ですが，Googleはウェブ・iOS・Androidで1つずつのストリームを紐づけることを推奨[^8]しています。

![ga12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/c1849dbd-9d57-14b8-46fb-999c39c19a3a.png)

クロスドメイントラッキングをGA4で行う場合，上記のようにWebのストリームを1つ作成し，その中に複数のドメインからのデータを保存します。上記のストリームを選択後にタグ付けの詳細設定（Configure tag settings） > ドメインの設定（Configure your domains）を選択，対象となるすべてのドメインを登録します。

![ga13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/e7bbf33a-e209-e8f4-ed55-cf901a4449ca.png)

# 計測イベントの分析
## Google Analytics 4上での分析
取得したイベントはGA4の管理画面左のメニューで「エンゲージメント > イベント」から確認可能です。デフォルトでは対象のすべてのデータの集計結果が表示されます。GA4のレポート画面は旧GAに比べて最低限の機能しか備えていません。付帯パラメータの掛け合わせなど，複数のディメンションを掛け合わせた分析をしたい場合，メニュー「探索」から自身でのレポートの作成が必要です。

## BigQuery上での分析
GA4では計測したイベントをBigQueryに連携し，ログ単位での分析を行えます。

### BigQueryとの連携設定
BigQueryの連携設計を行うためにはGoogle API コンソールのプロジェクトが必要になります[^9]。プロジェクトが作成されたら，GA4のProperty設定 > BigQuery Linksから連携設定を行います。データロケーションについては「東京(asia-northeast1)」か「大阪(asia-northeast2)」が推奨です。非常にレアケースですが，BigQueryのデータセットはロケーションを跨いでクエリすることができないので，基本的には一つのリージョンに統一しましょう[^10]。BigQueryが有償プランとなっている場合，データのエクスポート頻度をリアルタイムに設定することも可能です。

### データセットの確認

連携設定が完了すると，BigQuery上に「analytics_xxxxxxxxx」というデータセットが作成されます。「xxxxxxxxx」にはProperty IDが該当します。データセットの内部は日単位でテーブルが作成されています。

`{project_name}.analytics_{property_id}.events_20xxxxxx`

データエクスポート頻度をリアルタイムにしている場合，リアルタイムで更新されるテーブルもデータセットは以下に作成されます。

`{project_name}.analytics_{property_id}.events_intraday_20xxxxxx`

![ga11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175014/1f5f63a2-c209-bedc-5d40-d95dbcbb3de7.png)

BigQueryにエクスポートされたデータ構造[^11]はかなり詳細に定義されており，GA4上で確認できるデータはもちろんのこと，ユーザーやデバイス単位での絞り込みもSQLで実行可能です。

### クエリ

BigQueryでデータ分析を行うにはSQLの記述が必要です。GA4のデータ構造は複雑な形を取っているため，欲しいデータを出力するためにはいくつかのポイントを押さえておきましょう。

#### 頻出のカラム

以下のカラムは弊社の分析で頻繁に利用しています。


|カラム名  |概要  |sample  |
|---|---|---|
|event_date  |イベントの実行日  |20221215  |
|event_name  |イベント名  |login  |
|device.web_info.hostname  |Webアプリのドメイン  |app.curon.co  |
|stream_id  |GA4で設定されたストリームのID  |2181197051  |
|platform  |集計元のプラットフォーム  |WEB /  IOS / ANDROID  |



#### _TABLE_SUFFIXによる期間絞り込み

GA4のデータセットは日付毎にテーブルが作成されます。

```sql
from {project_name}.analytics_{property_id}.events_202212*
```

上記のようにfrom句の最後に*を入れて，12月全てのテーブルに絞ることも可能ですが，特定の期間のログを参照したい場合_TABLE_SUFFIXの利用が推奨です。_TABLE_SUFFIXを利用する場合はwhere句の中で絞り込みが行えます。

```sql
WHERE _TABLE_SUFFIX BETWEEN '20201101' AND '20210131'
```


#### RECORD型データの抽出[^12]

ページのタイトルやURL，計測時に取得したパラメータはevent_paramsというカラムに全て保存されます。event_paramsはRECORD型というデータ形式となっており，その中に含まれるカラムを取得するにはサブクエリの実行が必要です。

```sql
select
    (select value.string_value from unnest(event_params) where key = 'page_title') as page_title,
    count(event_name) as pageviews
from
    `{project_name}.analytics_{property_id}.events_202212*`
where
    event_name = 'page_view' 　-- イベント名を指定 
group by 
    page_title
order by 
    pageviews desc
```

上記の2行目にある，select ~ as ~の1行が該当の処理となります。

# おわりに

今回はGoogle Analytics 4のイベント定義・計測・分析の流れを紹介しました。弊社ではサービス間の依存関係が強いこともあって，一連の情報をBigQuery上で集計し，さまざまな改善施策に活用しています。GA4は旧GAからも大きく仕様が変わり，まだまだとっつきにくさを感じてる方も多いと思いますが，ぜひこの記事を通してその概要だけでも掴んでいただけると幸いです。

#### ※データ取得の目的とプライバシー保護
Google Analytics 4で取得したデータはサービスの改善を目的に利用しております。GA4に送信しているデータには，メールアドレス等，個人を特定する情報は含まれていません。

---

MICINではメンバーを大募集しています。

「とりあえず話を聞いてみたい」でも大歓迎ですので，お気軽にご応募ください！

MICIN採用ページ：https://recruit.micin.jp/


[^1]:アプリとウェブサイトの測定を統合する Google アナリティクスの新機能
[^2]:新しい Google アナリティクスのご紹介
[^3]:Google アナリティクス 4 でこれからに備えましょう
[^4]:コンテナのプレビューとデバッグ
[^5]:データレイヤー
[^6]:[GA4] User-ID のベスト プラクティス
[^7]:[GA4] User-ID で複数のプラットフォームをまたいでアクティビティを測定する
[^8]:[GA4] Google アナリティクス アカウントの構成 > データストリーム
[^9]:[GA4] BigQuery Export のセットアップ
[^10]:データセットのロケーション
[^11]:[GA4] BigQuery Export スキーマ
[^12]:BigQuery 活用術: UNNEST 関数