Google Analytics 4 Data API + Google Apps Scriptで平均エンゲージメント時間を取得

# 環境
Google Apps Script
Google Analytics 4 Data API: v1beta

:::note warn
最終更新が[2024-05-14](https://developers.google.com/analytics/devguides/reporting/data/v1/changelog#2024-05-14_added_comparisons_feature)のData API v1betaを利用しています。
:::

# はじめに
Google Analytics 4 (GA4) コンソールのレポートに平均エンゲージメント時間という項目があります。
それをGoogle Analytics 4 Data API + Google Apps Script (GAS) で取得する方法を検索しても出てこなかったため、記事に残します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/ad1ddbc7-cc7b-a4ba-1409-6dada2d469ff.png)

# 平均エンゲージメント時間という指標はない
[runReport](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runReport)メソッドを利用して平均エンゲージメント時間のレポートを出力したいです。

利用できる指標（Metrics）とディメンション（Dimension）の一覧はこちら∶

https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema

「平均エンゲージメント時間」なので、"average"、"engagement"などのキーワードで検索をかけましたが、ヒットしませんでした。
唯一近かったのはこれ。
| API Name | UI Name | Description |
|:---|:---|:---|
| userEngagementDuration | User engagement | The total amount of time (in seconds) your website or app was in the foreground of users` devices. |

でもこれは合計のエンゲージメント時間であって、平均値ではありません。

# そもそも平均エンゲージメント時間はなに？
冒頭の画像にも少しヒントがありました。
以下のヘルプに平均エンゲージメント時間の算出方法が書いてあります。

https://support.google.com/analytics/answer/13391283?hl=en#zippy=%2Cviews-and-event-count%2Caverage-engagement-time

> Average engagement time = (The total length of time your website was in focus or your app was in the foreground across all sessions) / (The total number of active users)

> 平均エンゲージメント時間 =（すべてのセッションでウェブサイトがフォーカス状態にあったか、アプリがフォアグラウンド表示されていた合計時間）÷（アクティブ ユーザーの合計数）

つまり、平均時間を出すには、先ほどの合計エンゲージメント時間をアクティブユーザー数で割らないといけません。

# 平均エンゲージメント時間の取得方法

以上を踏まえて、[全体の平均エンゲージメント時間](#全体の平均エンゲージメント時間)と[ページパスとスクリーンクラス別の平均エンゲージメント時間](#ページパスとスクリーンクラス別の平均エンゲージメント時間)を取得するソースコードを紹介します。
最後に[解説](#解説)を載せます。

## 全体の平均エンゲージメント時間
レポート＞ライフサイクル＞エンゲージメント＞概要 の平均エンゲージメント時間を取得する方法です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/27cb857a-cae7-532a-37db-91f44488d605.png)

`PROPERTY_ID`は事前に定義したプロパティIDです。

```js
function main() {
  const request = {
    metrics: [
      {
        name: 'averageEngagementTime',
        expression: "userEngagementDuration/activeUsers",
      },
    ],
    dateRanges: [
      {
        startDate: '2024-01-01',
        endDate: '2024-01-10',
      },
    ],
  };
  const response = AnalyticsData.Properties.runReport(request, 'properties/' + PROPERTY_ID);
  Logger.log(`response = ${response}`);
}
```
<details><summary>出力例</summary>

> response = {"metadata":{"timeZone":"Asia/Tokyo","currencyCode":"USD"},"kind":"analyticsData#runReport","rows":[{"metricValues":[{"value":"1525"}]}],"metricHeaders":[{"type":"TYPE_FLOAT","name":"averageEngagementTime"}],"rowCount":1}
</details>

コンソール上では分秒表記となっていますが、レスポンスは秒単位です。
割り切れない場合もあります。

例）25分25秒
> "value":"1525"

例）10分36秒
> "value":"635.8796296296296"

## ページパスとスクリーンクラス別の平均エンゲージメント時間

レポート＞ライフサイクル＞エンゲージメント＞ページとスクリーン のページパスとスクリーンクラス別の平均エンゲージメント時間を取得する方法です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243366/35ddb1f6-cf2d-7302-cb77-d8b6e9b8444a.png)


`PROPERTY_ID`は事前に定義したプロパティIDです。

```js
function main() {
  const request = {
    dimensions: [
      {
        name: 'unifiedScreenClass',
      },
    ],
    metrics: [
      {
        name: 'averageEngagementTime',
        expression: "userEngagementDuration/activeUsers",
      },
    ],
    dateRanges: [
      {
        startDate: '2024-01-01',
        endDate: '2024-01-10',
      },
    ],
  };
  const response = AnalyticsData.Properties.runReport(request, 'properties/' + PROPERTY_ID);
  Logger.log(`response = ${response}`);
}
```
<details><summary>出力例</summary>

> response = {"metadata":{"currencyCode":"USD","timeZone":"Asia/Tokyo"},"rows":[{"metricValues":[{"value":"715.88888888888891"}],"dimensionValues":[{"value":"/aaa"}]},{"metricValues":[{"value":"626.30769230769226"}],"dimensionValues":[{"value":"/ccc"}]},{"metricValues":[{"value":"493.75"}],"dimensionValues":[{"value":"/eee"}]},{"metricValues":[{"value":"153.77777777777777"}],"dimensionValues":[{"value":"/ddd"}]},{"metricValues":[{"value":"89.666666666666671"}],"dimensionValues":[{"value":"/bbb"}]}],"metricHeaders":[{"type":"TYPE_FLOAT","name":"averageEngagementTime"}],"kind":"analyticsData#runReport","dimensionHeaders":[{"name":"unifiedScreenClass"}],"rowCount":5}

</details>

こちらも同様、秒単位の小数が返却されます。

## 解説

今回の肝はここ。
```js
metrics: [
  {
    name: 'averageEngagementTime',
    expression: "userEngagementDuration/activeUsers",
  },
],
```

### `metrics.name`

通常、`metrics`の`name`に取得したい指標を指定します。
例えば、イベント数を取得したい場合はこうです。

```js
metrics: [
  {
    name: 'eventCount',
  },
],
```
[Metricのリファレンス](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/Metric)によると、`expression`が指定された場合に限って、任意の文字列を指定できるとのことでした。

> If expression is specified, name can be any string that you would like within the allowed character set. For example if expression is screenPageViews/sessions, you could call that metric's name = viewsPerSession. 

今回は平均エンゲージメント時間ということで、`averageEngagementTime`にしましたが、全く別の文字列でも構いません。
（正規表現の制限がありますので、リファレンスをご参照ください。）
ただ、`expression`に指定した`userEngagementDuration`を`name`にも指定するとエラーが発生します。

:::note alert
GoogleJsonResponseException: API call to analyticsdata.properties.runReport failed with error: userEngagementDuration is used in both a metric's name and the same metric's expression. Please use a different name in metric.name.
:::

### `metrics.expression`

じゃあ、`expression`はいつ使うかというと、今回のように事前に定義されていないが、指標同士の計算によって導き出せる時です。
平均エンゲージメント時間なら`userEngagementDuration/activeUsers`。
ユーザーあたりのイベント数なら`eventCount/totalUsers`。
GA4を利用する際に割り算を使いたいケースが多いだと思いますが、数式なので割り算以外もできます。
例えば、新規ユーザーを除いたユーザー数は`totalUsers-newUsers`です。

# さいごに
GASで書きましたが、リクエストオブジェクトに関わる部分なので、
HTTPにもJavaにもPythonにも通じるではないでしょうか。
「平均エンゲージメント時間という指標がない💦💦」と思った方の手助けになりますように。