Mímisbrunnr知恵の泉

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

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

📎 関連:顧客生涯価値(LTV) | 前提(CPA):マーケティングサイエンスとは

要点(BLUF)

1. CAC とは:CPA との違い

CAC(Customer Acquisition Cost)は、顧客を1人獲得するのにかかった総コストです。

CAC=ある期間の獲得関連コスト総額その期間に獲得した顧客数\text{CAC} = \frac{\text{ある期間の獲得関連コスト総額}}{\text{その期間に獲得した顧客数}}

混同しやすいのが マーケティングサイエンスとは で出た CPA(Cost Per Acquisition)。CPA はふつう広告費中心(広告費 ÷ 獲得数)で、媒体運用の効率を測る指標です。これに対し CAC は、広告費に加えて営業・マーケの人件費、ツール費、制作費など「獲得に関わる総コスト」を分子に入れます。だから一般に CAC ≧ CPA。CPA は広告効率の指標、CAC は事業の採算指標、と目的が違います。

集計範囲でも2種類あります。

2. LTV/CAC とペイバック期間(数式)

獲得が「割に合うか」は、顧客生涯価値(LTV) で求めた LTV を CAC と比べて判断します。

LTV/CAC 比=LTVCAC\text{LTV/CAC 比} = \frac{\text{LTV}}{\text{CAC}}

「3」はあくまで広く使われる経験則で、業種・粗利率・資金調達環境で適正値は動きます(要最新確認)。

比が良くても、いつ回収できるかは別問題です。ペイバック期間は、毎期の粗利で CAC を回収し終えるまでの期間で、キャッシュが限られるほど効いてきます。

flowchart TD
  CAC["CAC = 獲得コスト総額 / 獲得顧客数"] --> R{"LTV / CAC は?"}
  R -->|"3以上"| Good["健全:投資を拡大"]
  R -->|"1〜3"| Mid["要改善:CAC低減 または LTV向上"]
  R -->|"1未満"| Bad["赤字:獲得を止める・見直す"]
  Good --> PB["ペイバック期間で資金繰りも確認"]
  Mid --> PB

3. チャネル別に採算を判定する(コード)

4チャネルについて、CAC・LTV(02-01 の閉形式 m(1+d)/(1+dr)m(1+d)/(1+d-r))・LTV/CAC・ペイバック期間を pandas で算出し、LTV/CAC ≧ 3 かで持続可能性を判定します。低 CAC でも LTV が低ければ比は低いという逆転例(ディスプレイ)を入れています。

import numpy as np
import pandas as pd

d = 0.10  # 期間割引率(全チャネル共通)

def ltv(m, r, d):
    """02-01 の閉形式 LTV = m(1+d)/(1+d-r)(獲得直後の初回粗利も含む)"""
    return m * (1 + d) / (1 + d - r)

def payback_periods(m, r, d, cac, max_t=1000):
    """累積(割引×生存)粗利が CAC に到達するまでに必要な期数。
    到達しなければ inf(LTV < CAC で回収不能)。"""
    q = r / (1 + d)
    cum = 0.0
    for n in range(max_t + 1):
        cum += m * q ** n          # 第n期の割引後・期待粗利を積み上げる
        if cum >= cac:
            return n + 1           # 必要な期数(1始まり)
    return np.inf

# チャネル別の前提(合成データ)
ch = pd.DataFrame({
    "チャネル": ["紹介", "検索広告", "SNS広告", "ディスプレイ"],
    "CAC":     [4000, 5000, 8000, 1500],   # 獲得コスト総額 ÷ 獲得顧客数(円)
    "粗利m":   [3000, 2500, 2000,  900],   # 1期あたり粗利(円)
    "継続率r": [0.85, 0.80, 0.75, 0.55],
})

ch["LTV"]      = ch.apply(lambda x: ltv(x["粗利m"], x["継続率r"], d), axis=1)
ch["LTV/CAC"]  = ch["LTV"] / ch["CAC"]
ch["回収期数"] = ch.apply(lambda x: payback_periods(x["粗利m"], x["継続率r"], d, x["CAC"]), axis=1)
ch["持続可能"] = np.where(ch["LTV/CAC"] >= 3, "OK", "NG")  # LTV/CAC>=3 を健全の目安に

def fmt_pb(v):
    return "回収不能" if np.isinf(v) else f"{int(v)}期"

print("=== チャネル別の採算(割引率 d=0.10)===")
print(ch.to_string(index=False, formatters={
    "CAC": "{:,.0f}".format,
    "粗利m": "{:,.0f}".format,
    "継続率r": "{:.2f}".format,
    "LTV": "{:,.0f}".format,
    "LTV/CAC": "{:.2f}".format,
    "回収期数": fmt_pb}))

# 低CACでも採算が良いとは限らない、という逆転を取り出す
cheapest   = ch.loc[ch["CAC"].idxmin()]
best_ratio = ch.loc[ch["LTV/CAC"].idxmax()]
print(f"\n最も低いCAC      :{cheapest['チャネル']}"
      f"(CAC={cheapest['CAC']:,.0f}円, LTV/CAC={cheapest['LTV/CAC']:.2f})")
print(f"最も高いLTV/CAC  :{best_ratio['チャネル']}"
      f"(CAC={best_ratio['CAC']:,.0f}円, LTV/CAC={best_ratio['LTV/CAC']:.2f})")
print("→ CACが最も低いチャネルが、最も採算が良いとは限らない。")

出力:

=== チャネル別の採算(割引率 d=0.10)===
  チャネル   CAC   粗利m 継続率r    LTV LTV/CAC 回収期数 持続可能
    紹介 4,000 3,000 0.85 13,200    3.30   2期   OK
  検索広告 5,000 2,500 0.80  9,167    1.83   3期   NG
 SNS広告 8,000 2,000 0.75  6,286    0.79 回収不能   NG
ディスプレイ 1,500   900 0.55  1,800    1.20   3期   NG

最も低いCAC      :ディスプレイ(CAC=1,500円, LTV/CAC=1.20)
最も高いLTV/CAC  :紹介(CAC=4,000円, LTV/CAC=3.30)
→ CACが最も低いチャネルが、最も採算が良いとは限らない。

出力の意味:持続可能(LTV/CAC ≧ 3)と判定できたのは紹介だけ(比 3.30)。回収も 2 期と速く、最優先で伸ばすチャネルです。SNS広告は CAC が最大(8,000 円)で比 0.79 < 1、累積粗利が CAC に届かず回収不能——獲得するほど赤字なので止めるべき。注目はディスプレイで、CAC は最小の 1,500 円なのに、粗利 m=900m=900・継続率 0.55 と低く LTV が 1,800 円しかなく、比は 1.20 にとどまります。最も低い CAC(ディスプレイ)と最も高い LTV/CAC(紹介)は一致しません。つまり「CAC が安い=良いチャネル」ではない。採算は CAC 単体ではなく LTV/CAC で、資金繰りはペイバック期間で——この2つを併せて見るのが、獲得投資の意思決定の型です。

⚠️ よくある誤解

関連ノート