売上・受注予測をするときに精度よりも大事なこと

# はじめに

こんにちは。事業会社で働く新卒データサイエンティストです。

https://dippeople.dip-net.jp/18868/

今回は、売上予測や受注予測を行うときに大事だと思っている（思い始めた）ことについて書きます。結論を述べると、**予測精度は大事だが、精度が高いだけではどうしようもないので現場のKPIをモデルに組み込もう**、ということです。現場のKPIをどう設定するのか、というところまで落とし込んだモデルを作ってはじめて、予測が役に立つと思います。

# 背景：なぜ予測が必要か？
どの事業会社でも、売上や受注額の予測を行っていると思います。社外に外注している会社があれば、社内で過去の傾向からおよその売上・受注額を予測している会社もあるでしょう。あるいは、社内にデータサイエンティストがいれば、統計的な手法を用いた予測が可能になります。

では、なぜ売上・受注額の予測が必要なのでしょうか。以下のような理由が考えられそうです。

- 財務会計のため

対外的に公式の売上見込みを発表する場合などがそれにあたるでしょう。

- 適切なリソース配分のため

例えば、人材配置や在庫管理などです。意志決定層にとって、各リソースをどこにどれくらい分配し、それがどれくらい売上・受注につながるのか分かることは非常に望ましいことだと思います。

# 問題提起：予測精度は重要？
予測の際に、データサイエンティストに求められる役割とは何なのでしょうか。よくありそうなのは、予測精度の向上です。統計的な手法に則ってモデリングすれば、直感に基づく予測よりも精度の高い予測ができるでしょう（実際は、ドメイン知識のある意志決定層の予測精度の方が高いこともありますが）。

売上予測が財務会計のためなら、予測精度の向上が重要だと思います。一方、現場の適切なリソース配分の場合はどうでしょうか。確かに、一定の予測精度を担保することは重要です。的外れな予測をしても仕方がないので。

ですが、**精度が高いだけではどうしようもない**という現実があるように思います。「今月の予測値と実績値のズレは1%台でした！」と報告すれば、予測の依頼者は喜んでくれると思います。しかし、精度の高い予測値を出すだけでは売上の伸びに貢献しません。もう一歩踏み込んで、売上や受注の増加に結びつくような示唆を出すことが求められるように思います。

# 提案：現場のKPIをモデルに組み込む

売上・受注額の予測をする方法は様々だと思いますが、ここでは状態空間モデルを挙げます。

状態空間モデルでは、目に見えない（売上・受注額の平均的な）水準$\mu$があり、そこにトレンド成分$\delta$や季節成分$\gamma$を載せることで売上・受注額を表現できると考えます。時期を添え字の$t$で表すと、$t$期の売上・受注額$Y_t$は以下の数式で表現することができます（簡単のため、トレンド成分は無視します。）。

$\mu_t = \mu_{t-1} + w_t \quad \cdots ①$
$Y_t = \mu_t + \gamma_t + v_t \quad \cdots ②$

（ただし、$w_t$と$v_t$はそれぞれ誤差項。）

$②$に現場のKPIを組み込むことで、それがどのように売上・受注額に影響するかを表現できます。$t$期のKPIとその係数の組を$(X_t, \beta_t)$で表すと、$②$は

