1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

手元のPCで商品の売れ行きを予測してみよう!

1
Posted at

はじめに

小売店で商品を扱っていると,「来月はどれくらい仕入れればよいか」と悩む場面があると思います.仕入れが多すぎれば在庫が残り,少なすぎれば売り逃しが出ます.経験や勘に頼って発注している方も多いのではないでしょうか.もし,売れ行きをある程度でも予測できれば,過不足ない適切な発注量を算出することができるはずです.

予測と聞くと,最近のAIを思い浮かべる方もいると思います.費用が高い,手元のパソコンでは動かないと感じている方もいるかもしれません.しかし,傾向がはっきりしているデータであれば,手元のノートPCで高速に予測する方法があります.

この記事では,熊本県の蚊取り線香の購入点数を題材に,こうした手元のPCで動く予測モデルを紹介します.

この記事のゴールは次の3つです.

  • 学習データとテストデータを分ける理由を理解する
  • 時系列データを,古典的な方法でトレンドと季節成分に分解する
  • 分解した要素を組み合わせて予測し,その精度を確かめる

使用するデータ

RESAS(地域経済分析システム)の「生活用品消費分析」に掲載されている,熊本県の蚊取り線香の購入点数を使います.RESASは,地域の経済に関するデータを地図やグラフで利用できる政府が提供するデータプラットフォームです.

今回使用するのは,株式会社True Data様が提供する熊本県の蚊取り線香の購入点数データです.

記事への掲載にあたり,株式会社True Data様から利用の許諾をいただきました.この場を借りてお礼申し上げます.読者の皆様が本データを利用される場合も,事前に株式会社True Data様へ利用目的や利用方法をご確認のうえ,許諾を得ていただきますようお願いいたします.

指標は「レジ通過1000人あたり購入点数」です.レジを通った1000人あたりで,蚊取り線香が何点買われたかを表します.

RESASのグラフから月ごとの値を読み取りました(2026年10月6日に閲覧).期間は2019年3月〜2025年12月の82か月分です.

00_overview.png

グラフを見ると,購入点数は夏に多く,冬にはほぼ0になるという季節的なパターンに加え,全体として年々減少する傾向が読み取れます.こうした特徴から,今後の動きもある程度予想できそうです.本記事では,この季節性と長期的な傾向を数値で捉え,将来の購入点数を予測するとともに,その精度を評価していきます.

データの分割

今回の目的は,まだ手に入っていない未来の売れ行きを予測することです.そのためには,作ったモデルが見たことのないデータをどれだけ当てられるかを確かめる必要があります.

そこで,データを2つに分けます.モデルを作るための学習データと,予測の答え合わせに使うテストデータです.学習データだけで作ったモデルがテストデータをうまく予測できれば,この先の未来もある程度当てられると期待できます.

今回は,直近3年分の36か月(2023年1月〜2025年12月)をテストデータにします.残りの2019年3月〜2022年12月の46か月が学習データです.

00_train_test.png

青が学習データ,オレンジがテストデータです.学習データの4年弱から,オレンジの3年分を予測します.

時系列データをランダムに分割すると,未来の情報を使って過去を予測することになります.そのため,必ず時間で区切ります.

テストデータはまだ手に入っていない未来として扱います.未来のデータを見て分析の方針を決めると,本番の予測より良い成績が出てしまいます.そこで,この先の欠損の補完から時系列の分解までは,すべて学習データだけで行います.テストデータを使うのは,最後の答え合わせだけです.

データの前処理

予測の精度は,使う手法だけでなく,入力するデータの状態にも大きく左右されます.欠損や誤った値が残ったままだと,モデルはそれも含めて傾向を学んでしまいます.その結果,思ったような精度が出なかったり,実態とかけ離れた予測になったりします.

そのため,分析に入る前にデータの問題を見つけて手当てしておくことが大切です.今回のデータでは,前処理として欠損値を補完します.

欠損値の補完

2020年12月〜2021年2月の3か月は値がありません.冬の値はほぼ0です.前後の値(2020年11月の0.022と2021年3月の0.018)で直線補間すると,冬にしては大きな値になってしまいます.そこで,学習データに含まれるほかの年の同じ月の平均で値を補います.

