Mímisbrunnr知恵の泉

← マーケティングサイエンス 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 関連:コホート分析 | 前提:顧客生涯価値(LTV)

要点(BLUF)

1. リテンションとチャーン:定義を揃える

リテンション率(継続率)rr は、ある期に取引していた顧客が次の期も続ける確率です。チャーン率(解約率)cc はその逆で、抜ける確率。同じ期間で定義すれば、両者は表裏一体です。

c=1rc = 1 - r

ここで肝心なのは「同じ期間」で揃えること。月次の継続率と年次のチャーン率を引き算しても意味がありません(月次 churn 5% は年次では 10.951246%1-0.95^{12}\approx 46\%)。

獲得直後を第0期として、顧客が第 tt 期まで生き残っている確率を生存関数 S(t)S(t) と呼びます。各期の継続率 rkr_k を掛け合わせたものです。

S(t)=k=1trk,S(0)=1S(t) = \prod_{k=1}^{t} r_k, \qquad S(0) = 1

顧客生涯価値(LTV) で使った「期待継続期間」は、この生存関数を足し上げたものでした(獲得した第0期から数えます)。

期待生涯=t=0S(t)\text{期待生涯} = \sum_{t=0}^{\infty} S(t)

継続率が一定 rk=rr_k = r なら S(t)=rtS(t) = r^t となり、t=0rt=1/(1r)\sum_{t=0}^{\infty} r^t = 1/(1-r)。これが 02-01 の 1/(1r)1/(1-r) の正体です。問題は「継続率一定」が現実には成り立たないこと——次節でその理由を見ます。

2. なぜ継続率は一定でないのか:顧客の異質性

実データのリテンション率は、たいてい時間とともに上昇します(リテンション曲線が寝てくる、逓減がゆるくなる)。これは「だんだん顧客が定着した」とは限りません。顧客の異質性(heterogeneity)= churn の高い人と低い人が混在しているだけで、自然に起きます。

直感はこうです。獲得した集団に、すぐ離れる気まぐれ層(高 churn)と、長く続く忠実層(低 churn)が混ざっているとします。時間が経つと、高 churn の気まぐれ層は先にごっそり抜け、残るのは低 churn の忠実層に偏っていきます。すると、生き残った集団で測ったリテンション率は、忠実層の高い継続率へと近づいていく——これが生存者バイアスです。個々人の churn は一定でも、集団の観測値は上昇します。

flowchart TD
  Start["獲得時:忠実層60% + 気まぐれ層40%"] --> T1["時間が経つ"]
  T1 --> Leave["気まぐれ層(高churn)が先に抜ける"]
  T1 --> Stay["忠実層(低churn)が生き残る"]
  Leave --> Mix["生存者は低churn層に偏る"]
  Stay --> Mix
  Mix --> Rise["集団の観測リテンション率は上昇(曲線が寝る)"]

これは 顧客生涯価値(LTV) の「継続率一定」仮定の限界そのものです。観測された初期リテンションをそのまま一定とみなすと、生涯を大きく見誤ります。次節で数値にして確かめます。

3. 2セグメント混合で確かめる(コード)

概念:忠実層 A と気まぐれ層 B の2セグメント混合を考えます。各セグメントの churn は一定ですが、混ざると集団リテンションは一定になりません。

数式:重み wA,wBw_A, w_BwA+wB=1w_A+w_B=1)、各セグメントの継続率 rA,rBr_A, r_B とすると、集団の生存関数は2つの幾何級数の重み付き和です。

S(t)=wArAt+wBrBtS(t) = w_A\, r_A^{\,t} + w_B\, r_B^{\,t}

年齢 tt の集団で観測されるリテンション率は、隣り合う生存確率の比です。

r^(t)=S(t+1)S(t)\hat r(t) = \frac{S(t+1)}{S(t)}

tt\to\infty では高 churn 層 B が消え、r^(t)\hat r(t) は忠実層の rAr_A へ近づきます。一方、真の期待生涯はセグメントごとの 1/(1r)1/(1-r) を重みで足したものです。

t=0S(t)=wA1rA+wB1rB\sum_{t=0}^{\infty} S(t) = \frac{w_A}{1-r_A} + \frac{w_B}{1-r_B}

次のコードで、wA=0.6, rA=0.95w_A=0.6,\ r_A=0.95wB=0.4, rB=0.60w_B=0.4,\ r_B=0.60 として、観測リテンション率の表・真の期待生涯・素朴推定を出します。

import numpy as np
import pandas as pd

# 2セグメント混合:忠実層A(継続率0.95)と気まぐれ層B(継続率0.60)
w_A, r_A = 0.6, 0.95   # 忠実層:重み0.6、継続率0.95(churn 0.05)
w_B, r_B = 0.4, 0.60   # 気まぐれ層:重み0.4、継続率0.60(churn 0.40)

def S(t):
    """獲得時を母数とした年齢tの集団生存率 S(t)=w_A r_A^t + w_B r_B^t、S(0)=1"""
    return w_A * r_A**t + w_B * r_B**t

# age=0..9 で観測される集団リテンション率 = S(t+1)/S(t)
ages = np.arange(0, 10)
rows = []
for t in ages:
    rows.append({"年齢age": int(t),
                 "生存率S(t)": S(t),
                 "観測リテンション率": S(t + 1) / S(t)})
tbl = pd.DataFrame(rows)

