pythonでコホート分析

# 概要
ログ解析サービスなどでよく見かけるこの図
![98860c727bd48664294de0efbf70dd72.png](https://qiita-image-store.s3.amazonaws.com/0/159275/06c781f0-84ad-3d59-2107-a998905c5be5.png)
コホート図と呼ばれ、Webサービス・アプリサービスに対するユーザーの継続率を確認することができます。
マーケティングの分野で主に使われ、
- 短期的なマーケティング施策の効果測定
- 特定のユーザー層のエンゲージメント測定
などミクロな視点でサービス状況を確認できます。

基本的に、GoogleAnalyticsやfirebaseアナリティクスなどマーケティングサービスの標準機能として付属していますが、
ユーザーのイベントログを多面的にチェックしようと思うと応用が効かず。

python(pandas)で集計しようと思いましたが、
あまり良さそうなリソースがなく、（あることはあるが、集計ミスが起きうる書き方をしている・・・）
１から手で書く上での基本的な手順をまとめてみました。

## さっそく書いてみる
### jupyter notebook
細かい説明はいらないからコードを見たいという方はこちらへ
https://github.com/pigooosuke/cohort_analysis/blob/master/kernel.ipynb

### データ準備
手頃なオープンデータセットがあればいいのですが、
Webサービスのいい感じのユーザーログは公開されておらず、（かなり探したけれど見つからなかったです）
まずデータセットを作るところから始めます。

今回作るのはかなり簡易的なデータです。
- customer_id
- event_date (特定のログが記録された日付)
これを週次で観察してみます。

```python

import random
from datetime import datetime, timedelta

def random_date(start, end):
    """startとendの差をday変換してランダムで選択して、startに加算"""
    delta = end - start
    int_delta = (delta.days)
    random_day = random.randrange(int_delta)

    return (start + timedelta(days=random_day)).strftime("%Y-%m-%d")

def random_id(n_size):
    return random.choice(range(n_size))

def create_dataset(d_length, n_size, start_day, end_day):
    """ datasetをつくる
    customer_id: user
    event_date: action(ログイン、特定のログなど、なにかしらの行動が発生した日)
    weekofyear: event_dateがその年の何週目にあたるか
    """
    d_list = []
    for _ in range(d_length):
        d_list.append([random_id(n_size),random_date(start_day, end_day)])
    # dataframeに変換
    df = pd.DataFrame(d_list, columns=["customer_id","event_date"])
    # event_dateがその年の何週目にあたるのかを算出
    df["weekofyear"] = pd.to_datetime(df["event_date"]).dt.weekofyear
    
    return df

d1 = datetime.strptime('2018-01-01', '%Y-%m-%d')
d2 = datetime.strptime('2018-06-01', '%Y-%m-%d')
df = create_dataset(20000, n_size=1000, start_day=d1, end_day=d2)
df.head()
```

2018年1月から6月までで、
1000ユーザー分のログを2万件生成します。
![3665e3095d54d6b6fb9a778c194a596a.png](https://qiita-image-store.s3.amazonaws.com/0/159275/ca3d0538-672c-6ec8-f3bc-1eaf5a774f4f.png)
併せて、そのイベント日が1年のうち何週目に当たるかを3列目に入れています。

### 集計作業
今回特定のイベントを行ったユーザーの継続率を確認するために、ユーザー単位でそれぞれ一番古いイベントの日付を特定します。

```python
# merge
df=df.merge(df.groupby("customer_id")["event_date"].min().reset_index(),on="customer_id",suffixes=("","_init") ,how="left")
# 初回のイベント日についてもweekodyearを計算
df["weekofyear_init"] = pd.to_datetime(df["event_date_init"]).dt.weekofyear
df.head()
```

![b4cb71564b157783bbb55ee8ea910923.png](https://qiita-image-store.s3.amazonaws.com/0/159275/db0c91bb-38fb-85e4-1199-75265c18e8f5.png)
初回イベントに関しても何週目のイベントだったのかを確認します。

今回、特定期間から行動を始めたユーザーが各週でどう行動しているのかを確認しています。
そのため、マトリックスを作成し、それぞれに何ユーザー存在するかをカウントすればOKです。

```python
gp = df.groupby(["weekofyear_init","weekofyear"])["customer_id"].nunique()
gp = gp.reset_index()
gp.head()
```
![3e42774696f468e8e4c06b6bd3d2a247 (1).png](https://qiita-image-store.s3.amazonaws.com/0/159275/59699c51-e5a4-8370-a7a9-4fe3a0c7c76a.png)

上記の２行目で言うと、1週に行動したユーザーのうち、２週目に行動したユーザー数が366人いたということがわかります。
図示していくためには、さらに工夫が必要で、その`weekofyear`は`weekofyear_init`から数えて何週目に当たるのかを計算してみます。

```python
# weekofyear_init(初回イベント発生日)から見て、それぞれのイベントが何周目後に発生したものかを集計
gp["cohort_group"] = gp["weekofyear"] - gp["weekofyear_init"] + 1
gp.head()
```
![96a1a6346cc0285cd135bc00671dc562.png](https://qiita-image-store.s3.amazonaws.com/0/159275/d95ba766-8956-4cb8-1179-5b8215f712be.png)

これをピボットテーブルに落としてみます。

```python
user_retention=gp.pivot_table(values="customer_id",columns="cohort_group",index="weekofyear_init")
user_retention.head(20)
```
![71a9ebf632e8c5d316171d29106e1161.png](https://qiita-image-store.s3.amazonaws.com/0/159275/404a38c4-2fa8-3ccb-f771-597669e9b5f4.png)

この表がまさにGAとかでよく目にするやつです。
ここの数字はユーザー数になっているので、１週目を元として比率に変換してみます。

```python
# それぞれのイベント初日のユーザー数を取得し、各イベント期間にどれだけユーザーが残っていたのかを計算
user_retention = user_retention.apply(lambda x : x / x.iloc[0],axis=1)
user_retention.head()
```
![321b11724bebe0ba1e539f87faf400d7.png](https://qiita-image-store.s3.amazonaws.com/0/159275/0770779d-27d5-ceb4-622c-64620f3a1f71.png)

最後にプロットしてみると

```python
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(18, 6))
plt.title('Cohort Analysis: User Retention')
sns.heatmap(user_retention, mask=user_retention.isnull(), annot=True, fmt='.0%')
```

![40f06b0f4b76a8f9fc6e3f066f77d915.png](https://qiita-image-store.s3.amazonaws.com/0/159275/231ffaef-3461-224f-766c-a57a8a155552.png)

完成です。
今回は非常に簡易的な集計を行いましたが、
特定のユーザー層を元に、特定の行動を行っているのかを独自で集計したい場合はこのような形で集計しましょう。

## 参考
http://www.gregreda.com/2015/08/23/cohort-analysis-with-python/