補完した値は,12月が約0.006,1月が約0.002,2月が約0.008です.

01_missing.png

学習データから11月〜翌年3月の値を取り出して重ねた図です.緑の破線は直線補間した場合,オレンジの破線は同じ月の平均で補完した場合です.

12月〜2月に注目すると,直線補間した値は,ほかの年の冬と比べて明らかに高くなっています.これに対して,同じ月の平均で補完した値はほかの年とほぼ同じ水準に収まり,冬の傾向をうまく捉えています.

時系列の分解

次は,実際にデータの傾向を把握していきます.
時系列データは,3つの成分に分けて考えると理解しやすくなります.

  • トレンド:長期的な増減
  • 季節性:毎年繰り返す変動
  • 残差:上の2つで説明できない部分

今回は,シンプルな分解方法で,データを3つの成分に分けていきます.移動平均と平均の計算だけでできる,昔からある単純な方法です.

加法モデルと乗法モデル

3つの成分の組み合わせ方には,加法モデルと乗法モデルの2通りがあります.

\text{加法モデル:}\quad y_t = T_t + S_t + R_t
\text{乗法モデル:}\quad y_t = T_t \times S_t \times R_t

それぞれの記号の意味は次のとおりです.

  • $t$ :時点.今回は月を表します
  • $y_t$ :時点 $t$ の購入点数.実際に観測された値です
  • $T_t$ :時点 $t$ のトレンド.長期的な水準を表します
  • $S_t$ :時点 $t$ の季節成分.その月に特有の変動を表します
  • $R_t$ :時点 $t$ の残差.トレンドと季節成分で説明できない部分です

加法モデルでは,季節成分$S_t$はトレンドより何点多いかという差を表します.単位は購入点数と同じで,残差$R_t$は0のまわりの値になります.トレンドが下がっても,季節の振れ幅は変わりません.

乗法モデルでは,季節成分$S_t$はトレンドの何倍かという比を表します.単位のない倍率で,残差$R_t$は1のまわりの値になります.トレンドが下がれば,季節の振れ幅も同じ割合で小さくなります.

どちらが合うかは,年ごとの平均と振れ幅(最大値−最小値)を比べるとわかります.学習データのうち,12か月そろっている年で比べました.

年 年平均 振れ幅 振れ幅÷年平均
2020 0.373 1.225 3.29
2021 0.322 0.975 3.03
2022 0.306 0.878 2.87

年平均が下がると,振れ幅も小さくなっています.振れ幅÷年平均はおよそ3で,大きくは変わりません.振れ幅が水準に比例しているので,今回は乗法モデルを使います.

分解の手順

それでは,実際に今回のデータに時系列分解を施していきます.
ここでは,次の手順でトレンド$T_t$と季節成分$S_t$を求めます.

  1. 自己相関で,季節が繰り返す周期を確かめる
  2. 移動平均で,大まかなトレンドを取り出す
  3. 元の値を移動平均で割り,月ごとに平均して季節成分を求める
  4. 移動平均の対数に最小二乗法で直線を当てはめ,指数関数のトレンドとする
  5. 元の値・トレンド・季節成分・残差を並べて見比べる

古典的分解では,2.の移動平均をそのままトレンドとして使います.今回は4.で,移動平均を数式で表現したトレンドに置き換えます.移動平均は未来の月には計算できませんが,時刻に依存した数式で表現すれば予測に使えるからです.

周期の確認

まずは周期を確認します.グラフを見ると1年周期がありそうですが,見た目だけで決めずに以下で説明する自己相関を利用します.

自己相関(ACF)は,ある時点の値と$k$か月前の値との相関です.この$k$をラグと呼びます.データに12か月の周期があれば,ラグ12で正の自己相関が大きくなる傾向があります.ラグごとの自己相関を並べたグラフを,コレログラムと呼びます.

02_correlogram.png

自己相関は,波のように上下を繰り返しています.

  • ラグ5〜6で−0.60の谷になる.半年ずれると夏と冬が重なるため
  • ラグ12で0.66の山になる.1年ずれると同じ季節が重なるため
  • ラグ17付近で再び谷になり,ラグ24で0.43の山になる

半年ずれたラグ5〜6で負の相関,1年ずれたラグ12で正の相関が強く出ています.これは,夏に多く冬に少ない季節的な変動と整合します.そこで,以降は周期を12か月として分析を進めます.

