📈

ランチェスターの法則でマーケティング分析!一般化モデルとPythonでのシミュレーションを試してみました...

に公開

はじめに:戦場からビジネスへ、数学で切り込む!

ランチェスターの法則、皆さんはご存知でしょうか? 1914年にイギリスの数学者フレデリック・ランチェスターが航空戦を分析するために作った数理モデルです。戦場で兵士が減っていく様子を微分方程式で表現し、どちらが勝つかを計算します。シンプルだけど、めっちゃ奥深いんですよ! これが、ビジネスの世界、例えば市場シェアや売上の争いにもバッチリ応用できるんです。

今回は、日本のSIerを題材に、ランチェスターの法則をちょっとアレンジして遊んでみました。対象は某SIer4社です。第一法則と第二法則をパラメータで一般化し、2者対立をn者対立に拡張。さらに、2013~2024年の有価証券報告書の売上データを使ってPythonでシミュレーションしてみました。紅茶片手に、じっくり楽しんでください!

ランチェスターの法則の基礎:戦場を数式で読み解く

ランチェスターの法則は、戦闘の損耗を微分方程式でモデル化するもの。ビジネスでは、売上高や顧客数を「兵力」、技術力やブランド力を「戦闘効率」とみなします。2つの法則を、式変形とともに詳しく見ていきましょう。ちなみにここで示す各種変数は以下になります。

  • X : 自社のリソース(例: 売上高、顧客数)
  • Y : 競合のリソース
  • a : 自社の戦闘効率(技術力、ブランド力)
  • b : 競合の戦闘効率

第一法則:一騎打ちのシンプルな戦い

第一法則は、古代の剣や槍の戦いみたいな一対一のシチュエーションを想定します。相手の人数は関係なく、武器の性能(戦闘効率)だけで勝負が決まるんです。ビジネスだと、ニッチ市場で技術力や品質がモノを言うケースですね。例えば、スタートアップが独自のアルゴリズムで大手に挑むような場面です。

\frac{dX}{dt} = -b, \quad \frac{dY}{dt} = -a

第二法則:数で押す近代戦

第二法則は、銃や火砲を使った集団戦闘を想定。損耗率が相手の兵力数に比例するので、リソースが多い方が圧倒的に有利です。ビジネスでは、市場シェアや広告予算の規模がモノを言う市場、例えばEコマースやクラウドサービスに当てはまります。

\frac{dX}{dt} = -b Y, \quad \frac{dY}{dt} = -a X

ランチェスターの第一法則と第二法則を一般化:中間を埋める

現実のビジネスを第一法則(効率だけで勝負)や第二法則(リソース量が大事)に当てはめるのは無理があります。第一法則と第二法則の間には、中間的なケースがたくさんありますよね。そこで、パラメータcを導入して、損耗率を相手のリソースのc乗に依存させるモデルを作ってみました。

\frac{dX}{dt} = -b Y^c, \quad \frac{dY}{dt} = -a X^c\quad (0 \le c \le 1)

  • c = 0: 第一法則(Y^0 = 1), (\frac{dX}{dt} = -b, \frac{dY}{dt} = -a)
  • c = 1: 第二法則( Y^1 = Y), (\frac{dX}{dt} = -b Y, \frac{dY}{dt} = -a X)
  • 0 < c < 1: 中間法則

n項対立への拡張:複数社間のバチバチな戦い

現実のビジネスは2者対立じゃなくて、複数企業がガチンコで競うことが多い。そこで、2者対立をn者対立に拡張してみました。

\frac{dX_i}{dt} = -\sum_{j \neq i} b_j X_j^c, \quad i = 1, 2, \ldots, n

  • X_i: 企業iのリソース(例: 売上高)
  • b_j: 企業jの戦闘効率
  • c: リソース依存度

Pythonでシミュレーション:4社の売上データを試す

某SIer企業の有価証券報告書を参照し売上データを抽出して4社がどのような戦い方をしているのかPythonでささっと分析してみました。ちなみに値が大きいのでLog10でスケールを下げています。

extended_lanchesters
import numpy as np
from scipy.integrate import odeint
from scipy.optimize import minimize
import matplotlib.pyplot as plt

# データの入力(年度降順なので昇順にソート)
years = np.array([2024, 2023, 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013])[::-1]  # 2013 to 2024
data = np.array([
    [9783370, 3550116, 3477262, 13374559],
    [9728716, 3476985, 3313018, 13136194],
    [10881150, 3713767, 3014095, 12156447],
    [10264602, 3586839, 2994023, 11943966],
    [8729196, 3589702, 3095234, 11899415],
    [8767263, 3857797, 2913446, 11879842],
    [9480619, 3952437, 2844447, 11782148],
    [9368614, 4098379, 2665035, 11391016],
    [9162264, 4132972, 2824833, 11540997],
    [10034305, 4739294, 2935517, 11095317],
    [9774930, 4753210, 3043114, 10925174],
    [9666446, 4762445, 3071609, 10700740]
])[::-1, :]  # 行を逆順にして2013から2024に

# 底10の対数スケールダウン
data_log = np.log10(data)

