GoogleAnalytics4のデータをBigQueryで分析してみる

# 準備編
## GoogleAnalytics4のデータをBigQueryに転送する
- GoogleAnalytics4の設定から可能です。

<img width="720" alt="スクリーンショット 2021-10-15 20.07.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311026/7476de0e-6df4-99d8-8bfc-ce062ee2908f.png">

- 設定すると、BigQueryに自動でデータセットとテーブルが生成されます。
    - 生成されるテーブルは、整数範囲パーティション分割テーブルで、yyyymmddの形式で日付ごとに分割されています。
    - 接続するBIツールによっては、日付の分だけテーブルが表示されてしまうため、別途時間単位の列パーティション分割テーブルに切り出した上で、そちらを参照する方法もあります。
    - 公式から[スキーマ](https://support.google.com/analytics/answer/3437719?hl=ja)も公開されています。

<img width="720" alt="スクリーンショット 2021-10-15 20.07.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311026/0d2d74c9-4325-ebfb-b8db-6db88dd7105d.png">




# データ編
## GoogleAnayltics4の`イベント`を理解する

- GoogleAnalytics4では、ユーザーがサイト上(アプリ上)で起こした行動を、`イベント`という単位で計測していきます。
    - セッションをスタートした(session_start)
    - ページを閲覧した(page_view)
    - クリックした(click)
        - ※外部ドメインへのリンクをクリックした時のみ計測される
    - スクロールした(scroll)
    - その他、自動的に収集されるイベントは[こちら](https://support.google.com/analytics/answer/9234069?hl=ja&ref_topic=9756175)をご参照ください。

- イベント1つ1つに対して、複数のパラメータが設定されています。
    - パラメータは、key-value形式で保存されています。
    - valueに関して、内部的には`INTEGER`、`FLOAT`、`STRING`の3種類のデータ型のカラムが用意されています。
    - SQLを書く際は、出力したいvalueのデータ型によって、参照するカラムを変える必要があるのでご注意ください。

## 実際のデータで理解する

- 下記は、私がQiitaアカウントに設定しているGoogleAnalytics4のデータをBigQueryに転送したものです。

<img width="720" alt="スクリーンショット 2021-10-15 20.07.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311026/14ad239b-be65-c108-7d64-b2e2f0c1ee03.png">

- 1行1行が`イベント`になります。
- ユーザーが起こした`イベント`の名称が`event_name`カラムに格納され、`イベント`1つ1つに対して、`event_params.key`及び`event_params.value`が複数格納されています。
- GoogleAnalytics及びGoogleAnalytics360では、1行の単位が`セッション`で、その中に複数の`ヒット`が格納されているため、データ構造自体も大きく違うことが見て取れるかと思います。


## 余談「なぜBigQueryに転送する必要があるのか？」

- ブラウザ上でGoogleAnalytics[^1]にアクセスすれば、蓄積されたデータをレポート形式で出力して確認することは可能です。
- しかし、扱える指標やディメンションに制限があり、場合によっては集計したい形式でレポートを作成することができないことがあります。
- 具体的な例を出すと、`イベントの値`という項目をディメンションに設定することができません。(2021年10月16日現在)
    - `イベントの値`とは、key-value形式のvalueのカラムを指します。
- これにより、`ページごとのユニークユーザー数を出力する`というレポートを作ることができないのです。
    - ユーザーが閲覧したページパスの情報は、keyが`page_location`の`STRING`型のvalueカラムに格納されています。
    - 選択できるディメンションの中に、このvalueカラム(正確には`イベントの値`という項目)が存在しません。
- なお、GoogleデータポータルからデータソースにGoogleAnalytics[^1]を選択した場合も同様に選択することができません。
- 元々BigQueryへの転送は、有償版のGoogleAnalytics360のみで開放されていた機能でした。
- 無償で利用できるGoogleAnalytics4でも転送ができるのは、あくまでデータを蓄積する箱としてプロパティを利用し、その先のデータ分析はBigQueryに転送した上で行うことを前提にしているのではないか？と予想しています。


# クエリ編
- ここから先では、いくつかクエリ例をご紹介します。
- なお、公式にもサンプルがいくつか公開されているので、[こちら](https://support.google.com/analytics/answer/9037342?hl=ja&ref_topic=9359001#zippy=%2C%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%86%85%E5%AE%B9)も必要に応じてご参照ください。

## ページごとのユニークユーザー数

```sql
SELECT 
  value.string_value
  , COUNT(DISTINCT user_pseudo_id) AS uu
FROM 
  `project_name.dataset_name.events_*`
  , UNNEST(event_params)
WHERE
  _TABLE_SUFFIX BETWEEN '20211009'
  AND '20211015'
  AND event_name = 'page_view'
  AND key = 'page_location'
GROUP BY  
  1
ORDER BY
  2 DESC
```

- key-value形式のパラメータはRECORD型のカラムのため、UNNEST関数を用いてフラット化することで、中身を参照することができるようになります。
- ユーザーがページを閲覧した`イベント`は、イベント名`page_view`で送信されます。
- ユニークユーザーID(≒ブラウザごとに設定された一意の値)は、`user_pseudo_id`カラムに格納されています。

## セッション開始日ごとのユニークユーザー数

```sql
SELECT 
  DATE(TIMESTAMP_MICROS(event_timestamp), 'Asia/Tokyo') AS session_begin_date
  , COUNT(DISTINCT user_pseudo_id) AS uu
FROM 
　　`project_name.dataset_name.events_*`
WHERE
  _TABLE_SUFFIX BETWEEN '20211009'
  AND '20211015'
  AND event_name = 'session_start'
GROUP BY  
  1
ORDER BY
  1
```

- ユーザーがセッションを開始した`イベント`は、イベント名`session_start`で送信されます。
- イベント発生時刻は、`event_timestamp`カラムにINT型で格納されています。
    - TIMESTAMP_MICROS関数及び、DATE関数を用いて変換しましょう。

## 参照元/メディアごとのユニークユーザー数

```sql
SELECT 
  (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'source') AS utm_source
  , (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'medium') AS utm_medium
  , COUNT(DISTINCT user_pseudo_id) AS uu
FROM 
  `project_name.dataset_name.events_*`
WHERE
  _TABLE_SUFFIX BETWEEN '20211009'
  AND '20211015'
  AND event_name = 'page_view'
GROUP BY  
  1,2
ORDER BY
  3 DESC
```

- 参照元(utm_source)、メディア(utm_medium)の情報は、イベント名`page_view`のパラメータにkey-value形式で格納されています。
- 1レコードに複数のパラメータのvalueを並べて出力したい場合は、SELECT句にサブクエリを書きましょう。

## 直近3ヶ月の記事ごとのLGTM率、ストック率

```sql
WITH 
  query_results AS (
    SELECT 
      (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_title') AS page_title
      , COUNT(DISTINCT CASE WHEN event_name = 'page_view' THEN user_pseudo_id END) AS uu
      , COUNT(DISTINCT CASE WHEN event_name = 'Like an Article' THEN user_pseudo_id END) AS like_an_article_uu
      , COUNT(DISTINCT CASE WHEN event_name = 'Stock an Article' THEN user_pseudo_id END) AS stock_an_article_uu
    FROM 
      `project_name.dataset_name.events_*`
    WHERE
      _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE('Asia/Tokyo'), INTERVAL 3 MONTH))
      AND event_name IN ('page_view', 'Like an Article', 'Stock an Article')
    GROUP BY  
      1
  )

SELECT 
  page_title
  , uu
  , like_an_article_uu
  , ROUND(SAFE_DIVIDE(like_an_article_uu, uu), 3) AS like_an_article_rate
  , stock_an_article_uu
  , ROUND(SAFE_DIVIDE(stock_an_article_uu, uu), 3) AS stock_an_article_rate
FROM 
  query_results 
ORDER BY
  2 DESC
```

- 現在の日付から3ヶ月前を、日付関数で計算して出力します。
    - event_yyyymmddテーブルには、当日のテーブルが含まれないため、厳密には`3ヶ月-1日`のテーブルが対象となります。
- Qiitaでは、LTGMされると`Like an Article`というイベント名で、ストックされると`Stock an Article`というイベント名でデータが送信されます。
    - (なお、私がこれまで書いた記事では、LTGM率は1%、ストックは0.5%程度でした)

## 初回閲覧記事と次に閲覧した記事の組み合わせごとのユニークユーザー数
```sql
WITH 
  query_results AS (
    SELECT 
      ROW_NUMBER() OVER(PARTITION BY user_pseudo_id, (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') ORDER BY event_timestamp) AS event_number
      , event_timestamp
      , user_pseudo_id AS unique_user_id
      , (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id
      , (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_title') AS page_title
    FROM 
      `project_name.dataset_name.events_*`
    WHERE
      _TABLE_SUFFIX >= FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE('Asia/Tokyo'), INTERVAL 3 MONTH))
      AND event_name IN ('page_view')
  )

SELECT 
  COUNT(DISTINCT step1.unique_user_id ) AS uu
  , step1.page_title AS step1_page_title
  , step2.page_title AS step2_page_title
FROM 
  query_results AS step1
INNER JOIN 
  query_results AS step2
ON 
  step1.unique_user_id = step2.unique_user_id 
  AND step1.session_id = step2.session_id
  AND step1.event_timestamp < step2.event_timestamp
  AND step2.event_number = 2
WHERE 
  step1.event_number = 1
  AND step1.page_title != step2.page_title
GROUP BY  
  2,3
ORDER BY   
  1 DESC
```

- `page_view`のイベントに絞った上で、ROW_NUMBER関数でイベント発生順に数字を付与していきます。
- WITH句で生成した結果を自己結合し、event_numberの値が1の場合`初回閲覧記事`、2の場合`次に閲覧した記事`と定義して絞り込んでいます。


# まとめ

- 今回は、GoogleAnayltics4のデータをBigQueryで集計するお話を書かせていただきました。
- 筆者もまだまだ手探りのため、間違いなどございましたらコメントにてご指摘いただけますと幸いです。

[^1]: WEBサービスとしての、GoogleAnalyticsのこと。GoogleAnalytics360やGoogleAnalytics4は、あくまでプロパティの名称でありサービス名ではない。