継続率三角表を作り三つの罠を数字で見る
目標
「モアノート」の週次の登録コホートで、継続率の三角表を作り、分母・空欄・生存者という3つの落とし穴を数字で確認したうえで、正しい加重継続率と、登録経路別の継続率で判断します。
なぜ重要なのか
全体のアクティブ数は、新しく入ってくる人と、去る人を、1つの数字に混ぜます。同じ週に登録した人を別々に追いかけて初めて、製品が人を引き留めているかどうかが見えます。そして、その計算は、分母を変えたり、空欄を0で埋めたりするだけで、簡単に良くなったり悪くなったりします。広告費とロードマップがかかった数字なので、定義どおりに計算し直されなければなりません。
用意するものと定義
/opt/fixtures/founder/product/users.csv・events.csv(指標のモジュールと同じデータ)。登録経路は、/opt/fixtures/founder/product/touches.csvの登録前の最後の接点(user_id,ts,channelでtsが最も遅い行)のchannelです。- ソウル時刻基準の週(月曜00:00開始)、0週目 = 2026-03-02、観測は15週目までです。社内アカウント(is_internal=1)は除きます。
- コホートc = 登録時刻の週(0–11)。N週目の継続 = 週c+Nにsession_startが1回以上。分母は、コホートの登録者数。 c+N > 15の欄は、観測前なので
nullです。 - すべての関数の最初の引数は、データのディレクトリです。比率は、小数第4位で四捨五入します。
ステップ
/root/founder/cohort/sizes.jsonに、コホートごとの登録者数を、{"0": n, ..., "11": n}で書く。/root/founder/cohort/cohort.pyに、retention(fix, max_n)を作る:{"0": [r0, r1, ..., r_max_n], ...}、観測前の欄はNone。/root/founder/cohort/denominator.jsonに、コホート4の4週目の継続率を、2つの分母で書く:rate_cohort_size(正しい分母)、rate_week0_active(0週目の活動者を分母にする。わざと犯す間違い)。/root/founder/cohort/censor.jsonに、6週目の継続率を、2つの方法で書く:pooled_rate(観測が終わったコホートだけ、人数で加重)、naive_rate_with_zeros(すべてのコホート、観測前の欄を0として)、そしてeligible_cohorts(観測が終わったコホートの数)。/root/founder/cohort/survivor.jsonに、4週目の継続率を、2つの名簿で書く:all_users_rate(コホート0–11の全員、観測が終わったコホートだけ)、survivors_only_rate(15週目に活動した人だけを選んで、同じ計算)、survivors(その人数)。cohort.pyに、pooled(fix, n, unbounded=False)を加える: 観測が終わったコホートだけを集めた、人数で加重した継続率{"cohorts": k, "users": u, "retained": r, "rate": 0.0000}。unboundedが真なら、「c+N週目以降のどの週でも(c+Nを含む)活動」として数える。/root/founder/cohort/channel.jsonに、登録経路(最後の接点)別の4週目の加重継続率を、{"organic_search": r, ...}の5つのチャネルで書く。/root/founder/cohort/report.jsonに、curve(n=0..8のbounded加重継続率9個)、week4、week4_unbounded、flatten_week(前の週に比べた低下が0.02未満になる最初のn、1以上)、best_channel、worst_channelを書く。
参考
- 週:
(서울 시각 - datetime(2026, 3, 2, tzinfo=서울)).days // 7(プレースホルダーはソウル時刻と、ソウルのタイムゾーンです) - 人ごとの「活動した週の集合」を先に作っておけば、すべてのステップが、集合の検査1回で済みます。
- よくある間違い: 分母を0週目の活動者にすること、観測前の欄を0で埋めて平均すること、コホートの比率を単純平均すること、いま残っている人だけを選んで過去を見ることです。
コホートごとの登録者数
/root/founder/cohort/sizes.jsonに、登録週(0–11)ごとの外部の登録者数を、文字列のキーで書く。
signup_atをソウル時刻に変えて、2026-03-02から何日かを7で割った商が、コホートです。社内アカウントは除きます。
継続率の三角表: 観測前の欄はNone
/root/founder/cohort/cohort.pyに、retention(fix, max_n)を作る。コホートの文字列キー → n=0..max_nの継続率のリスト、c+n>15の欄はNone。
人ごとに、session_startがあった週の集合を先に作ってください。分母は、コホートの登録者数で、観測が終わっていない欄は、0ではなくNoneです。
分母を変えると、継続率が良く見える
/root/founder/cohort/denominator.jsonに、コホート4の4週目の継続率を、rate_cohort_sizeとrate_week0_activeの2つの分母で書く。
分子は同じです(週8に活動したコホート4の人)。分母だけを、登録者数と、0週目(週4)の活動者数に変えてみます。
観測前の欄を0で埋めると、曲線が偽物のように折れる
/root/founder/cohort/censor.jsonに、6週目の継続率のpooled_rate・naive_rate_with_zeros・eligible_cohortsを書く。
正しいほうは、c+6 ≤ 15のコホートだけを集めて、全体の継続者 ÷ 全体の登録者です。間違ったほうは、すべてのコホートの登録者を分母に入れます(観測前の人は、継続0として数えられます)。
いま残っている人だけを見ると、過去が良く見える
/root/founder/cohort/survivor.jsonに、4週目の加重継続率を、all_users_rate(全員)とsurvivors_only_rate(15週目の活動者だけ)で、そしてsurvivors(その人数)を書く。
2つの計算は、コホート・週のルールが同じで、人の名簿だけが違います。名簿は登録時点で固定するのが原則なので、2つ目の数字は、わざと犯す間違いです。
加重継続率の関数: boundedとunbounded
cohort.pyに、pooled(fix, n, unbounded=False)を加える。観測が終わったコホートだけ、人数で加重、{cohorts, users, retained, rate}を返す。
コホートの比率の単純平均ではなく、継続者の合計 ÷ 登録者の合計です。unboundedは、c+n週目と、その後のどの週でも活動したかどうかを見ます。
登録経路別の4週目の継続率
/root/founder/cohort/channel.jsonに、最後の接点のチャネル5つそれぞれの、4週目の加重継続率を書く。
touches.csvで、人ごとにtsが最も遅い行のchannelが、登録経路です。名簿だけを、そのチャネルの人に絞って、pooledと同じ計算を行います。
曲線と判断を1枚に
/root/founder/cohort/report.jsonに、curve(n=0..8)、week4、week4_unbounded、flatten_week、best_channel、worst_channelを書く。
flatten_weekは、n=1から見て、curve[n-1] − curve[n] < 0.02となる最初のnです。チャネルは、ステップ7の結果で、最も高いものと低いものです。