GoogleAnalyticsのデータでアソシエーション分析

:::note warn
この記事はGoogle Analytics 4ではなく、Universal Analyticsを対象としています。
:::

## Rでアソシエーション分析したい

多変量解析の手法のひとつであるアソシエーション分析を実データで学習しておりまして、アクセスログからデータ抽出・前処理するまでの流れをメモ程度に残しておきます。
この記事はGoogle Analytics 360(BigQuery Export)の利用を前提としています。

## アソシエーション分析とは
バスケット内のアイテム間の意味のある関係を調べる分析手法です。「XXが購入されるとYYも購入される傾向にある」といったようなアイテムごとの組み合わせの規則を抽出することができます。

## データ抽出
分析に用いるデータのトランザクション(共起するデータポイント)の単位について、以下2つの粒度で抽出してみます。

- セッション内でのイーコマーストランザクション(SKU)
- セッション内で表示されたページ(第二階層)

今回の場合はBigQueryからデータ抽出・CSVファイル出力し、R側で最終的にトランザクション型のデータを用意することになります。

### 1. SKU単位の場合

セッション(visitId)ごとの購入(SKU)を1つのトランザクションとします。
※抽出元のサービスが1度に1つの商品しか購入できないため、「1セッションを1つのトランザクション」として捉えています。一度に複数購入可能であればこの限りではありません。

```sql
SELECT
  visitId,
  product.productSKU AS sku 
FROM
  `project_name.dataset_name.ga_sessions_YYYYMMDD`,
  UNNEST(hits) AS hits,
  UNNEST(hits.product) AS product
```

以下のようなテーブルが抽出できるイメージです。

| 行 | visitId | sku |
|----|---------|-----|
|  1  |   1585704179      |  ABC00000149   |
| 2   |    1585674923     |   DEF00001609  |
| 3   |    1585701501     |   GHI00004228  |


### 2. ページ単位の場合

セッションごとの閲覧ディレクトリ(第2階層/pagePathLevel2)を1つのトランザクションとします。`/`の文字列は列名としては扱えないので、あらかじめ除去しておきます。

```sql
WITH
  raw_data AS (
  SELECT
    visitId,
    hits.page.pagePathLevel2 AS path
  FROM
    `project_name.dataset_name.ga_sessions_YYYYMMDD`,
    UNNEST(hits) AS hits
  WHERE
    hits.type = "PAGE"),
  fixed_data AS(
  SELECT
    visitId,
  IF
    (path = "/",
      "top_page",
      path) AS path
  FROM
    raw_data)
SELECT
  visitId,
  REGEXP_REPLACE(path, "/", "") AS path,
FROM
  fixed_data
GROUP BY
  visitId,
  path
```

以下のようなテーブルが抽出できるイメージです。


| 行 | visitId | path |
|----|---------|-----|
|  1  |   1585704179      |  top_page   |
| 2   |    1585674923     |   faq  |
| 3   |    1585701501     |   products  |

## Rで前処理

今回必要なライブラリを読み込みます。

```R
library(tidyverse) # データハンドリング
library(arules) # アソシエーションルールの検出
library(arulesViz) # アソシエーションルールの可視化
library(dummies) # one-hotエンコード
```

ローカル環境の処理能力の限界もありますので、前処理は可能な限りBigQueryやDataprepなどクラウド側の計算資源を活かした方が良さそうです。

CSVファイルをデータフレームとして読み込み、one-hotエンコーディングし、transaction型へキャストします。

```R
raw_data <- read_csv("src/xxx_by_visitid.csv") %>% 
  distinct(visitId,sku) %>% 
  as.data.frame() #dummiesがtibbleを扱えないためdataframeにキャスト
df.one_hot <- dummy.data.frame(raw_data,sep = "_") 

colnames(df.one_hot) = str_replace(colnames(df.one_hot), 'column_name_', '')

df.one_hot <- df.one_hot %>% 
  group_by(visitId) %>% 
  summarise_each(funs(sum)) %>% 
  select(-visitId)

d.transaction <- as(as.matrix(df.one_hot), "transactions") 
```


## aprioriの実行
`apriori()`を実行してアソシエーションルールを検出します。`parameter`での支持度、確信度の閾値は 検出されるルールの数やマシンの計算資源に合わせて適宜調整します。

```R
d.ap <- apriori(d.transaction, parameter = list(supp = 0.001, conf = 0.1))
```

