💰 STEP 52: 実践プロジェクト3 – マーケティングROI分析
広告キャンペーンの効果を測定し、ROIを最大化しよう
📋 このプロジェクトで取り組むこと
- チャネル別のROI・ROAS・CAC計算
- アトリビューション分析(貢献度配分)
- A/Bテストによる統計的検証
- 予算配分の最適化提案
- マーケティング会議向けプレゼンテーション
難易度: 実践レベル(マーケティングを「科学」する)
📊 1. プロジェクトの概要
あなたに与えられたミッション
あなたはD2C化粧品ブランド「BeautyNow」のマーケティング部に所属しています。
今期、複数のマーケティングチャネルに総額5,000万円を投資しました。
「現在、5つのチャネルに投資しているが、
どのチャネルが最も効果的か分析し、来期の予算配分を最適化したい。
また、顧客獲得単価(CAC)を下げる方法も提案してほしい。
2週間後のマーケティング会議で報告をお願いします。」
現在の予算配分:
・Google広告: 2,000万円(40%)
・SNS広告: 1,500万円(30%)
・インフルエンサー: 800万円(16%)
・メール: 400万円(8%)
・アフィリエイト: 300万円(6%)
プロジェクトの全体像
→ ROI、ROAS、CACなどの指標を計算
STEP 2: アトリビューション分析
→ 複数チャネルの貢献度を公平に評価
STEP 3: A/Bテストによる統計的検証
→ 施策の効果を統計的に証明
STEP 4: 予算配分の最適化
→ データに基づいて予算をシフト
STEP 5: 改善施策の提案
→ 具体的なアクションプランを作成
📈 2. マーケティング指標の基礎知識
ROI、ROAS、CACとは
→ 投資した金額に対して、どれだけ「利益」が得られたか
2. ROAS(Return on Ad Spend: 広告費用対効果)
→ 投資した金額に対して、どれだけ「売上」が得られたか
3. CAC(Customer Acquisition Cost: 顧客獲得単価)
→ 1人の顧客を獲得するのにいくらかかったか
| 指標 | 計算式 | 意味 | 目安 |
|---|---|---|---|
| ROAS | 売上 / 広告費 | 広告の即時効果 | 300%以上 |
| ROI | (粗利-広告費) / 広告費 | 実際の収益性 | 100%以上 |
注意: ROAS 400%でも、粗利率が低ければROIは赤字になることがあります。
ROIの方がより厳密な指標です。
📈 3. STEP 1: チャネル別パフォーマンス分析
データの準備
まず、必要なライブラリを読み込み、ビジネス指標を設定します。
・pandas: データを表形式で扱うライブラリ
・numpy: 数値計算用ライブラリ
・scipy.stats: 統計検定用ライブラリ
・ビジネス指標(AOV、粗利率、LTV)を定数として設定
# 必要なライブラリを読み込む
import pandas as pd # データを表形式で扱う
import numpy as np # 数値計算
from scipy import stats # 統計検定
# ビジネス指標(前提条件)
AOV = 8000 # 平均注文金額(Average Order Value)
gross_margin_rate = 0.6 # 粗利率 60%
LTV = 20000 # 顧客生涯価値(Lifetime Value)
次に、各チャネルの実績データを作成します。実務では、広告管理画面やGoogle Analyticsから取得します。
# チャネル別の実績データ
# pd.DataFrame(): 表形式のデータを作成する関数
channels = pd.DataFrame({
'チャネル': ['Google広告', 'SNS広告', 'インフルエンサー', 'メール', 'アフィリエイト'],
'広告費': [20000000, 15000000, 8000000, 4000000, 3000000], # 円
'クリック数': [100000, 80000, 50000, 30000, 25000],
'CV数': [1000, 900, 600, 500, 400], # コンバージョン数(購入数)
'新規顧客数': [850, 800, 550, 200, 380] # 新規顧客の数
})
効率指標を計算する
各チャネルのCPC、CVR、CPA、CACを計算します。これらの指標で「効率」を測ります。
・CPC(Cost Per Click): 1クリックあたりの費用
・CVR(Conversion Rate): クリックから購入への転換率
・CPA(Cost Per Acquisition): 1件の購入獲得費用
・CAC(Customer Acquisition Cost): 1人の新規顧客獲得費用
# CPC(Cost Per Click: クリック単価)
# 1クリックあたりいくらかかったか
channels['CPC'] = channels['広告費'] / channels['クリック数']
# CVR(Conversion Rate: コンバージョン率)
# クリックした人のうち何%が購入したか
channels['CVR'] = channels['CV数'] / channels['クリック数'] * 100
# CPA(Cost Per Acquisition: 獲得単価)
# 1件のコンバージョンにいくらかかったか
channels['CPA'] = channels['広告費'] / channels['CV数']
# CAC(Customer Acquisition Cost: 顧客獲得単価)
# 1人の新規顧客を獲得するのにいくらかかったか
channels['CAC'] = channels['広告費'] / channels['新規顧客数']
収益指標を計算する
売上、粗利、ROI、ROASを計算します。これらの指標で「収益性」を測ります。
# 売上 = CV数 × 平均注文金額
channels['売上'] = channels['CV数'] * AOV
# 粗利 = 売上 × 粗利率
channels['粗利'] = channels['売上'] * gross_margin_rate
# ROI(Return on Investment: 投資収益率)
# (粗利 - 広告費) / 広告費 × 100
# → 100%以上なら黒字、100%未満なら赤字
channels['ROI'] = (channels['粗利'] - channels['広告費']) / channels['広告費'] * 100
# ROAS(Return on Ad Spend: 広告費用対効果)
# 売上 / 広告費 × 100
# → 広告費の何倍の売上が得られたか
channels['ROAS'] = channels['売上'] / channels['広告費'] * 100
LTVベースのROIも計算
初回購入だけでなく、顧客の生涯価値(LTV)で評価します。新規顧客は将来もリピート購入するため、長期的な価値で評価することが重要です。
# LTV(顧客生涯価値)ベースの計算
# 新規顧客は将来もリピート購入するため、長期的な価値で評価
# LTVベースの売上 = 新規顧客数 × LTV
channels['LTV売上'] = channels['新規顧客数'] * LTV
# LTVベースの粗利 = LTV売上 × 粗利率
channels['LTV粗利'] = channels['LTV売上'] * gross_margin_rate
# LTVベースのROI
# → 長期的な視点での投資効率
channels['LTV_ROI'] = (channels['LTV粗利'] - channels['広告費']) / channels['広告費'] * 100
分析結果を確認
# 結果を表示
# iterrows(): DataFrameの各行を順番に処理する
for i, row in channels.iterrows():
print(f"【{row['チャネル']}】")
print(f" 広告費: ¥{row['広告費']/1000000:.0f}M")
print(f" CPC: ¥{row['CPC']:.0f} CVR: {row['CVR']:.2f}%")
print(f" CPA: ¥{row['CPA']:,.0f} CAC: ¥{row['CAC']:,.0f}")
print(f" ROI: {row['ROI']:.0f}% ROAS: {row['ROAS']:.0f}%")
print(f" LTV_ROI: {row['LTV_ROI']:.0f}%")
print()
実行結果の解釈
| チャネル | 広告費 | CAC | ROI | LTV_ROI |
|---|---|---|---|---|
| Google広告 | ¥20M | ¥23,529 | -76% | -49% |
| SNS広告 | ¥15M | ¥18,750 | -71% | -36% |
| インフルエンサー | ¥8M | ¥14,545 | -64% | -18% |
| メール | ¥4M | ¥20,000 | -40% | +50% |
| アフィリエイト | ¥3M | ¥7,895 | -36% | +52% |
※ROIがマイナスの理由:初回購入のみで計算すると赤字になる。
しかしLTV_ROIで見ると、リピート購入を含めてプラスになるチャネルがある。
・メールとアフィリエイトがLTV_ROIでプラス(効率的)
・Google広告は予算の40%を占めるがLTV_ROIが最低(非効率)
・予算配分が効率性と一致していない
次のアクション:
アトリビューション分析で、各チャネルの「真の貢献度」を確認する
📈 4. STEP 2: アトリビューション分析
アトリビューションとは何か
例: ある顧客の購入までの流れ
1. Instagram広告を見る(認知)
2. Google検索でサイトを訪問(検討)
3. メルマガのクーポンで購入(購入)
問題:
「ラストクリック」(最後に接触したメール)だけに貢献を与えると、
最初に認知を作ったInstagramの貢献が無視されてしまう。
解決策:
複数のアトリビューションモデルで評価し、公平に貢献度を配分する。
顧客の接触パターンを分析
実務では、Google Analyticsなどから顧客の接触パターンを取得します。
# 顧客の接触パターン(上位7パターン)
# 実務では、Google Analyticsなどから取得
attribution_data = pd.DataFrame({
'パターン': [
'Google → 購入', # Googleだけで購入
'SNS → 購入', # SNSだけで購入
'Google → SNS → 購入', # Google経由でSNSで購入
'SNS → Google → 購入', # SNS経由でGoogleで購入
'Influencer → Google → 購入', # インフルエンサー経由
'Email → 購入', # メールだけで購入
'Google → Email → 購入' # Google経由でメールで購入
],
'顧客数': [200, 180, 150, 120, 100, 150, 50],
'ラストクリック': ['Google', 'SNS', 'SNS', 'Google', 'Google', 'Email', 'Email']
})
print("【顧客の接触パターン】")
print(attribution_data.to_string(index=False))
ラストクリックモデル
最後に接触したチャネルに100%の貢献を与える、最もシンプルなモデルです。
# ラストクリックモデル
# groupby(): 指定した列でグループ化
# sum(): 各グループの合計を計算
lastclick = attribution_data.groupby('ラストクリック')['顧客数'].sum()
print("【ラストクリックモデル】")
print("最後に接触したチャネルに100%の貢献")
print()
print(lastclick)
# 結果:
# Google: 470人(200+120+100+50)
# SNS: 330人(180+150)
# Email: 200人(150+50)
リニアモデル(均等配分)
接触したすべてのチャネルに均等に貢献を配分します。
# リニアモデル(均等配分)
# 接触したすべてのチャネルに均等に貢献を配分
# 例: 「Google → SNS → 購入」のパターン(150人)
# Google: 75人(50%)、SNS: 75人(50%)
linear_attribution = {
'Google': 200 + 150/2 + 120/2 + 100/2 + 50/2, # 410人
'SNS': 180 + 150/2 + 120/2, # 315人
'Influencer': 100/2, # 50人
'Email': 150 + 50/2 # 175人
}
print("【リニアモデル(均等配分)】")
for channel, contribution in linear_attribution.items():
print(f" {channel}: {contribution:.0f}人")
タイムディケイモデル(時間減衰)
購入に近い接触ほど高い貢献度を与えます。多くのビジネスで推奨されるモデルです。
# タイムディケイモデル(時間減衰)
# 購入に近い接触ほど高い貢献度を与える
# 例: 最後のチャネル70%、最初のチャネル30%
timedecay_attribution = {
'Google': 200 + 150*0.3 + 120*0.7 + 100*0.3 + 50*0.3, # 344人
'SNS': 180 + 150*0.7 + 120*0.3, # 321人
'Influencer': 100*0.4, # 40人(認知のみ)
'Email': 150 + 50*0.7 # 185人
}
print("【タイムディケイモデル】")
for channel, contribution in timedecay_attribution.items():
print(f" {channel}: {contribution:.0f}人")
アトリビューションモデルの比較
| チャネル | ラストクリック | リニア | タイムディケイ |
|---|---|---|---|
| 470人 | 410人 | 344人 | |
| SNS | 330人 | 315人 | 321人 |
| Influencer | 0人 | 50人 | 40人 |
| 200人 | 175人 | 185人 |
注目: Influencerはラストクリックでは0人だが、リニアでは50人の貢献がある。
認知に貢献しているが、ラストクリックでは過小評価されている。
・Google広告はラストクリックでは過大評価されている
・インフルエンサーは認知に貢献しているが、ラストクリックでは0
・メール・SNSはどのモデルでも安定した貢献
推奨:
タイムディケイモデルを採用し、認知〜購入までの貢献を公平に評価する
📈 5. STEP 3: A/Bテストによる統計的検証
A/Bテストとは
それが偶然かもしれないという可能性があります。
A/Bテストの目的:
・2つのパターンを同時にテスト
・ランダムにユーザーを振り分け
・統計的に有意な差があるか検証
今回のテスト:
SNS広告のクリエイティブAとBを比較
テストデータの準備
# A/Bテストのデータ
# Creative A(現行)とCreative B(新案)を比較
# Creative A(現行デザイン)
creative_a_clicks = 800 # クリック数
creative_a_conversions = 90 # コンバージョン数
# Creative B(新デザイン)
creative_b_clicks = 950 # クリック数
creative_b_conversions = 105 # コンバージョン数
# コンバージョン率(CVR)を計算
cvr_a = creative_a_conversions / creative_a_clicks # 11.25%
cvr_b = creative_b_conversions / creative_b_clicks # 11.05%
print("【A/Bテスト: SNS広告クリエイティブ比較】")
print(f"Creative A: CVR = {cvr_a*100:.2f}%")
print(f"Creative B: CVR = {cvr_b*100:.2f}%")
統計的検定を実行
2標本比率検定(Z検定)を使って、AとBに本当に差があるかを検証します。
1. プールした比率を計算(2グループの合算)
2. 標準誤差(SE)を計算(ばらつきの大きさ)
3. Z値を計算(差が標準誤差の何倍か)
4. p値を計算(この差が偶然である確率)
# 2標本比率検定(Z検定)
# 「AとBに本当に差があるか」を統計的に検証
# プールしたコンバージョン率(2グループの合算)
total_conversions = creative_a_conversions + creative_b_conversions
total_clicks = creative_a_clicks + creative_b_clicks
pooled_cvr = total_conversions / total_clicks
# 標準誤差(SE)を計算
# SE = √(p(1-p)(1/n1 + 1/n2))
# np.sqrt(): 平方根を計算
se = np.sqrt(pooled_cvr * (1 - pooled_cvr) *
(1/creative_a_clicks + 1/creative_b_clicks))
# Z値を計算
# Z = (CVR_B - CVR_A) / SE
z_score = (cvr_b - cvr_a) / se
# p値を計算(両側検定)
# stats.norm.cdf(): 正規分布の累積分布関数
p_value = 2 * (1 - stats.norm.cdf(abs(z_score)))
検定結果の解釈
# 結果を表示
print("【統計的検定の結果】")
print(f"CVR差: {(cvr_b - cvr_a)*100:.2f}pp")
print(f"相対改善: {(cvr_b/cvr_a - 1)*100:.1f}%")
print(f"Z値: {z_score:.3f}")
print(f"p値: {p_value:.4f}")
# p値による判断
if p_value < 0.05:
print("✓ 統計的に有意(p < 0.05)")
print(" → BはAより明確に優れている")
else:
print("✗ 統計的に有意ではない(p >= 0.05)")
print(" → 差はランダムな可能性がある")
| 統計用語 | ビジネス言葉での説明 |
|---|---|
| p値 < 0.05 | この差が偶然である確率は5%未満。95%の確率で本当の差 |
| 統計的に有意 | たまたまではなく、実際に効果があると言える |
| Z値 = 2.0 | 平均から標準偏差2つ分離れている(珍しい) |
| 95%信頼区間 | 本当の値が95%の確率でこの範囲にある |
・Creative BはAより CVR が改善
・p値が0.05未満なら統計的に有意
ビジネスインパクト(Bを採用した場合):
・月間追加CV: 約50件
・月間追加売上: 約40万円
・年間追加売上: 約480万円
📈 6. STEP 4: 予算配分の最適化
現状の問題点
・Google広告: 予算40%を占めるが、LTV_ROIは最低(-49%)
・メール: 予算8%しかないが、LTV_ROIは最高(+50%)
問題2: 非効率なチャネルへの過剰投資
・Google広告に2,000万円投資 → 赤字
・この予算を効率的なチャネルに回せば利益改善
原則:
ROIが高いチャネルに予算をシフトする
最適化案を作成
# 最適化後の予算配分
optimized_budget = pd.DataFrame({
'チャネル': ['Google広告', 'SNS広告', 'インフルエンサー', 'メール', 'アフィリエイト'],
'現予算': [20000000, 15000000, 8000000, 4000000, 3000000],
'最適予算': [15000000, 18000000, 7000000, 8000000, 2000000]
})
# 変化額を計算
optimized_budget['変化額'] = optimized_budget['最適予算'] - optimized_budget['現予算']
optimized_budget['変化率'] = (optimized_budget['変化額'] / optimized_budget['現予算'] * 100)
# 総予算は変わらないことを確認
print(f"現予算合計: ¥{optimized_budget['現予算'].sum()/1000000:.0f}M")
print(f"最適予算合計: ¥{optimized_budget['最適予算'].sum()/1000000:.0f}M")
最適化案の比較
| チャネル | 現予算 | 最適予算 | 変化 | 理由 |
|---|---|---|---|---|
| Google広告 | ¥20M | ¥15M | -¥5M | LTV_ROI最低 |
| SNS広告 | ¥15M | ¥18M | +¥3M | クリエイティブ最適化済み |
| インフルエンサー | ¥8M | ¥7M | -¥1M | 効果の高い人に集中 |
| メール | ¥4M | ¥8M | +¥4M | LTV_ROI最高 |
| アフィリエイト | ¥3M | ¥2M | -¥1M | 規模の上限あり |
| 合計 | ¥50M | ¥50M | ±0 | 追加投資なし |
期待される効果
・平均ROI: +15pp改善(-10% → +5%)
・追加粗利: 約20M円/年
・CAC: 17,778円 → 15,000円(-16%)
ポイント:
追加予算なしで、配分を変えるだけで利益が増える!
📈 7. STEP 5: 改善施策の提案
5つの改善施策
・メールマーケティングを倍増(¥4M → ¥8M)
・Google広告を25%削減(¥20M → ¥15M)
・SNS広告を20%増額(¥15M → ¥18M)
期待効果:
・平均ROI +15pp
・追加利益 ¥22.5M/年
・A/Bテストで勝ちクリエイティブを全展開
・SNS広告でCreative B採用
・Google広告でも同様のテスト実施
期待効果:
・CVR +10%
・年間追加売上 ¥18M
・サイト訪問者への再アプローチ
・カート放棄者への自動メール
・予算: ¥3M(Google予算から振替)
期待効果:
・CVR +15%
・ROI 200%+
・ROIの高いインフルエンサーに集中
・マイクロインフルエンサー活用
・成果報酬型に移行
期待効果:
・CAC -20%
・ROI +30pp
・サブスクリプションモデル導入
・ロイヤルティプログラム強化
・クロスセル・アップセル最適化
期待効果:
・リピート率 40% → 55%
・LTV +30%
📊 8. マーケティング会議プレゼンテーション
エグゼクティブサマリー
予算再配分により、追加投資なしで年間約20M円の利益改善が可能。
メール強化(+100%)とGoogle削減(-25%)を推奨。
【現状の課題】
・Google広告: 予算40%だがLTV_ROI最低(-49%)
・メール: 予算8%だがLTV_ROI最高(+50%)
・予算配分が効率性と一致していない
【改善施策】
1. 予算再配分: メール+4M、Google-5M
2. クリエイティブ最適化: A/Bテストで検証済み
3. リターゲティング強化
【期待効果】
・平均ROI: -10% → +5%(+15pp)
・追加粗利: 約20M円/年
・CAC: 17,778円 → 15,000円(-16%)
【Next Steps】
◆ 今週: CMO承認
◆ 来週: 予算再配分実施
◆ 1ヶ月後: 初回レビュー
📝 STEP 52 のまとめ
- ROI・ROAS・CAC計算: チャネル別の投資効率を測定
- LTV視点: 初回購入だけでなく長期価値で評価
- アトリビューション: 複数接触を考慮した貢献度配分
- A/Bテスト: 施策の効果を統計的に証明
- 予算最適化: データに基づく資源配分
ROI、ROAS、CACなど多角的に判断。1つの指標だけで判断しない。
2. LTV視点を持つ
初回購入だけでなく、顧客の生涯価値で評価。
3. アトリビューションを考慮
ラストクリックだけでなく、認知〜購入までの貢献を評価。
4. 統計的に検証
「良さそう」ではなく、A/Bテストで「有意な差」を証明。
5. 継続的に最適化
月次でレビューし、柔軟に予算を調整。
❓ よくある質問
ROAS(広告費用対効果):
ROAS = 売上 / 広告費 × 100%
→ 広告費の何倍の売上が得られたか
ROI(投資収益率):
ROI = (粗利 – 広告費) / 広告費 × 100%
→ 実際にどれだけ利益が出たか
注意:
ROAS 400%でも、粗利率が低ければROIは赤字の可能性あり。
ROIの方がより厳密な指標です。
CAC/LTV比率の目安:
・優秀: CAC < LTV / 3(例: LTV 2万円 → CAC 6,666円以下)
・許容: CAC < LTV / 2(例: LTV 2万円 → CAC 1万円以下)
・要改善: CAC > LTV / 2
・危険: CAC > LTV(獲得するほど赤字)
CACとLTVのバランスがビジネスの持続可能性を決めます。
ラストクリック: シンプル、短い購買サイクル向け
ファーストクリック: 認知獲得を評価したい場合
リニア: 全タッチポイントを平等に評価
タイムディケイ(推奨): 購入に近いほど高評価、多くのビジネスに適用可能
実務では複数モデルを比較し、傾向を把握することが重要です。
有意差が出ない原因:
・サンプルサイズが小さい
・実際に差がない(どちらも同じ効果)
・テスト期間が短い
対処法:
・テスト期間を延長してサンプルを増やす
・より大胆な変更をテストする
・「差がない」という結論も価値ある発見
📝 参考: 完全版コード
このプロジェクトの全コードをまとめたものです。実務で使う際の参考にしてください。
※ スマートフォンの場合は横スクロールできます
# ===================================================
# マーケティングROI分析 - 完全版コード
# ===================================================
import pandas as pd
import numpy as np
from scipy import stats
# ===== 設定 =====
AOV = 8000 # 平均注文金額
gross_margin_rate = 0.6 # 粗利率
LTV = 20000 # 顧客生涯価値
# ===== データ準備 =====
channels = pd.DataFrame({
'チャネル': ['Google広告', 'SNS広告', 'インフルエンサー', 'メール', 'アフィリエイト'],
'広告費': [20000000, 15000000, 8000000, 4000000, 3000000],
'クリック数': [100000, 80000, 50000, 30000, 25000],
'CV数': [1000, 900, 600, 500, 400],
'新規顧客数': [850, 800, 550, 200, 380]
})
# ===== 効率指標 =====
channels['CPC'] = channels['広告費'] / channels['クリック数']
channels['CVR'] = channels['CV数'] / channels['クリック数'] * 100
channels['CPA'] = channels['広告費'] / channels['CV数']
channels['CAC'] = channels['広告費'] / channels['新規顧客数']
# ===== 収益指標 =====
channels['売上'] = channels['CV数'] * AOV
channels['粗利'] = channels['売上'] * gross_margin_rate
channels['ROI'] = (channels['粗利'] - channels['広告費']) / channels['広告費'] * 100
channels['ROAS'] = channels['売上'] / channels['広告費'] * 100
# ===== LTVベース =====
channels['LTV売上'] = channels['新規顧客数'] * LTV
channels['LTV粗利'] = channels['LTV売上'] * gross_margin_rate
channels['LTV_ROI'] = (channels['LTV粗利'] - channels['広告費']) / channels['広告費'] * 100
# ===== A/Bテスト =====
cvr_a = 90 / 800 # Creative A
cvr_b = 105 / 950 # Creative B
pooled_cvr = (90 + 105) / (800 + 950)
se = np.sqrt(pooled_cvr * (1 - pooled_cvr) * (1/800 + 1/950))
z_score = (cvr_b - cvr_a) / se
p_value = 2 * (1 - stats.norm.cdf(abs(z_score)))
print(f"A/Bテスト p値: {p_value:.4f}")
print(f"統計的に有意: {'Yes' if p_value < 0.05 else 'No'}")
学習メモ
ビジネスデータ分析・意思決定 - Step 52