Mímisbrunnr知恵の泉

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

🎓 レベル:標準 | 重要度:B(標準)

📎 関連:価格最適化(利益最大化) | 前提:需要曲線と価格弾力性

要点(BLUF)

1. 価格差別・動的価格:単一価格は需要を取りこぼす

価格最適化(利益最大化) では、ひとつの市場にひとつの最適価格 PP^{*} を求めました。しかし顧客の支払意思額(WTP)はばらばらです。単一価格では、PP^{*} より高く払ってもよかった顧客から取りこぼし(消費者余剰の放置)、PP^{*} では高すぎて買わなかった顧客も取りこぼし(販売機会の放置)ています。需要曲線の下・限界費用の上に残るこの面積を取りにいくのが**価格差別(price discrimination)**です。慣習的に3つの級に分けます。

flowchart TD
  S["単一の静的価格 P*"] --> L["需要曲線の下の三角形(消費者余剰・取りこぼし需要)を捨てる"]
  L --> D{"どう差をつける?"}
  D -->|"顧客ごとにWTPで"| D1["第1級:各単位をWTPで(理論的な上限)"]
  D -->|"数量・バージョンで自己選択"| D2["第2級:数量割引・松竹梅メニュー"]
  D -->|"識別できるセグメント・時間で"| D3["第3級:学割・地域別・時間別(動的価格)"]

数値例(第3級)。限界費用は両セグメント共通で c=10c=10。2つのセグメントが線形需要を持つとします。

S1(高WTP・非弾力的): Q1=100P,S2(低WTP・弾力的): Q2=1202P\text{S1(高WTP・非弾力的):}\ Q_1 = 100 - P,\qquad \text{S2(低WTP・弾力的):}\ Q_2 = 120 - 2P

各セグメントは独立した市場なので、価格最適化(利益最大化) の線形需要の閉形式 P=a+bc2bP^{*}=\dfrac{a+bc}{2b} をそのまま当てはめられます。

P1=100+11021=55,P2=120+21022=35P_1^{*} = \frac{100 + 1\cdot 10}{2\cdot 1} = 55,\qquad P_2^{*} = \frac{120 + 2\cdot 10}{2\cdot 2} = 35

利益はそれぞれ π1=(5510)45=2025\pi_1=(55-10)\cdot 45 = 2025π2=(3510)50=1250\pi_2=(35-10)\cdot 50 = 1250、合計 3,275。いっぽう、両セグメントにひとつの価格しかつけられない場合(裁定が防げず、識別もできない場合)、合算需要は P60P\le 60Q=Q1+Q2=2203PQ=Q_1+Q_2=220-3P。利益 π(P)=(P10)(2203P)\pi(P)=(P-10)(220-3P) を最大化すると P=250641.7P=\tfrac{250}{6}\approx 41.7、利益 3,008\approx 3{,}008 です。差し引き 約267 が価格差別の取り分になります。

import numpy as np
from scipy.optimize import minimize_scalar

c = 10.0  # 限界費用(両セグメント共通)

# 2セグメントの線形需要
def Q1(P): return np.maximum(100 - P, 0)      # 高WTP・非弾力的
def Q2(P): return np.maximum(120 - 2 * P, 0)  # 低WTP・弾力的

def profit_seg(P, Q): return (P - c) * Q(P)

# (A) セグメント別に最適価格を求める(第3級価格差別)
r1 = minimize_scalar(lambda P: -profit_seg(P, Q1), bounds=(c, 100), method="bounded")
r2 = minimize_scalar(lambda P: -profit_seg(P, Q2), bounds=(c, 60), method="bounded")
P1, P2 = r1.x, r2.x
pi1, pi2 = profit_seg(P1, Q1), profit_seg(P2, Q2)
pi_discrim = pi1 + pi2

# (B) 単一価格:両セグメント合算需要 Q=Q1+Q2 に1つの価格をつける
def Q_total(P): return Q1(P) + Q2(P)
def profit_single(P): return (P - c) * Q_total(P)
rs = minimize_scalar(lambda P: -profit_single(P), bounds=(c, 100), method="bounded")
P_single = rs.x
pi_single = profit_single(P_single)

