【Python】コホート分析の基礎
コホート分析は、グロースハックなどでよく利用される手法で、主にリテンション率を顧客全体もしくはセグメント別に時系列に把握することで、行動パターンの把握や問題の仮説を洗い出し、定着率などを改善する目的で利用されます。
今回はそのコホート分析をPythonを使って自在かつ速攻で行うための処理を整理します。
まずは基本ライブラリをimportします。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
説明用に、こんなサンプルデータを作ってみます。
data = {
'user_id': np.random.randint(1, 1000, 10000),
'login_date': pd.date_range(start='2024-01-01', end='2024-12-31', periods=10000)
}
df = pd.DataFrame(data)
dfを呼び出すと、こんなdataframeができました。
user_idはランダムに生成されていますが、複数行あるuser_idが存在するはずです。
これをユーザーごとのログイン履歴に見立てて、全体のリテンション率を可視化していきます。
ここでのリテンション率は、「最初にログインした年月から、nヶ月目までに何%生き残っているか?」という形でアウトプットすることにします。
まずは、ユーザーの最初のlogin_dateの年月の部分だけを抽出した、login_monthを生成します。
次に、transform()
を使って、user_idごとに最初のlogin_monthを付与することで、コホート、つまり集計に使うグループを作成します。
※groupbyオブジェクトにおけるtransform()
とapply()
などの挙動の違いは重要なのでいつか解説します。
df['login_month'] = df['login_date'].dt.to_period('M')
df['cohort'] = df.groupby('user_id')['login_month'].transform('min')
現時点では、dfはこんな感じになっています。
次に、各login_monthと先ほど作成したcohort(最初のlogin_month)の引き算をします。
これにより、各user_idのそれぞれのlogin_monthについて、cohort(最初のlogin_month)との月数の差分が取得できます。
df['cohort_index'] = (df['login_month'] - df['cohort']).apply(lambda x: x.n)
※細かいですが、このcohort_indexで表現される月数の差は pd.Period
オブジェクトとして表現されます。apply(lambda x: x.n)
は、各 Periodオブジェクトの数値部分(月数)を取得してくれます。この処理がない場合、cohort_indexは<10 * MonthEnds>
のような形で出力されますが、apply(lambda x: x.n)
の処理を入れることによって、10
の部分をくり抜いています。
現時点では、dfはこんな感じになっています。
あとは、cohortとcohort_indexごとにログインしたuser_idのユニーク数を集計します。
これにより、例えば2024-01にログインしたユーザー群が、翌月、翌々月、それ以降にどれくらいの規模で継続ログインしているか?を表現します。
cohort_data = df.groupby(['cohort', 'cohort_index'])['user_id'].nunique().reset_index()
cohort_dataを呼び出すとこんな感じになってます。
このcohort_dataをpivot()
を使って欲しい形に整形します。
cohort_counts = cohort_data.pivot(index='cohort', columns='cohort_index', values='user_id')
cohort_countsを呼び出すとこんな感じになってます。
いよいよ、リテンション率を計算します。
やりたいこととしては、cohort_countsに対して、行方向に1列目の数値を割り算していきたいです。
# まずは割り算の分子の部分(cohort_indexが0の列の数値)を用意
initial_cohort_sizes = cohort_counts.iloc[:, 0]
# 次に、各行列に対して、割り算を計算する
retention = cohort_counts.divide(initial_cohort_sizes, axis=0)
※この処理では、initial_cohort_sizesの各値が、対応するcohort_countsの行全体、つまりそのコホートの全てのcohort_indexに対して「ブロードキャスト」されます。
retentionを呼び出すと、こんな感じになってます。
あとは、ヒートマップを作成して完成です。
plt.figure(figsize=(12, 8))
sns.heatmap(retention, annot=True, cmap='YlGnBu', fmt='.0%')
plt.title('cohort analysis')
plt.xlabel('cohort index')
plt.ylabel('cohort')
plt.show()
最後に、ワンブロックにコードをまとめておきます。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# サンプルデータの作成
data = {
'user_id': np.random.randint(1, 1000, 10000),
'login_date': pd.date_range(start='2024-01-01', end='2024-12-31', periods=10000)
}
df = pd.DataFrame(data)
# ユーザーの最初のlogin_dateの年月の部分だけを抽出した、login_monthを生成する。
df['login_month'] = df['login_date'].dt.to_period('M')
# user_idごとに最初のlogin_monthを付与することで、コホート、つまり集計に使うグループを作成する。
df['cohort'] = df.groupby('user_id')['login_month'].transform('min')
# 各login_monthと先ほど作成したcohort(最初のlogin_month)の引き算をする。
df['cohort_index'] = (df['login_month'] - df['cohort']).apply(lambda x: x.n)
# cohortとcohort_indexごとにログインしたuser_idのユニーク数を集計する。
cohort_data = df.groupby(['cohort', 'cohort_index'])['user_id'].nunique().reset_index()
# pivot()を使ってdataframeを欲しい形に整形する。
cohort_counts = cohort_data.pivot(index='cohort', columns='cohort_index', values='user_id')
# リテンション率を計算する。上のcohort_countsで、行方向に1列目の数値を割り算していきたい。
# まずは分子を用意する。
initial_cohort_sizes = cohort_counts.iloc[:, 0]
# 次に、各行列に対して、割り算を計算する
retention = cohort_counts.divide(initial_cohort_sizes, axis=0)
# ヒートマップの作成
plt.figure(figsize=(12, 8))
sns.heatmap(retention, annot=True, cmap='YlGnBu', fmt='.0%')
plt.title('cohort analysis')
plt.xlabel('cohort index')
plt.ylabel('cohort')
plt.show()
Discussion