移動平均で大まかなトレンドを取り出す

周期と同じ12か月で移動平均を取ると,季節の山と谷が打ち消し合い,大まかなトレンドが残ります.窓幅の12は,前の節のコレログラムで確かめた周期です.

移動平均には,各時点の前後の値を使う中心化移動平均を使います.ただし12は偶数なので,単純に12か月を平均すると,中心が月と月の間にずれてしまいます.そこで,前後6か月ずつの13か月を使い,両端の月だけ半分の重みにします.これを2×12移動平均と呼びます.使う値はすべて学習データの中なので,テストデータの情報は混ざりません.

M_t = \frac{1}{12}\left(\frac{1}{2}y_{t-6} + y_{t-5} + \cdots + y_{t+5} + \frac{1}{2}y_{t+6}\right)

$M_t$は時点$t$の移動平均です.トレンド$T_t$と区別するため,別の記号にしています.

03_trend.png

移動平均は,2019年9月の0.44から2022年6月の0.31まで下がっています.前後6か月分の値が必要なので,学習期間の最初と最後の6か月は計算できません.

季節成分を求める

元の値$y_t$を,同じ時点の移動平均$M_t$で割ります.割った比は,各月の値が大まかなトレンドの何倍かを表します.

04_seasonal.png

移動平均で割ると,年々下がる傾向が取り除かれ,季節による変動が残ります.ただし,同じ月でも年によって値がばらつきます.そこで,比を月ごとに平均してならし,季節成分 $S_t$ とします.たとえば9月の季節成分は,2019〜2021年の9月の比(2.32,0.90,1.88)の平均がもとになります.最後に,季節成分が全体の水準を動かさないよう,12か月の平均が1になるようにそろえます.

季節成分は,7月が3.03で,トレンドのおよそ3倍売れることを示しています.1月は0.005で,ほとんど売れません.

移動平均からトレンドを求める

今回使用した中心化移動平均は,$M_t$の計算において将来の時刻の値を使用して計算されるため,将来の予測を行う際は計算することができません.そこで,移動平均の変化を数式で表現し,それをトレンド$T_t$として利用します.

今回は,トレンドが毎年同じ割合で減ると仮定し,指数関数で表します.

指数関数は,対数をとると直線になります.そこで,移動平均の対数に最小二乗法で直線を当てはめます.最小二乗法は,直線と各点の縦のずれを2乗して合計し,それが最も小さくなる直線を選ぶ方法です.

\log M_t \approx a + b\,t
  • $a$ :切片.直線の高さを決めます
  • $b$ :傾き.1か月あたりの対数の増減を表します

求めた直線を,指数関数で元の値に戻したものがトレンドです.

T_t = \exp(a + b\,t)

04_trend_fit.png

左は対数に変換した値,右は元の尺度に戻したものです.左で当てはめた直線を元の尺度に戻すと,少しわかりにくいですが,右の曲線になります.トレンドは1年あたり0.878倍で,毎年約12%ずつ減る形になりました.2019年3月の0.45から,2022年12月の0.28まで下がっています.

3つの成分を並べて見比べる

トレンド$T_t$と季節成分$S_t$が求まったので,残差$R_t$も計算できます.乗法モデルの式を変形すると,残差は元の値をトレンドと季節成分で割った残りになります.

R_t = \frac{y_t}{T_t \times S_t}

元の値 $y_t$ と3つの成分を,上から順に並べました.

04_tsr.png

  • トレンド$T_t$は,0.45から0.28へゆるやかに下がる曲線です
  • 季節成分$S_t$は,毎年まったく同じ形を繰り返しています.月ごとの平均から作っているので,年による違いはありません
  • 残差$R_t$は中央値が0.99で,4分の3の月が0.7〜1.3に収まっています.トレンドと季節成分で,元の値の動きをおおむね説明できているということです
  • 残差が大きく外れた月もあります.2022年11月は2.81,2019年12月は1.84と1を大きく上回り,2021年12月は0.40と大きく下回りました.季節成分の小さい冬の月や,その年だけの売れ方が残差に表れています