print("=== 価格差別 vs 単一価格(c=10)===")
print("セグメント別最適:")
print(f"  S1(Q=100-P) : P1* = {P1:.1f}, Q1 = {Q1(P1):.0f}, pi1 = {pi1:,.0f}")
print(f"  S2(Q=120-2P): P2* = {P2:.1f}, Q2 = {Q2(P2):.0f}, pi2 = {pi2:,.0f}")
print(f"  価格差別の合計利益 = {pi_discrim:,.0f}")
print("単一価格(合算需要 Q=220-3P):")
print(f"  P* = {P_single:.2f}, Q = {Q_total(P_single):.0f}, pi = {pi_single:,.2f}")
print(f"価格差別の利得 = {pi_discrim - pi_single:+,.2f}")
print("→ セグメント別に値付けする方が利益が大きい(取りこぼし需要を拾える)。")

出力:

=== 価格差別 vs 単一価格(c=10)===
セグメント別最適:
  S1(Q=100-P) : P1* = 55.0, Q1 = 45, pi1 = 2,025
  S2(Q=120-2P): P2* = 35.0, Q2 = 50, pi2 = 1,250
  価格差別の合計利益 = 3,275
単一価格(合算需要 Q=220-3P):
  P* = 41.67, Q = 95, pi = 3,008.33
価格差別の利得 = +266.67
→ セグメント別に値付けする方が利益が大きい(取りこぼし需要を拾える)。

出力の意味:非弾力的な S1 には高め(55)、弾力的な S2 には安め(35)と別々の価格をつけると、合計利益は 3,275。これは単一価格(最適 P41.7P\approx41.7)の 3,008.33266.67(約267)上回ります。直感は明快です——単一価格 41.7 は、S1 にとっては安すぎて余剰を取りこぼし、S2 にとってはやや高い。セグメントを分けると、各市場で 03-02 のラーナー最適をそれぞれ達成できるので、取りこぼしが減ります。弾力性の違い(S1 は鈍く、S2 は敏感)が大きいほど、価格差別の取り分は大きくなります。これは 03-02 の単一最適価格を、市場を分割して各部分に適用し直したものに他なりません。

利益の差を図で確かめます。左に2セグメントの需要曲線と各最適価格、右に「単一価格の利益曲線」と「価格差別の合計利益(水平線)」を重ねます。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

c = 10.0
# セグメント別最適(線形需要の閉形式 P*=(a+bc)/(2b))
P1s = (100 + 1 * c) / (2 * 1)              # = 55
P2s = (120 + 2 * c) / (2 * 2)              # = 35
pi_discrim = (P1s - c) * (100 - P1s) + (P2s - c) * (120 - 2 * P2s)  # = 3275
P_single = 250 / 6                          # 単一価格の最適(約41.67)

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

# 左:2セグメントの需要曲線(縦軸=価格)と各最適価格
Pq = np.linspace(10, 100, 400)
ax[0].plot(np.maximum(100 - Pq, 0), Pq, color="C0", lw=2, label="S1: Q=100-P(非弾力的)")
ax[0].plot(np.maximum(120 - 2 * Pq, 0), Pq, color="C1", lw=2, label="S2: Q=120-2P(弾力的)")
ax[0].axhline(P1s, color="C0", ls=":", lw=1.2)
ax[0].axhline(P2s, color="C1", ls=":", lw=1.2)
ax[0].set_title("セグメント別の需要と最適価格(P1*=55, P2*=35)")
ax[0].set_xlabel("需要量 Q")
ax[0].set_ylabel("価格 P")
ax[0].legend(loc="upper right", fontsize=9)