# 時間軸(2013をt=0, 2024をt=11)
t = np.arange(0, 12, 1)  # 0 to 11 years
n = 4  # 企業数 (H, F, N, T)

# ランチェスターの一般化モデル(timeはodeintのために必要だが未使用)
def lanchester_model(X, time, b, c, n):
    X = np.maximum(X, 0)  # 負の値防止
    dXdt = np.zeros(n)
    for i in range(n):
        sum_term = 0
        for j in range(n):
            if j != i:
                sum_term += b[j] * (X[j] ** c)
        dXdt[i] = -sum_term
    return dXdt

# 目的関数(モデル予測とデータの誤差二乗和)
def objective(params, data, t, n):
    b = params[:n]
    c = params[n]
    X0 = data[0, :]  # 初期値 (2013年)
    sol = odeint(lanchester_model, X0, t, args=(b, c, n))
    error = np.sum((sol - data) ** 2)
    return error

# 初期推定値(対数スケール後、bを適切な範囲に設定)
initial_guess = np.array([1.0, 1.0, 1.0, 1.0, 0.5])  # b1(H), b2(F), b3(N), b4(T), c
bounds = [(0, 10)] * n + [(0, 1)]  # bの範囲を対数スケールに合わせて調整、cは0-1

# 最適化実行
result = minimize(objective, initial_guess, args=(data_log, t, n), bounds=bounds, method='L-BFGS-B')
estimated_b = result.x[:n]
estimated_c = result.x[n]

# 結果出力
print("Estimated b (H, F, N, T):", estimated_b)
print("Estimated c:", estimated_c)

# フィット結果のプロット(元のスケールに戻す)
plt.figure(figsize=(10, 6))
companies = ['H', 'F', 'N', 'T']
for i in range(n):
    plt.plot(years, data[:, i], 'o', label=f'{companies[i]} Data')
    plt.plot(years, 10 ** odeint(lanchester_model, data_log[0, :], t, args=(estimated_b, estimated_c, n))[:, i], '-', label=f'{companies[i]} Fit')
plt.xlabel('Year')
plt.ylabel('Revenue (million yen)')
plt.title(f'Lanchester Model Fit (c={estimated_c:.3f})')
plt.legend()
plt.grid(True)
plt.show()

# 結果保存
np.savetxt('estimated_params_log.txt', np.append(estimated_b, estimated_c), header='b_H b_F b_N b_T c')
np.savetxt('data_log.txt', data_log, header='H F N T')

結果:
以下の値がが出力されました。

  • Estimated b: [1.01760567 (N社), 1.01721865 (F社), 1.01721865 (N社), 0.99804381 (T社)]
  • Estimated c: 0.5009393906445575

シミュレーション結果と現実の合致度:

各社のb_jに関する考察

b_jは各社の競争力、つまり技術力やブランド力、営業効率を示します。対数スケールX_i = \log_{10}(\text{売上高})で推定してるので、値は売上(百万~千万単位)に比べて小さいです。N社(1.01760567)、F社(1.01721865)、N社(1.01721865)はほぼ同じ競争力。T社(0.99804381)はちょっとだけ低い。現実では、T社の売上はダントツ(2024年で1.3兆円超)なのに、競争力(効率)が劣ると出てしまった。これは、T社の売上が通信事業に依存してるから、技術力や顧客奪取力で3社に僅差で負けてる可能性を示していると思われる。ただし、b_jの差(1.017~0.998)が小さすぎるので、競争力の差が売上変化に与える影響は限定的。データ見ると、T社の売上はガンガン伸びてるけど、モデルでは効率の差が小さく出てしまった。売上規模X_iの影響がおおきのかもしれない...

各社のcに関する考察

c \approx 0.501は、競争がリソース(売上高)と効率(技術力、ブランド)の両方に依存してることを示します。リソースの影響は平方根( X_j^{0.5} )くらいのイメージ。c \approx 0.5は、マーケットが規模の経済(大規模プロジェクトの受注力)と技術力のバランスで動いてることを表します。SIer市場の成長率は2024年で7.4%(市場規模7兆205億円)。T社の規模優位と、N社の堅実な成長(売上データで2017~2024年に安定)やN社の回復傾向(同じく増加)が、技術力と規模の両方で説明できる。c < 1なので、完全な規模勝負(価格競争や広告量)じゃなく、中堅企業が技術で戦える市場ってのは、SIerの現実と一致。海外売上含む成長率8.9%も、規模と効率のバランスを裏付けてます。

気づいた点:失敗と改善のメモ

シミュレーションやってみて、うまくいったところもあれば、盛大にコケたところも。失敗と改善点を正直にまとめます。

  • ランチェスターの法則は「損耗」(リソース減)を前提にしてるけど、全社売上増。市場成長を無視したのが失敗。改善点:成長項をモデルに追加して、市場拡大を考慮。
  • 初期推定値( b_j = 1.0, c = 0.5)や範囲( b_j \in [0, 10])に依存しすぎ。最適化が局所解にハマった可能性。改善点:グリッドサーチやベイズ推定で、推定の安定性を上げる。
  • DX・AI需要や景気変動が売上に影響してるのに、モデルは単純な競争のみ。失敗:これを無視したこと。改善点:外部変数をモデルに組み込む。

Discussion