元の値の大きな変動は,ほとんどが季節成分$S_t$によるものです.一方,年ごとに山が低くなっていく動きは,トレンド$T_t$が受け持っています.

予測に使うモデル

ここからは,時系列の分解で求めたトレンドと季節成分を使って予測します.使う要素を1つずつ増やした,次の3つのモデルを用意しました.

  • モデル1(トレンドのみ)は,トレンドだけを使う最も単純なモデルです.時系列の分解で求めたトレンド$T_t$の曲線を,そのまま3年先まで延ばして予測値とします.季節による変動は考えません.
  • モデル2(季節ナイーブ)は,季節性だけを使うモデルです.前年の同じ月と同じ値になると予測する方法で,計算はほとんど必要ありません.今回は3年先まで予測するので,2023〜2025年のどの年にも,学習データの最後の年である2022年の値を使います.
  • モデル3(トレンド×季節)は,トレンドと季節性の両方を使うモデルです.モデル1と同じく延ばしたトレンドに,季節成分を掛けて予測します.

モデル3の予測は,次の式で表せます.

\hat{y}_t = \hat{T}_t \times S_{m(t)}
  • $\hat{y}_t$ :時点$t$の予測値
  • $\hat{T}_t$ :時点$t$まで延ばしたトレンドの値
  • $S_{m(t)}$ :時点$t$の月$m(t)$に対応する季節成分

評価

それでは実際に,3つのモデルを評価しましょう.まずは結果を数値で比べられるように,今回使う評価指標を説明します.そのうえで,テスト期間(2023〜2025年)の実測値と予測を比べます.

評価指標

今回は,MAEとRMSEの2つの指標を使います.どちらも予測と実測値のずれの大きさを表し,値が小さいほど良い予測です.

主に見るのはMAE(平均絶対誤差)です.予測が平均でどれだけずれたかを表し,単位も元のデータと同じなので直感的に読めます.

\mathrm{MAE} = \frac{1}{n}\sum_{t=1}^{n} \left| y_t - \hat{y}_t \right|
  • $n$ :テスト期間の月数(36か月)
  • $y_t$ :時点$t$の実測値
  • $\hat{y}_t$ :時点$t$の予測値

あわせてRMSE(二乗平均平方根誤差)も見ます.誤差を2乗してから平均するので,大きく外した月があると値が大きくなります.

\mathrm{RMSE} = \sqrt{\frac{1}{n}\sum_{t=1}^{n} \left( y_t - \hat{y}_t \right)^2}

評価結果

テスト期間の36か月について,3つのモデルの予測を実測値と重ねました.

08_comparison.png

モデル 使う要素 MAE RMSE
モデル1:トレンドのみ トレンド 0.218 0.249
モデル2:季節ナイーブ 季節性(前年同月) 0.082 0.135
モデル3:トレンド×季節 トレンドと季節性 0.063 0.086

図と表から,要素を足すごとに予測が実測値に近づき,MAEもRMSEも小さくなっていることがわかります.トレンドだけのモデル1は,夏の山を捉えられません.季節性だけのモデル2は山の時期が合うものの,2022年の高さをそのまま使うので,年々の減少を反映できません.トレンドと季節性を掛け合わせたモデル3は,減少に合わせて山も低くなるので,3つの中で最も実測値に近い予測になりました.

まとめ

いかがでしたでしょうか.この記事では,熊本県の蚊取り線香の購入点数を題材に,時系列データをトレンドと季節性に分けて予測する方法を紹介しました.

使った計算は,移動平均や最小二乗法といった基本的なものだけで,今回のデータ量であれば手元のノートPC(M2 Macを使用しました)で1秒もかからずに終わります.増減の傾向や時期ごとに売れ行きが変わるような商品があれば,同じ手順で予測してみてはいかがでしょうか.仕入れや発注の目安づくりに,ぜひ役立てもらえれば良いなと考えています.

ご拝読いただきありがとうございました!

出典

出典:RESAS(地域経済分析システム)「生活用品消費分析」熊本県・蚊取り線香(データ提供:株式会社True Data「True Data」,2026年10月6日閲覧)のデータを読み取り,筆者作成

本記事の作成にあたり,株式会社True Data様にはデータの利用を許諾いただきました.データを公開しているRESASとあわせて,心より感謝申し上げます.

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?