# 右:単一価格の利益曲線 vs 価格差別ベンチマーク(水平線)
P = np.linspace(10, 70, 400)
def Q_total(P): return np.maximum(100 - P, 0) + np.maximum(120 - 2 * P, 0)
profit_single = (P - c) * Q_total(P)
ax[1].plot(P, profit_single, color="C3", lw=2, label="単一価格の利益")
ax[1].axhline(pi_discrim, color="C2", ls="--", lw=1.5, label=f"価格差別の合計 {pi_discrim:.0f}")
ax[1].axvline(P_single, color="C3", ls=":", lw=1.2)
ax[1].set_ylim(0, 3600)
ax[1].set_title("単一価格の利益 < 価格差別")
ax[1].set_xlabel("単一価格 P")
ax[1].set_ylabel("利益")
ax[1].legend(loc="lower center", fontsize=9)

fig.tight_layout()
plt.show()

右パネルで、単一価格の利益カーブは P41.7P\approx41.7 で頂点(3,008)に達しますが、それでも価格差別の合計(3,275、緑の破線)には届きません。この上下の隙間が、単一価格が捨てている取りこぼしです。

2. バンドル:WTPの異質性をならす

バンドル(bundling)は複数の財をまとめて売る戦略です。なぜ効くのか——鍵は支払意思額(WTP)のばらつきを平均してならすことにあります。Adams–Yellen(1976)の古典的な結果は、消費者間で2財の WTP が負相関のとき、バンドルは個別販売より利益が増えるというものです。バンドル WTP は2財の WTP の和なので、その分散は

Var(WA+WB)=Var(WA)+Var(WB)+2Cov(WA,WB)\mathrm{Var}(W_A + W_B) = \mathrm{Var}(W_A) + \mathrm{Var}(W_B) + 2\,\mathrm{Cov}(W_A, W_B)

共分散 Cov(WA,WB)\mathrm{Cov}(W_A,W_B)なら、和の分散は縮みます。WTP がそろう(ばらつきが小さい)ほど、ひとつのバンドル価格を多くの顧客が受け入れるので、取りこぼしが減るわけです。

数値例。限界費用0、2消費者×2財(A, B)。WTP を表で与えます。

財A財B
消費者182
消費者228

消費者1は A を高く・B を低く、消費者2はその逆——WTP が完全な負相関です。個別販売だと、財Aは「価格8で1人(売上8)」か「価格2で2人(売上4)」の二択で、最適は 8。財Bも同様に 8個別合計=16。いっぽう純バンドルでは、各消費者のバンドル WTP は 8+2=108+2=102+8=102+8=10 とそろうので、価格10で2人とも購入=売上20。バンドルが個別を上回ります。

import numpy as np

# 2消費者 × 2財 の支払意思額(WTP)行列。限界費用は 0。
# 行=消費者, 列=財(A,B)
WTP_neg = np.array([[8.0, 2.0],     # 消費者1:A を高く、B を低く評価
                    [2.0, 8.0]])    # 消費者2:その逆(WTP が負相関)

def best_individual(WTP):
    """財ごとに、各消費者のWTPを候補価格として総当たりし、売上を最大化"""
    total, detail = 0.0, []
    for j in range(WTP.shape[1]):
        col = WTP[:, j]
        best_p, best_rev = 0.0, 0.0
        for p in np.unique(col):              # 候補価格は各消費者のWTP
            rev = p * np.sum(col >= p)         # その価格で買う人数 × 価格
            if rev > best_rev:
                best_p, best_rev = p, rev
        detail.append((best_p, best_rev))
        total += best_rev
    return total, detail

def best_bundle(WTP):
    """バンドルWTP=財WTPの和。バンドル価格を総当たりして売上最大化"""
    bundle = WTP.sum(axis=1)
    best_p, best_rev = 0.0, 0.0
    for p in np.unique(bundle):
        rev = p * np.sum(bundle >= p)
        if rev > best_rev:
            best_p, best_rev = p, rev
    return best_p, best_rev, bundle

ind_total, ind_detail = best_individual(WTP_neg)
bp, brev, bundle = best_bundle(WTP_neg)
corr = np.corrcoef(WTP_neg[:, 0], WTP_neg[:, 1])[0, 1]