$Y_t = \mu_t + \gamma_t + \beta_tX_t + v_t \quad \cdots ②^{'}$

となります。

実際には、$t$期のKPIだけが$t$期の売上・受注額に影響するとは限らないので、$t-1$期や$t-2$期など、数期前のKPIもモデルに組み込む必要があるかもしれません。今回の記事ではそこまで踏み込みませんが、幾何級数的な表現を組み込んだりすることでラグを表現できます。

このように現場のKPIをモデルに組み込むことで、「このKPIを来月これくらいに設定すれば、売上・受注額がこれくらい伸びそう」といった判断ができるようになります。つまり、来月のKPIに基づいたシミュレーションができます。

# コードの具体例

例として、複数のKPIをモデルに組み込んで売上額を予測をすることを考えます。以下のモデルをStanで実装します。

$\mu_t = \mu_{t-1} + \delta_{t-1} + w_{t} \quad \cdots (状態方程式)$
$Y_t = \mu_t + \gamma_t + \sum_{k = 1}^{K}\beta_{k,t}\times KPI_{k,t} + v_t \quad \cdots (観測方程式)$

ただし、
- $\mu_t$：水準
- $\delta_t$：ドリフト成分
- $w_t$：状態方程式の誤差項
- $Y_t$：売上額
- $\gamma_t$：季節成分
- $\beta_{k,t}$：各KPI変数の係数
- $KPI_{k,t}$：各KPI, ただし$k=(1,2,\cdots,K)$
- $v_t$：観測方程式の誤差項
を表すものとします。

```Stan
data {
  int time_type;        // データ取得期間の長さ
  int kpi_type; // KPIの種類
  int pred_term; // 予測期間の長さ
  vector[time_type] amount;  // 売上額
  matrix[kpi_type, time_type] kpi; // KPI
}

parameters {
  vector[time_type] mu;       // 水準成分
  vector[time_type] drift;    // ドリフト成分
  vector[time_type] seasonality;    // 季節成分
  real<lower=0> sigma_state;  // 水準成分の標準偏差
  real<lower=0> sigma_drift;  // ドリフト成分の標準偏差
  real<lower=0> sigma_seasonality;  // 季節成分の標準偏差
  real<lower=0> sigma_obs;  // 観測誤差の標準偏差

  matrix[kpi_type, time_type] beta; // KPI変数の係数
  real<lower=0> sigma_beta; // KPI変数の係数に共通の標準偏差
}

transformed parameters {
  vector[time_type] alpha;  // 各成分の和として得られる状態推定値
  
  for (i in 1:time_type) {
    alpha[i] = mu[i] + seasonality[i] + dot_product(beta[:, i], kpi[:, i]);
  }
}

model {
  sigma_state ~ gamma(0.1, 0.1);
  sigma_drift ~ gamma(0.1, 0.1);
  sigma_obs ~ gamma(0.1, 0.1);
  sigma_seasonality ~ gamma(0.1, 0.1);
  
  for (k in 1:kpi_type) {
    beta[k,:] ~ normal(0, sigma_beta);
   }
  
  // 状態方程式に従って状態が遷移する
  for(i in 2:time_type) {
    mu[i] ~ normal(mu[i-1] + drift[i-1], sigma_state);
    drift[i] ~ normal(drift[i-1], sigma_drift);
  }
  
  // 季節成分
  for (i in 12:time_type) {
    seasonality[i] ~ normal(-sum(seasonality[(i-11):(i-1)]), sigma_seasonality);
  }
  
  // 観測方程式に従って観測値が得られる
  for(i in 1:time_type) {
    amount[i] ~ normal(alpha[i], sigma_obs);
  }
}

generated quantities {
  vector[time_type + pred_term] mu_pred; // 水準成分の推定値
  vector[time_type + pred_term] drift_pred; // ドリフト成分の推定値
  vector[time_type + pred_term] seasonality_pred;    // 季節成分の推定値
  vector[time_type + pred_term] alpha_pred;    // 状態推定値
  matrix[kpi_type, time_type + pred_term] beta_pred;

  // データ取得期間は同じ
  mu_pred[1:time_type] = mu;
  drift_pred[1:time_type] = drift;
  seasonality_pred[1:time_type] = seasonality;
  alpha_pred[1:time_type] = alpha;
  beta_pred[:, 1:time_type] = beta;
  

  // データ取得期間を超えた部分を予測
  for(i in 1:pred_term){
    mu_pred[time_type + i] = normal_rng(mu_pred[time_type + i - 1] + drift_pred[time_type + i - 1], sigma_state);
    drift_pred[time_type + i] = normal_rng(drift_pred[time_type + i - 1], sigma_drift);
    seasonality_pred[time_type + i] = normal_rng(-sum(seasonality_pred[(time_type + i - 11):(time_type + i - 1)]), sigma_seasonality);
    for (k in 1:kpi_type) {
    beta_pred[k, time_type + i] = normal_rng(beta_pred[k, time_type + i - 1], sigma_beta);
    }
    alpha_pred[time_type + i] = mu_pred[time_type + i] + seasonality_pred[time_type + i] + dot_product(beta_pred[:, time_type + i], kpi[:, time_type]);
  }
}
```

# 課題

ただ、現場のKPIをモデルに組み込む上記の方法には、いくつか課題があるように思います。

- KPIを伸ばしたからといって、売上・受注額が必ず達成されるとは限らない。

数式$②$でも示したように、実際にはKPIだけでなくて市場のトレンドやサービス特有の季節成分が組み合わさって売上・受注額が達成されます。なので、KPIを高めに設定してシミュレーションすれば、必ず目標の売上・受注額が達成できるというのは難しいでしょう。

- 識別の仮定を満たしているのか確認する必要がある。

これはどちらかというとMMM（マーケティングミックスモデリング）寄りの話になるかもしれませんが、KPIと売上・受注額がトレンド成分・季節成分で条件づけたときに独立になっている必要があります。

# おわりに

今回は売上・受注予測に関して書きました。最低限の予測精度が担保されたモデルを作ることは重要です。が、それ以上に大事なのは、予測値を達成するためには現場のKPIがどうあればよいか、というところまで落とし込んだモデルを作ることだと思います。とはいえ、上で紹介した内容は、まだまだ思いつきの範囲を出ません。私自身、KPIを変数として組み込むことがどの程度正当化されるのか、しばらく文献を漁ろうと思っています。