100本ノック / マーケティングサイエンス / マーケティングサイエンス100本ノック

製造業のマーケティングサイエンス入門|KPI・因果・予算最適化をPythonで学ぶ

製造業B2Bマーケティングを「勘」から「意思決定」へ:マーケティングサイエンス入門10本ノック

タイトル・概要

このNotebookは、製造業の営業・マーケティング部門が、展示会、技術セミナー、Web広告などへの投資をデータで判断するための入門編です。全100本を通じて、統計・顧客分析・需要予測・価格・推薦・最適化・経営判断へ段階的に進みます。最初の10本では、その土台となる問い、KPI、モデル、因果、不確実性、シミュレーションを、架空の産業機器メーカーのデータで確認します。

学習上のゴールは、手法を暗記することではありません。「どの施策に、いくら配分し、何を測り、次にどう変えるか」という判断を、再現可能な形で説明できるようになることです。

[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。

はじめに:この記事で扱う製造業の実務課題

架空の産業機器メーカー「工房テック」は、複数チャネルから引合いを獲得しています。しかし、受注まで長く、案件規模にもばらつきがあります。単純なリード数や受注件数だけでは、展示会とWeb広告のどちらを強化すべきか判断できません。

本稿では、マーケティングを「広告の集計」ではなく、限られた経営資源を市場機会へ配分する意思決定として扱います。売上だけでなく粗利、営業負荷、確度、ばらつきを同時に見ます。

現場でよくある状況

  • 部門ごとに「成果」の定義が違う
  • リード数は増えたが、営業が追える案件は増えていない
  • 昨年実績をそのまま次年度予算に使っている
  • 施策後に受注が増えると、施策の効果だと解釈してしまう
  • 平均値だけで計画し、下振れ時の対応を決めていない

なぜこの問題は判断が難しいのか

製造業B2Bでは、施策から受注まで時間差があり、景況、製品力、営業活動も結果に影響します。観測データだけでは反実仮想、すなわち「施策を行わなかった場合」が見えません。また、正しい予測がそのまま正しい行動になるわけでもありません。予測値を、利益・能力・リスクという制約の下で意思決定へ変換する必要があります。

今回扱うノックの全体像

No.テーマ現場で答えたい問い
001マーケティングサイエンスとは何かデータをどう意思決定につなぐか
002なぜ数理が必要か単価と確率が違う施策をどう比べるか
003データ駆動経営共通指標で会議をどう変えるか
004KPI先行指標と結果指標をどうつなぐか
005意思決定とモデル受注確率をどう判断補助に使うか
006相関と因果見せかけの効果をどう避けるか
007最適化予算をどこへ配るか
008不確実性平均だけでなく下振れをどう扱うか
009シミュレーション意思決定の分布をどう可視化するか
010Python環境構築再現可能な分析環境をどう作るか

Python環境の準備

外部データは使用しません。NumPyで乱数を生成し、pandasで集計、matplotlibで可視化します。乱数生成器のseedを固定するため、何度実行しても同じ結果を再現できます。

import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from IPython.display import display

rng = np.random.default_rng(42)
pd.set_option("display.max_columns", 20)
pd.set_option("display.float_format", lambda x: f"{x:,.2f}")
plt.rcParams["figure.figsize"] = (8, 4.5)
plt.rcParams["axes.unicode_minus"] = False

print("Python:", sys.version.split()[0])
print("NumPy:", np.__version__)
print("pandas:", pd.__version__)
print("matplotlib:", matplotlib.__version__)
Python: 3.11.9
NumPy: 1.26.4
pandas: 2.2.2
matplotlib: 3.9.2

架空データの作成

24か月・4チャネルの施策実績を作ります。費用、リード数、商談化率、受注確率、案件単価、粗利率に加えて、市況指数も持たせます。金額単位は万円です。実務ではCRM、広告管理、展示会名簿、基幹システムのIDを接続して同様の分析テーブルを作ります。

months = pd.date_range("2024-01-01", periods=24, freq="MS")
channels = ["展示会", "技術セミナー", "Web広告", "既存顧客紹介"]
base = {
    "展示会": (300, 85, 0.24, 0.22, 850, 0.35),
    "技術セミナー": (130, 48, 0.31, 0.25, 720, 0.37),
    "Web広告": (180, 125, 0.14, 0.16, 520, 0.32),
    "既存顧客紹介": (70, 24, 0.44, 0.38, 980, 0.40),
}
rows = []
for t, month in enumerate(months):
    market = 100 + 8*np.sin(2*np.pi*t/12) + rng.normal(0, 3)
    for ch in channels:
        cost, leads0, opp_rate, win0, price0, margin = base[ch]
        spend = max(20, cost * rng.normal(1, 0.12))
        leads = rng.poisson(leads0 * (spend/cost)**0.65 * market/100)
        opportunities = rng.binomial(leads, opp_rate)
        win_prob = np.clip(win0 + 0.0025*(market-100), 0.05, 0.70)
        orders = rng.binomial(opportunities, win_prob)
        avg_price = max(200, price0 * rng.lognormal(0, 0.10))
        revenue = orders * avg_price
        gross_profit = revenue * margin - spend
        rows.append([month, ch, market, spend, leads, opportunities, orders,
                     avg_price, revenue, margin, gross_profit])

df = pd.DataFrame(rows, columns=["month", "channel", "market_index", "spend",
    "leads", "opportunities", "orders", "avg_price", "revenue", "margin_rate", "gross_profit"])
df["lead_to_opportunity"] = df["opportunities"] / df["leads"].replace(0, np.nan)
df["win_rate"] = df["orders"] / df["opportunities"].replace(0, np.nan)
display(df.head(8))
print(f"行数: {len(df):,}、欠損数: {df.isna().sum().sum():,}")
month channel market_index spend leads opportunities orders avg_price revenue margin_rate gross_profit lead_to_opportunity win_rate
0 2024-01-01 展示会 100.91 262.56 90 16 7 860.94 6,026.55 0.35 1,846.73 0.18 0.44
1 2024-01-01 技術セミナー 100.91 125.07 38 11 5 724.77 3,623.85 0.37 1,215.76 0.29 0.45
2 2024-01-01 Web広告 100.91 204.35 135 19 1 567.75 567.75 0.32 -22.67 0.14 0.05
3 2024-01-01 既存顧客紹介 100.91 69.58 28 17 9 938.91 8,450.19 0.40 3,310.50 0.61 0.53
4 2024-02-01 展示会 102.94 319.16 68 18 5 816.15 4,080.74 0.35 1,109.09 0.26 0.28
5 2024-02-01 技術セミナー 102.94 122.01 45 11 1 661.98 661.98 0.37 122.92 0.24 0.09
6 2024-02-01 Web広告 102.94 162.19 126 21 4 549.03 2,196.10 0.32 540.56 0.17 0.19
7 2024-02-01 既存顧客紹介 102.94 64.41 28 11 5 1,002.16 5,010.80 0.40 1,939.91 0.39 0.45
行数: 96、欠損数: 0

No.001:マーケティングサイエンスとは何か

実務での意味

マーケティングサイエンスは、顧客理解、予測、施策評価、資源配分を定量化し、意思決定を改善する活動です。分析レポートを作ること自体が目的ではなく、選択肢・評価尺度・制約を明示して行動を選ぶことが目的です。

分析・モデル化の考え方

チャネル cc の価値を、売上ではなく投下費用控除後の粗利で比較します。

Contributionc=Revenuec×MarginRatecSpendc\text{Contribution}_c = \text{Revenue}_c\times\text{MarginRate}_c-\text{Spend}_c

Pythonで確認する

channel_summary = df.groupby("channel").agg(
    spend=("spend", "sum"), leads=("leads", "sum"), orders=("orders", "sum"),
    revenue=("revenue", "sum"), gross_profit=("gross_profit", "sum")
)
channel_summary["profit_per_spend"] = channel_summary["gross_profit"] / channel_summary["spend"]
display(channel_summary.sort_values("gross_profit", ascending=False).round(2))
spend leads orders revenue gross_profit profit_per_spend
channel
既存顧客紹介 1,653.50 592 92 87,987.12 33,541.35 20.29
展示会 7,068.09 1996 123 103,732.18 29,238.17 4.14
技術セミナー 3,131.49 1117 87 63,869.47 20,500.22 6.55
Web広告 4,317.58 3045 54 27,153.56 4,371.56 1.01

結果の読み取り

売上、受注、投資効率の順位は必ずしも一致しません。経営が「成長額」を重視するか「資本効率」を重視するかで選択は変わります。分析の第一歩は、唯一の正解を出すことではなく、何を最適化するのかを合意することです。

No.002:なぜ数理がマーケティングに必要か

実務での意味

リード単価だけで施策を選ぶと、高単価・高確度案件を取り逃がします。確率と経済価値を同じ尺度に変換すれば、異なるチャネルを比較できます。

分析・モデル化の考え方

1リードの期待粗利を

EV=(商談化率×受注率×平均単価×粗利率)1リード当たり費用EV=(商談化率\times受注率\times平均単価\times粗利率)-1リード当たり費用

と定義します。期待値は長期平均であり、単月の結果を保証する値ではありません。

Pythonで確認する

economics = df.groupby("channel").agg(
    spend=("spend", "sum"), leads=("leads", "sum"), opportunities=("opportunities", "sum"),
    orders=("orders", "sum"), revenue=("revenue", "sum"), margin_rate=("margin_rate", "mean")
)
economics["opportunity_rate"] = economics["opportunities"] / economics["leads"]
economics["win_rate"] = economics["orders"] / economics["opportunities"]
economics["avg_order_value"] = economics["revenue"] / economics["orders"]
economics["cost_per_lead"] = economics["spend"] / economics["leads"]
economics["expected_value_per_lead"] = (economics["opportunity_rate"] * economics["win_rate"] *
    economics["avg_order_value"] * economics["margin_rate"] - economics["cost_per_lead"])
display(economics[["cost_per_lead", "opportunity_rate", "win_rate", "expected_value_per_lead"]].round(2))
cost_per_lead opportunity_rate win_rate expected_value_per_lead
channel
Web広告 1.42 0.13 0.13 1.44
展示会 3.54 0.25 0.24 14.65
技術セミナー 2.80 0.33 0.24 18.35
既存顧客紹介 2.79 0.44 0.35 56.66

結果の読み取り

安いリードが最も価値の高いリードとは限りません。数理は、確率の連鎖と金額を一つの評価軸に翻訳します。ただし、能力不足で追客できない場合は営業工数も費用に含める必要があります。

No.003:データ駆動経営とは

実務での意味

データ駆動経営とは、数字に自動的に従うことではありません。定義されたデータを共通言語にして、仮説、判断、結果、学びを継続的に更新することです。

分析・モデル化の考え方

月次の経営ビューでは、活動量(費用)、ファネル(リード・商談)、結果(受注・粗利)を同じ粒度で並べます。前年差や目標差を出せる構造が重要です。

Pythonで確認する

monthly = df.groupby("month").agg(spend=("spend", "sum"), leads=("leads", "sum"),
    opportunities=("opportunities", "sum"), orders=("orders", "sum"),
    gross_profit=("gross_profit", "sum")).reset_index()
monthly["rolling_3m_profit"] = monthly["gross_profit"].rolling(3).mean()
display(monthly.tail(6).round(1))

fig, ax = plt.subplots()
ax.plot(monthly["month"], monthly["gross_profit"], marker="o", alpha=.55, label="Monthly")
ax.plot(monthly["month"], monthly["rolling_3m_profit"], linewidth=2.5, label="3-month average")
ax.set_title("Monthly contribution profit and trend")
ax.set_xlabel("Month"); ax.set_ylabel("Contribution profit (10k JPY)")
ax.grid(True, alpha=.3); ax.legend(); fig.tight_layout(); plt.show()
month spend leads opportunities orders gross_profit rolling_3m_profit
18 2025-07-01 653.50 279 56 16 3,956.50 3,571.70
19 2025-08-01 630.40 256 50 14 3,758.90 3,500.80
20 2025-09-01 684.80 244 60 8 1,835.10 3,183.50
21 2025-10-01 591.80 250 62 15 3,708.40 3,100.80
22 2025-11-01 652.70 250 55 9 1,389.40 2,310.90
23 2025-12-01 633.60 277 69 17 3,598.60 2,898.80

png

結果の読み取り

単月値には大型案件による振れがあります。3か月移動平均を併記すると傾向を読みやすくなります。会議では「増減した理由」と「次の検証行動」を記録し、ダッシュボード閲覧だけで終わらせないことが重要です。

No.004:KPIの考え方

実務での意味

売上は重要ですが、結果が確定する頃には打ち手が遅れます。KPIは、現場が週次・月次で動かせ、将来の結果につながる指標として設計します。

分析・モデル化の考え方

ファネルは次の乗法分解で表せます。

受注数=リード数×商談化率×受注率受注数=リード数\times商談化率\times受注率

粗利を最終KGI、商談数と受注率をKPI、メール送付数のような作業量をPIとして区別します。

Pythonで確認する

kpi = df.groupby("channel").agg(leads=("leads", "sum"), opportunities=("opportunities", "sum"),
    orders=("orders", "sum"), gross_profit=("gross_profit", "sum"))
kpi["opportunity_rate"] = kpi["opportunities"] / kpi["leads"]
kpi["win_rate"] = kpi["orders"] / kpi["opportunities"]
kpi["orders_reconstructed"] = kpi["leads"] * kpi["opportunity_rate"] * kpi["win_rate"]
display(kpi.round(3))

fig, ax = plt.subplots()
x = np.arange(len(kpi)); width = .35
ax.bar(x-width/2, kpi["opportunity_rate"], width, label="Lead to opportunity")
ax.bar(x+width/2, kpi["win_rate"], width, label="Win rate")
ax.set_title("Conversion KPIs by channel"); ax.set_xlabel("Channel"); ax.set_ylabel("Rate")
ax.set_xticks(x, kpi.index, rotation=15); ax.grid(True, axis="y", alpha=.3); ax.legend()
fig.tight_layout(); plt.show()
leads opportunities orders gross_profit opportunity_rate win_rate orders_reconstructed
channel
Web広告 3045 405 54 4,371.56 0.13 0.13 54.00
展示会 1996 508 123 29,238.17 0.26 0.24 123.00
技術セミナー 1117 367 87 20,500.22 0.33 0.24 87.00
既存顧客紹介 592 262 92 33,541.35 0.44 0.35 92.00
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 24195 (\N{CJK UNIFIED IDEOGRAPH-5E83}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 21578 (\N{CJK UNIFIED IDEOGRAPH-544A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 23637 (\N{CJK UNIFIED IDEOGRAPH-5C55}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 31034 (\N{CJK UNIFIED IDEOGRAPH-793A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 20250 (\N{CJK UNIFIED IDEOGRAPH-4F1A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 25216 (\N{CJK UNIFIED IDEOGRAPH-6280}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 34899 (\N{CJK UNIFIED IDEOGRAPH-8853}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 12475 (\N{KATAKANA LETTER SE}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 12511 (\N{KATAKANA LETTER MI}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 12490 (\N{KATAKANA LETTER NA}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 12540 (\N{KATAKANA-HIRAGANA PROLONGED SOUND MARK}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 26082 (\N{CJK UNIFIED IDEOGRAPH-65E2}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 23384 (\N{CJK UNIFIED IDEOGRAPH-5B58}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 39015 (\N{CJK UNIFIED IDEOGRAPH-9867}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 32057 (\N{CJK UNIFIED IDEOGRAPH-7D39}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1667793017.py:14: UserWarning: Glyph 20171 (\N{CJK UNIFIED IDEOGRAPH-4ECB}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 24195 (\N{CJK UNIFIED IDEOGRAPH-5E83}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 21578 (\N{CJK UNIFIED IDEOGRAPH-544A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23637 (\N{CJK UNIFIED IDEOGRAPH-5C55}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 31034 (\N{CJK UNIFIED IDEOGRAPH-793A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20250 (\N{CJK UNIFIED IDEOGRAPH-4F1A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 25216 (\N{CJK UNIFIED IDEOGRAPH-6280}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 34899 (\N{CJK UNIFIED IDEOGRAPH-8853}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12475 (\N{KATAKANA LETTER SE}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12511 (\N{KATAKANA LETTER MI}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12490 (\N{KATAKANA LETTER NA}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12540 (\N{KATAKANA-HIRAGANA PROLONGED SOUND MARK}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 26082 (\N{CJK UNIFIED IDEOGRAPH-65E2}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23384 (\N{CJK UNIFIED IDEOGRAPH-5B58}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 39015 (\N{CJK UNIFIED IDEOGRAPH-9867}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 32057 (\N{CJK UNIFIED IDEOGRAPH-7D39}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20171 (\N{CJK UNIFIED IDEOGRAPH-4ECB}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)


png

結果の読み取り

リード数が多くても、商談化率が低ければ営業負荷だけが増えます。KPIは部門を責める指標ではなく、ボトルネックを特定する計器です。定義、責任者、更新頻度、打ち手をセットで管理します。

No.005:意思決定とモデル

実務での意味

モデルは現実の縮約です。完全な再現ではなく、ある判断に必要な要素を残します。ここでは、案件を追客するかを受注確率と期待粗利で支援します。

分析・モデル化の考え方

確率 pp、受注粗利 GG、追客費用 CC に対して

EV=pGCEV=pG-C

が正なら追客候補です。ただし、戦略顧客や学習価値など、数値化しにくい要素は最終判断で補います。

Pythonで確認する

deals = pd.DataFrame({
    "deal": [f"D-{i:03d}" for i in range(1, 9)],
    "win_probability": [0.12, .25, .32, .45, .55, .62, .70, .82],
    "gross_profit_if_won": [600, 420, 900, 300, 520, 750, 280, 450],
    "follow_up_cost": [50, 45, 85, 70, 65, 90, 55, 80]
})
deals["expected_net_value"] = deals["win_probability"]*deals["gross_profit_if_won"]-deals["follow_up_cost"]
deals["model_recommendation"] = np.where(deals["expected_net_value"] > 0, "Prioritize", "Review")
display(deals.sort_values("expected_net_value", ascending=False).round(1))
deal win_probability gross_profit_if_won follow_up_cost expected_net_value model_recommendation
5 D-006 0.60 750 90 375.00 Prioritize
7 D-008 0.80 450 80 289.00 Prioritize
4 D-005 0.60 520 65 221.00 Prioritize
2 D-003 0.30 900 85 203.00 Prioritize
6 D-007 0.70 280 55 141.00 Prioritize
3 D-004 0.40 300 70 65.00 Prioritize
1 D-002 0.20 420 45 60.00 Prioritize
0 D-001 0.10 600 50 22.00 Prioritize

結果の読み取り

受注確率が低くても案件粗利が大きければ追客価値を持ちます。逆に確度だけの順位付けは経済価値を見落とします。確率の校正、費用の範囲、例外承認ルールを定期的に監査する必要があります。

No.006:相関と因果

実務での意味

広告費と売上が同時に増えても、広告が売上を増やしたとは限りません。好況期に広告費を増やしただけかもしれず、市況が交絡因子になります。

分析・モデル化の考え方

相関は変数が一緒に動く強さ、因果は介入によって結果が変わる関係です。因果効果には、無作為化比較、差の差、回帰不連続などの識別設計が必要です。ここでは市況を除去した残差相関も確認しますが、これだけで因果は証明できません。

Pythonで確認する

web = df[df["channel"] == "Web広告"].copy()
raw_corr = web["spend"].corr(web["revenue"])
spend_coef = np.polyfit(web["market_index"], web["spend"], 1)
revenue_coef = np.polyfit(web["market_index"], web["revenue"], 1)
web["spend_residual"] = web["spend"] - np.polyval(spend_coef, web["market_index"])
web["revenue_residual"] = web["revenue"] - np.polyval(revenue_coef, web["market_index"])
adjusted_corr = web["spend_residual"].corr(web["revenue_residual"])
print(f"単純相関: {raw_corr:.3f}")
print(f"市況の線形影響を除いた残差相関: {adjusted_corr:.3f}")

fig, ax = plt.subplots()
sc = ax.scatter(web["spend"], web["revenue"], c=web["market_index"], cmap="viridis", s=65)
ax.set_title("Web spend and revenue colored by market conditions")
ax.set_xlabel("Spend (10k JPY)"); ax.set_ylabel("Revenue (10k JPY)")
ax.grid(True, alpha=.3); fig.colorbar(sc, ax=ax, label="Market index")
fig.tight_layout(); plt.show()
単純相関: -0.040
市況の線形影響を除いた残差相関: 0.096


png

結果の読み取り

調整前後で相関が変わるなら、市況を無視した解釈が危険だと分かります。ただし、未観測の営業力や季節性も残り得ます。次の施策では、地域や顧客群をランダムに分ける実験設計を検討します。

No.007:最適化の考え方

実務での意味

各施策の効率が分かっても、予算上限や最低出稿、営業能力があるため配分は自明ではありません。最適化は、目的と制約を明示して候補を探索します。

分析・モデル化の考え方

チャネル別予算 xcx_c に対し、収穫逓減を含む期待粗利 fc(xc)f_c(x_c) を最大化します。

maxxcfc(xc)s.t.cxcB,  LcxcUc\max_x \sum_c f_c(x_c)\quad \text{s.t.}\quad \sum_c x_c\leq B,\;L_c\leq x_c\leq U_c

入門として50万円刻みの全組合せを列挙します。

Pythonで確認する

budget = 800
grid = np.arange(50, 501, 50)
response = {"展示会": (12.0, 240), "技術セミナー": (10.5, 150),
            "Web広告": (8.5, 180), "既存顧客紹介": (14.0, 90)}
plans = []
for a in grid:
    for b in grid:
        for c in grid:
            for d in grid:
                alloc = np.array([a, b, c, d])
                if alloc.sum() <= budget:
                    expected = sum(scale*np.sqrt(x) - x*0.15 for x, (scale, _) in zip(alloc, response.values()))
                    plans.append([*alloc, expected])
plans = pd.DataFrame(plans, columns=[*channels, "expected_profit"])
best = plans.nlargest(5, "expected_profit")
display(best.round(1))

fig, ax = plt.subplots()
ax.bar(channels, best.iloc[0][channels])
ax.set_title("Optimized marketing budget allocation")
ax.set_xlabel("Channel"); ax.set_ylabel("Budget (10k JPY)")
ax.grid(True, axis="y", alpha=.3); ax.tick_params(axis="x", rotation=15)
fig.tight_layout(); plt.show()
展示会 技術セミナー Web広告 既存顧客紹介 expected_profit
1373 250 150 100 300 525.80
1208 200 200 100 300 525.70
1174 200 150 100 350 525.20
1180 200 150 150 300 524.90
1399 250 200 100 250 524.60
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 23637 (\N{CJK UNIFIED IDEOGRAPH-5C55}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 31034 (\N{CJK UNIFIED IDEOGRAPH-793A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 20250 (\N{CJK UNIFIED IDEOGRAPH-4F1A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 25216 (\N{CJK UNIFIED IDEOGRAPH-6280}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 34899 (\N{CJK UNIFIED IDEOGRAPH-8853}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 12475 (\N{KATAKANA LETTER SE}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 12511 (\N{KATAKANA LETTER MI}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 12490 (\N{KATAKANA LETTER NA}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 12540 (\N{KATAKANA-HIRAGANA PROLONGED SOUND MARK}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 24195 (\N{CJK UNIFIED IDEOGRAPH-5E83}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 21578 (\N{CJK UNIFIED IDEOGRAPH-544A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 26082 (\N{CJK UNIFIED IDEOGRAPH-65E2}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 23384 (\N{CJK UNIFIED IDEOGRAPH-5B58}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 39015 (\N{CJK UNIFIED IDEOGRAPH-9867}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 32057 (\N{CJK UNIFIED IDEOGRAPH-7D39}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/3625590457.py:23: UserWarning: Glyph 20171 (\N{CJK UNIFIED IDEOGRAPH-4ECB}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23637 (\N{CJK UNIFIED IDEOGRAPH-5C55}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 31034 (\N{CJK UNIFIED IDEOGRAPH-793A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20250 (\N{CJK UNIFIED IDEOGRAPH-4F1A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 25216 (\N{CJK UNIFIED IDEOGRAPH-6280}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 34899 (\N{CJK UNIFIED IDEOGRAPH-8853}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12475 (\N{KATAKANA LETTER SE}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12511 (\N{KATAKANA LETTER MI}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12490 (\N{KATAKANA LETTER NA}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12540 (\N{KATAKANA-HIRAGANA PROLONGED SOUND MARK}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 24195 (\N{CJK UNIFIED IDEOGRAPH-5E83}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 21578 (\N{CJK UNIFIED IDEOGRAPH-544A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 26082 (\N{CJK UNIFIED IDEOGRAPH-65E2}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23384 (\N{CJK UNIFIED IDEOGRAPH-5B58}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 39015 (\N{CJK UNIFIED IDEOGRAPH-9867}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 32057 (\N{CJK UNIFIED IDEOGRAPH-7D39}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20171 (\N{CJK UNIFIED IDEOGRAPH-4ECB}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)


png

結果の読み取り

最適解は、全額を過去最高効率の施策へ寄せず、収穫逓減を考慮して分散します。実務では営業が処理できる商談数、ブランド維持、契約済み枠なども制約に加えます。係数が推定値である以上、解を絶対視せず感度分析を行います。

No.008:不確実性との向き合い方

実務での意味

平均受注率が同じでも、観測件数が10件と1,000件では信頼度が違います。点推定だけで予算を決めると、データの少ない施策を過大評価する危険があります。

分析・モデル化の考え方

受注をベルヌーイ試行とみなし、標準誤差を

SE(p^)=p^(1p^)nSE(\hat p)=\sqrt{\frac{\hat p(1-\hat p)}{n}}

で近似します。95%区間は p^±1.96SE\hat p\pm1.96SE とします(小標本ではWilson区間等が適切です)。

Pythonで確認する

uncertainty = df.groupby("channel").agg(wins=("orders", "sum"), trials=("opportunities", "sum"))
uncertainty["rate"] = uncertainty["wins"] / uncertainty["trials"]
uncertainty["se"] = np.sqrt(uncertainty["rate"]*(1-uncertainty["rate"])/uncertainty["trials"])
uncertainty["lower95"] = (uncertainty["rate"]-1.96*uncertainty["se"]).clip(0, 1)
uncertainty["upper95"] = (uncertainty["rate"]+1.96*uncertainty["se"]).clip(0, 1)
display(uncertainty.round(3))

fig, ax = plt.subplots()
err = np.vstack([uncertainty["rate"]-uncertainty["lower95"], uncertainty["upper95"]-uncertainty["rate"]])
ax.errorbar(uncertainty.index, uncertainty["rate"], yerr=err, fmt="o", capsize=5)
ax.set_title("Win rate with approximate 95% intervals")
ax.set_xlabel("Channel"); ax.set_ylabel("Win rate")
ax.grid(True, alpha=.3); ax.tick_params(axis="x", rotation=15)
fig.tight_layout(); plt.show()
wins trials rate se lower95 upper95
channel
Web広告 54 405 0.13 0.02 0.10 0.17
展示会 123 508 0.24 0.02 0.20 0.28
技術セミナー 87 367 0.24 0.02 0.19 0.28
既存顧客紹介 92 262 0.35 0.03 0.29 0.41
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 24195 (\N{CJK UNIFIED IDEOGRAPH-5E83}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 21578 (\N{CJK UNIFIED IDEOGRAPH-544A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 23637 (\N{CJK UNIFIED IDEOGRAPH-5C55}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 31034 (\N{CJK UNIFIED IDEOGRAPH-793A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 20250 (\N{CJK UNIFIED IDEOGRAPH-4F1A}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 25216 (\N{CJK UNIFIED IDEOGRAPH-6280}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 34899 (\N{CJK UNIFIED IDEOGRAPH-8853}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 12475 (\N{KATAKANA LETTER SE}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 12511 (\N{KATAKANA LETTER MI}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 12490 (\N{KATAKANA LETTER NA}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 12540 (\N{KATAKANA-HIRAGANA PROLONGED SOUND MARK}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 26082 (\N{CJK UNIFIED IDEOGRAPH-65E2}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 23384 (\N{CJK UNIFIED IDEOGRAPH-5B58}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 39015 (\N{CJK UNIFIED IDEOGRAPH-9867}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 32057 (\N{CJK UNIFIED IDEOGRAPH-7D39}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/ipykernel_1522/1893485025.py:14: UserWarning: Glyph 20171 (\N{CJK UNIFIED IDEOGRAPH-4ECB}) missing from font(s) DejaVu Sans.
  fig.tight_layout(); plt.show()
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 24195 (\N{CJK UNIFIED IDEOGRAPH-5E83}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 21578 (\N{CJK UNIFIED IDEOGRAPH-544A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23637 (\N{CJK UNIFIED IDEOGRAPH-5C55}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 31034 (\N{CJK UNIFIED IDEOGRAPH-793A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20250 (\N{CJK UNIFIED IDEOGRAPH-4F1A}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 25216 (\N{CJK UNIFIED IDEOGRAPH-6280}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 34899 (\N{CJK UNIFIED IDEOGRAPH-8853}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12475 (\N{KATAKANA LETTER SE}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12511 (\N{KATAKANA LETTER MI}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12490 (\N{KATAKANA LETTER NA}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 12540 (\N{KATAKANA-HIRAGANA PROLONGED SOUND MARK}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 26082 (\N{CJK UNIFIED IDEOGRAPH-65E2}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23384 (\N{CJK UNIFIED IDEOGRAPH-5B58}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 39015 (\N{CJK UNIFIED IDEOGRAPH-9867}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 23458 (\N{CJK UNIFIED IDEOGRAPH-5BA2}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 32057 (\N{CJK UNIFIED IDEOGRAPH-7D39}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)
/Users/hiroshi/anaconda3/lib/python3.11/site-packages/IPython/core/pylabtools.py:170: UserWarning: Glyph 20171 (\N{CJK UNIFIED IDEOGRAPH-4ECB}) missing from font(s) DejaVu Sans.
  fig.canvas.print_figure(bytes_io, **kw)


png

結果の読み取り

区間が重なるチャネルの小さな順位差を確定的に語るべきではありません。新施策には学習予算を設け、データ蓄積後に再配分する運用が合理的です。リスク許容度に応じて、期待値より区間下限を重視する判断もあります。

No.009:シミュレーションの役割

実務での意味

事業計画には「売上はいくらか」だけでなく、「赤字確率は何%か」「悪い場合にどこまで下がるか」が必要です。シミュレーションは複数の不確実性を組み合わせ、結果を分布で示します。

分析・モデル化の考え方

商談数をポアソン分布、受注数を二項分布、案件単価を対数正規分布で表すモンテカルロ法を使います。分布仮定は過去データと業務知識で検証します。

Pythonで確認する

sim_rng = np.random.default_rng(42)
n_sim = 10_000
sim_opportunities = sim_rng.poisson(55, n_sim)
sim_orders = sim_rng.binomial(sim_opportunities, 0.24)
sim_prices = sim_rng.lognormal(np.log(720), 0.25, n_sim)
sim_profit = sim_orders * sim_prices * 0.36 - 650
q = np.quantile(sim_profit, [0.05, 0.50, 0.95])
print(f"5%点: {q[0]:,.0f} 万円 / 中央値: {q[1]:,.0f} 万円 / 95%点: {q[2]:,.0f} 万円")
print(f"赤字確率: {(sim_profit < 0).mean():.2%}")

fig, ax = plt.subplots()
ax.hist(sim_profit, bins=45, color="steelblue", alpha=.8)
for value, label in zip(q, ["5%", "Median", "95%"]):
    ax.axvline(value, linestyle="--", label=label)
ax.set_title("Monte Carlo distribution of contribution profit")
ax.set_xlabel("Contribution profit (10k JPY)"); ax.set_ylabel("Frequency")
ax.grid(True, alpha=.3); ax.legend(); fig.tight_layout(); plt.show()
5%点: 1,044 万円 / 中央値: 2,683 万円 / 95%点: 5,404 万円
赤字確率: 0.04%


png

結果の読み取り

中央値だけでなく5%点と赤字確率を示すことで、予備費や撤退条件を先に決められます。シミュレーションは未来を当てる装置ではなく、仮定が成立した場合のリスクを可視化する装置です。

No.010:Python環境構築

実務での意味

分析が担当者のPCでしか動かない状態では、意思決定プロセスとして定着しません。環境、依存ライブラリ、乱数、データ定義、実行順を記録して再現可能にします。

分析・モデル化の考え方

Notebookは探索と説明に向きます。一方、本番運用ではデータ検証、関数化、テスト、バージョン管理、定期実行が必要です。セルの上から順に再実行できることを最低条件にします。

Pythonで確認する

environment = pd.DataFrame({
    "item": ["Python", "NumPy", "pandas", "matplotlib", "random seed", "external data"],
    "value": [sys.version.split()[0], np.__version__, pd.__version__, matplotlib.__version__, "42", "None"]
})
display(environment)
assert len(df) == 24 * 4
assert df[["spend", "leads", "orders", "revenue"]].notna().all().all()
assert (df[["spend", "leads", "orders", "revenue"]] >= 0).all().all()
print("データ品質チェック: OK")
item value
0 Python 3.11.9
1 NumPy 1.26.4
2 pandas 2.2.2
3 matplotlib 3.9.2
4 random seed 42
5 external data None
データ品質チェック: OK

結果の読み取り

バージョンとseedが明示され、品質チェックが通りました。実務では依存関係をロックし、元データの更新日時、列定義、個人情報の取扱い、承認履歴まで記録します。

対象ノックを通して見える実務上の示唆

10本に共通するのは、データ量より意思決定の設計が先だという点です。KGIからファネルKPIへ分解し、確率と金額を期待価値へ変換し、交絡を疑い、制約下で配分し、不確実性を幅として報告します。これにより、会議の論点は「どの数字が正しいか」から「どの仮定で、どの行動を選ぶか」へ移ります。

実務導入する場合に必要なこと

  1. 経営課題と意思決定頻度を定義する
  2. 顧客・案件・施策・受注を共通IDで接続する
  3. KPIの計算式、責任者、更新頻度をデータ辞書にする
  4. 施策前に対照群、期間、成功基準、撤退条件を決める
  5. モデル精度だけでなく、粗利・工数・リスクで評価する
  6. 月次で仮定と結果を見直し、小さく再配分する

まとめ

マーケティングサイエンスは、複雑な手法を使うことではなく、問いを測定可能にし、比較可能な選択肢を作り、結果から学ぶ仕組みです。本Notebookでは、製造業B2Bの架空データを通じて、集計からKPI、モデル、因果、最適化、不確実性、シミュレーションまでを一つの意思決定の流れとして確認しました。

法人向けのご相談

数理工房では、製造業におけるマーケティングKPI設計、顧客・案件データ基盤、需要予測、施策効果検証、予算配分最適化について、課題整理から分析実装・人材育成まで支援します。データが十分に整っていない段階でも、意思決定と収集設計の整理からご相談いただけます。

📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。