print("=== バンドル vs 個別販売(限界費用0, WTPが負相関)===")
print(f"財AとBのWTPの相関 = {corr:+.1f}(負相関)")
print("個別販売:")
print(f"  財A:最適価格 {ind_detail[0][0]:.0f} → 売上 {ind_detail[0][1]:.0f}")
print(f"  財B:最適価格 {ind_detail[1][0]:.0f} → 売上 {ind_detail[1][1]:.0f}")
print(f"  個別販売の合計売上 = {ind_total:.0f}")
print("純バンドル:")
print(f"  バンドルWTP = {bundle}(各消費者 8+2, 2+8)")
print(f"  最適バンドル価格 {bp:.0f} → 売上 {brev:.0f}")
print(f"バンドルの利得 = {brev - ind_total:+.0f}")

# 対照:WTPが正相関なら、バンドルの優位は消える
WTP_pos = np.array([[8.0, 8.0],
                    [2.0, 2.0]])
ind_total_p, _ = best_individual(WTP_pos)
bp_p, brev_p, bundle_p = best_bundle(WTP_pos)
corr_p = np.corrcoef(WTP_pos[:, 0], WTP_pos[:, 1])[0, 1]
print("--- 対照:WTPが正相関なら ---")
print(f"相関 = {corr_p:+.1f}(正相関), 個別合計売上 = {ind_total_p:.0f}, "
      f"バンドル売上 = {brev_p:.0f}(バンドルWTP={bundle_p})")
print(f"バンドルの利得 = {brev_p - ind_total_p:+.0f} → 正相関ではバンドルの優位が消える")

出力:

=== バンドル vs 個別販売(限界費用0, WTPが負相関)===
財AとBのWTPの相関 = -1.0(負相関)
個別販売:
  財A:最適価格 8 → 売上 8
  財B:最適価格 8 → 売上 8
  個別販売の合計売上 = 16
純バンドル:
  バンドルWTP = [10. 10.](各消費者 8+2, 2+8)
  最適バンドル価格 10 → 売上 20
バンドルの利得 = +4
--- 対照:WTPが正相関なら ---
相関 = +1.0(正相関), 個別合計売上 = 16, バンドル売上 = 16(バンドルWTP=[16.  4.])
バンドルの利得 = +0 → 正相関ではバンドルの優位が消える

出力の意味:負相関のケースでは、バンドル売上 20 が個別合計 16+4 上回りました。理由は WTP のならし——個別だと財Aの WTP は {8, 2} とばらつき、価格を8にすれば1人しか買わず、2にすれば余剰を取りこぼします。バンドルにすると WTP は {10, 10} にそろい、価格10で全員から余剰をぴったり回収できます。対照の正相関のケース(消費者1が両財とも高評価、消費者2が両方とも低評価)では、バンドル WTP が {16, 4} とかえって広がり、バンドル売上は16で個別と同じ(利得 +0)。バンドルが効くのは WTP が負相関のときで、いつでも得とは限らないことが数字で確認できます。なお実務では純バンドルと単品の両方を併売する**混合バンドル(mixed bundling)**が使われ、高評価の単品客も取りこぼさないようにします。

3. 心理価格:数理は弱く、実験で測る

3つ目は心理価格(psychological pricing)——行動経済学的な値付けです。代表例が端数価格:¥2,000 を ¥1,980 にすると、20円の値引き以上に売れることがあります。先頭の桁が「2」から「1」に変わると安く感じるleft-digit 効果です。ほかにも、最初に見せた高い価格が基準になるアンカリング、比較対象を置くと選ばれやすくなるおとり効果、同じ価格でも文脈で評価が変わる文脈依存など。

ここで率直に言うべきは、これらの数理モデルは §1・§2 ほど強くないということです。効果の大きさは商品・客層・提示文脈に強く依存し、理論からは決まりません。だから心理価格は実験(A/Bテスト)で測るのが基本になります(→ 第7章)。下のデモは、left-digit 効果を「しきい値で需要が不連続にジャンプする」ものとして単純化し、ジャンプ幅が分かって初めて値付けの良し悪しが言えることを示します。

import numpy as np

# なめらかな(合理的)需要 base(P)=a-bP。限界費用0。
a, b = 1000.0, 0.3
def base(P): return a - b * P

# left-digit 効果:千の位が下がる(P<2000)と知覚価格が大きく下がり需要が跳ねる
JUMP = 60.0
def demand_psych(P):
    return base(P) + (JUMP if P < 2000 else 0.0)