print("=== 集団で観測されるリテンション率(年齢ageごと)===")
print(tbl.to_string(index=False, formatters={
    "生存率S(t)": "{:.4f}".format,
    "観測リテンション率": "{:.4f}".format}))

# 真の期待生涯 = Σ S(t) = w_A/(1-r_A) + w_B/(1-r_B)
true_life = w_A / (1 - r_A) + w_B / (1 - r_B)
T = 2000
life_num = np.sum(S(np.arange(T + 1)))   # 数値和でも確認

# 素朴推定:初期(age=0)の観測リテンション r0=0.81 を一定とみなす → 1/(1-r0)
r0 = S(1) / S(0)
naive_life = 1 / (1 - r0)

print(f"\n初期age=0の観測リテンション率 r0 = {r0:.4f}")
print(f"真の期待生涯 Σ S(t)(閉形式)  = {true_life:.4f} 期")
print(f"真の期待生涯 Σ S(t)(数値和)  = {life_num:.4f} 期")
print(f"素朴推定 1/(1-r0)(churn一定) = {naive_life:.4f} 期")
print(f"→ 一定churn仮定は期待生涯を {true_life - naive_life:.2f} 期ぶん過小評価")

出力:

=== 集団で観測されるリテンション率(年齢ageごと)===
 年齢age 生存率S(t) 観測リテンション率
     0  1.0000    0.8100
     1  0.8100    0.8463
     2  0.6855    0.8765
     3  0.6008    0.8997
     4  0.5405    0.9164
     5  0.4954    0.9280
     6  0.4597    0.9358
     7  0.4302    0.9409
     8  0.4048    0.9442
     9  0.3822    0.9463

初期age=0の観測リテンション率 r0 = 0.8100
真の期待生涯 Σ S(t)(閉形式)  = 13.0000 期
真の期待生涯 Σ S(t)(数値和)  = 13.0000 期
素朴推定 1/(1-r0)(churn一定) = 5.2632 期
→ 一定churn仮定は期待生涯を 7.74 期ぶん過小評価

出力の意味:観測リテンション率は、年齢0の 0.8100 から、年齢9で 0.9463 へと単調に上昇しています。施策で定着率が上がったわけではありません——コードの churn はセグメントごとに一定です。高 churn の気まぐれ層が先に抜け、生存者が忠実層(rA=0.95r_A=0.95)へ偏るので、観測値が rAr_A へ近づくだけ(生存者バイアス)。一方、真の期待生涯は 13.00 期(閉形式・数値和とも一致)。ところが、観測された初期リテンション 0.81 を「ずっと一定」とみなして 1/(10.81)1/(1-0.81) で見積もると、わずか 5.26 期——真値を 7.74 期も過小評価します。02-01 の LTV はこの期待生涯に粗利 mm を掛けて出すので、生涯を半分以下に見誤れば LTV も半分以下に潰れる。「観測した1点のリテンションを一定とみなす」のは危険だと数字が示しています。

リテンション曲線が寝ていく様子と、生存が2層の和であることを図にすると、生存者バイアスの直感がつかめます。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib  # noqa: F401  日本語ラベル

w_A, r_A = 0.6, 0.95
w_B, r_B = 0.4, 0.60

def S(t):
    return w_A * r_A**t + w_B * r_B**t

ages = np.arange(0, 13)
surv = S(ages)
comp_A = w_A * r_A**ages   # 忠実層の寄与
comp_B = w_B * r_B**ages   # 気まぐれ層の寄与
obs_ret = S(ages + 1) / S(ages)   # 各ageで観測される集団リテンション率

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4.2))

# 左:集団生存 S(t) を2セグメントに分解
ax1.plot(ages, surv, "o-", color="black", label="集団生存 S(t)")
ax1.plot(ages, comp_A, "s--", color="tab:blue", label="忠実層の寄与 0.6·0.95^t")
ax1.plot(ages, comp_B, "^--", color="tab:red", label="気まぐれ層の寄与 0.4·0.60^t")
ax1.set_xlabel("年齢 age(経過期間)")
ax1.set_ylabel("生存率")
ax1.set_title("生存は2層の和:気まぐれ層が先に消える")
ax1.legend()
ax1.grid(alpha=0.3)

# 右:観測される集団リテンション率は上昇していく
ax2.plot(ages, obs_ret, "o-", color="tab:green", label="観測リテンション率 S(t+1)/S(t)")
ax2.axhline(r_A, ls=":", color="tab:blue", label=f"忠実層の継続率 {r_A}")
ax2.axhline(obs_ret[0], ls=":", color="gray", label=f"初期リテンション {obs_ret[0]:.2f}(素朴に一定とみなす)")
ax2.set_xlabel("年齢 age(経過期間)")
ax2.set_ylabel("観測リテンション率")
ax2.set_title("生存者バイアスでリテンション率は上昇")
ax2.set_ylim(0.5, 1.0)
ax2.legend()
ax2.grid(alpha=0.3)

fig.tight_layout()
plt.show()

左図は、集団生存 S(t)S(t) が忠実層(青)と気まぐれ層(赤)の和であること、そして気まぐれ層が早々にゼロへ消えることを示します。右図は、観測リテンション率が初期 0.81 から忠実層の 0.95 へ向かって寝ていく様子。初期リテンション 0.81 を水平に伸ばす素朴な見方(灰点線)が、いかに集団の実態とズレるかが一目で分かります。

⚠️ よくある誤解

関連ノート