`d.ap`にルール集合を代入しているのでで`inspect()`を用いてアソシエーションルールの確認ができます。ここではリフト値の高い順に10件抽出してみます。

```R
inspect(head(sort(d.ap, by = "lift"),10))
```

```R
##      lhs                         rhs        support   confidence coverage 
## [1]  {category,game,top10}    => {market}   0.8035937 0.9778611  0.8217872
## [2]  {enquete,top10}          => {market}   0.8010750 0.9761986  0.8206065
## [3]  {category,game,product}  => {market}   0.8049768 0.9743712  0.8261500
## [4]  {category,game}          => {market}   0.8096207 0.9738291  0.8313787
## [5]  {game,product,top10}     => {market}   0.8071919 0.9730396  0.8295571
## [6]  {game,top10}             => {market}   0.8123531 0.9723680  0.8354379
## [7]  {category,product,top10} => {market}   0.8955393 0.9723114  0.9210417
## [8]  {category,top10}         => {market}   0.9021848 0.9716029  0.9285530
## [9]  {game,market,top10}      => {category} 0.8035937 0.9892172  0.8123531
## [10] {game,market,product}    => {category} 0.8049768 0.9884297  0.8143996
##      lift     count
## [1]  1.057983 71466
## [2]  1.056185 71242
## [3]  1.054208 71589
## [4]  1.053621 72002
## [5]  1.052767 71786
## [6]  1.052040 72245
## [7]  1.051979 79643
## [8]  1.051213 80234
## [9]  1.050876 71466
## [10] 1.050039 71589
```

`lhs(left-hand-side)`が条件部、`rhs(right-hand-side)`が結論部となり、「lhsが含まれるトランザクションにはrhsが含まれやすい」と判断できます。

ここでの結果はどれもリフト値が1に近いため、ルールの重要度が高いと判断するのは難しそうです。

## ルール分布をplotで描画

`plot()`では支持度と信頼度の散布図を描画することができます。

```R
plot(d.ap)
```

![Rplot05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/681d16f2-e776-562c-809e-e591c5ef17bb.png)


`medhod`に`grouped`を指定するとルールをグルーピングすることができます。

```R
plot(d.ap, method = "grouped", control = list(k=10))
```

![Rplot03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/71b34fad-3fa2-27a0-dc61-988b90944b7e.png)

`method`に`graph`を指定するとigraphによるグラフ描画が可能です。

```R
plot(d.ap, method = "graph", control = list(type = "items"))
```

![Rplot07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/6b878282-c627-423d-3576-1787bc9279b0.png)

円の大きさがルールの支持度、濃淡がリフト値を表しています。アイテムからルールに向かう矢印は「そのアイテムが左側」であり、ルールからアイテムに向かう矢印は「そのアイテムが右側」であることを意味しています。

`interactive`に`TRUE`を指定するとXQuartsを利用してインタラクティブな描画が可能になります。

```R
plot(d.ap,method = "graph", control = list(type="items"), interactive = TRUE)
```

![スクリーンショット 2021-01-28 0.19.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147689/c79c350e-73ba-7b4a-d000-30bd782ab5f8.png)


このグラフからアソシエーションルールの向きの意味を解釈するのは難しそうですが、クラスタのパターン発見という意味では有意義な結果が得られそうです。例えば事前の知識がない状態でアクセスログを確認する場合に、ざっくりとした行動パターンのクラスタは把握できるかもしれません。また「導線設置」の施策があったとして、その施策の前後のグラフを比較することでクラスタに変化があったかどうかを可視化できそうです。

## おわりに
実際にアソシエーションルールを分析したところSKUについてはある程度納得の行く結果が得られたものの、同時購入ではなくセッション内での購入ベースとなるため、共起の重要性が低そうな印象もありました。
またページパスについてはサイトにおいてある程度ユーザーの行動が決まっていることもあり、有意味なルールを見つけることはできませんでした。ただ、グラフ描画によるクラスタ理解は活用できそうに思えました。

## 参考文献
- [Rによるデータサイエンス データ解析の基礎から最新手法まで 金 明哲](https://www.amazon.co.jp/gp/product/4627096011)
- [Rによる実践的マーケティングリサーチと分析 (原著第2版) クリス チャップマン](https://www.amazon.co.jp/dp/4320124561/)