for P in (1980.0, 2000.0):
    qs, qp = base(P), demand_psych(P)
    print(f"P={P:.0f}: なめらか需要 Q={qs:.0f}(売上{P*qs:,.0f}) / "
          f"心理込み Q={qp:.0f}(売上{P*qp:,.0f})")

ds = 1980 * base(1980) - 2000 * base(2000)                  # なめらかモデルでの売上差
dp = 1980 * demand_psych(1980) - 2000 * demand_psych(2000)  # left-digit モデルでの売上差
print("1980 が 2000 に勝つ売上差:")
print(f"  なめらかモデル   = {ds:+,.0f}(ごくわずか)")
print(f"  left-digit モデル = {dp:+,.0f}(ジャンプで大きく逆転)")
print("→ ジャンプ幅は理論では決まらない。A/Bテストで測るしかない(第7章)。")

出力:

P=1980: なめらか需要 Q=406(売上803,880) / 心理込み Q=466(売上922,680)
P=2000: なめらか需要 Q=400(売上800,000) / 心理込み Q=400(売上800,000)
1980 が 2000 に勝つ売上差:
  なめらかモデル   = +3,880(ごくわずか)
  left-digit モデル = +122,680(ジャンプで大きく逆転)
→ ジャンプ幅は理論では決まらない。A/Bテストで測るしかない(第7章)。

出力の意味:なめらかな需要曲線だけで考えると、¥1,980 は ¥2,000 を売上で +3,880 しか上回らない(20円値引きの効果はわずか)。しかし left-digit のジャンプ(ここでは需要 +60)が本当に存在すれば、¥1,980 は +122,680 で圧勝します。問題は、この +60 というジャンプ幅は理論からは出てこないこと。実際にあるのか、どれだけ大きいのかは、価格を振った A/Bテストでしか確かめられません。心理価格は「効くはず」という思い込みでなく、実測して初めて使える——次の図でその不連続を見ます。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

a, b, JUMP = 1000.0, 0.3, 60.0
P = np.linspace(1900, 2100, 401)
base = a - b * P
psych = base + np.where(P < 2000, JUMP, 0.0)

fig, ax = plt.subplots(figsize=(7.5, 4.4))
ax.plot(P, base, color="C0", lw=1.8, ls="--", label="なめらかな需要 a-bP")
left = P < 2000
ax.plot(P[left], psych[left], color="C3", lw=2.4, label="left-digit 込みの需要")
ax.plot(P[~left], psych[~left], color="C3", lw=2.4)
ax.axvline(2000, color="gray", ls=":", lw=1.2)
ax.annotate("2000円のしきい値で\n需要がジャンプ",
            xy=(1999, base[np.argmin(np.abs(P - 1999))] + JUMP),
            xytext=(1930, 470), arrowprops=dict(arrowstyle="->", color="C3"))
ax.set_title("left-digit 効果:しきい値で需要が不連続にジャンプ")
ax.set_xlabel("価格 P(円)")
ax.set_ylabel("需要量 Q")
ax.legend(loc="upper right")
fig.tight_layout()
plt.show()

赤い需要曲線は ¥2,000 の手前で上にジャンプします。この不連続こそ心理価格の正体で、なめらかな需要曲線(青破線)の延長では捉えられません。ジャンプの有無と大きさを決めるのは数式でなくデータ——だから第7章の実験につながります。

まとめ:価格差別・バンドル・心理価格はいずれも、03-02 の「静的な単一最適価格」の拡張・補完です。価格差別とバンドルは需要の数理から利得が導けますが、その前提(弾力性・WTP 分布・負相関)が正しいかは推定に依存します。心理価格にいたっては理論が弱く、効果は実験頼みです。したがってどの戦略も、最終的には価格テスト=A/Bテスト(第7章)で検証するのが筋になります。さらに、需要が分からないまま価格を動かしながら学習・最適化する問題は、多腕バンディット(第8章/機械学習テキスト)の出番です。

⚠️ よくある誤解

関連ノート