100本ノック / 機械学習 / 実務機械学習100本ノック
製造業の需要予測を強くする特徴量設計10選|Python実践100本ノック
製造業の需要予測を強くする特徴量設計 ― 受注履歴を意思決定情報へ変える10本ノック
本記事では、架空の部品メーカーの受注履歴を題材に、予測時点で本当に利用できる情報だけから特徴量を設計します。曜日や祝日、過去需要、顧客・商品特性を、在庫・要員・生産計画に使える形へ変換することが目的です。
対象は「機械学習100本ノック」第6章の No.051〜No.060 です。コードを動かすだけでなく、特徴量の業務上の意味、作り方、結果の読み方、データリークを防ぐ運用まで確認します。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。 掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
はじめに:この記事で扱う製造業の実務課題
部品メーカーでは、翌週の受注数量を予測し、材料手配、設備枠、残業、外注を決めます。しかし受注量の生データだけでは、月末集中、休日、顧客ごとの発注周期、商品ごとの需要水準をモデルへ伝えられません。本記事では「履歴を、予測に使える説明変数へ翻訳する」工程に焦点を当てます。
現場でよくある状況
- ERPから日付・顧客・商品・数量を抽出したが、そのままモデルへ入れている
- 全期間平均を特徴量にして、検証時だけ精度が高くなる
- 欠損したラグをゼロで埋め、休業日と履歴不足を混同する
- 特徴量の定義や利用可能時点が属人化している
なぜこの問題は判断が難しいのか
特徴量は予測精度だけで評価できません。計画確定時点に取得可能か、将来も同じ定義で生成できるか、現場が意味を説明できるかも重要です。とくに時系列では、未来の実績をわずかに混ぜただけで検証値が過大になります。
今回扱うノックの全体像
| No. | テーマ | 計画業務への接続 |
|---|---|---|
| 051 | 日付特徴量 | 曜日・月・四半期の周期 |
| 052 | 祝日・営業日 | 稼働可能日と休日影響 |
| 053 | ラグ | 直近・同曜日の需要 |
| 054 | 移動平均 | 基準需要と変動 |
| 055 | 顧客別集計 | 顧客固有の発注水準 |
| 056 | 商品別集計 | 品目固有の需要水準 |
| 057 | 比率 | 能力負荷・構成比 |
| 058 | 交互作用 | 条件の組合せ効果 |
| 059 | ターゲットエンコーディング | 高カーディナリティの数値化 |
| 060 | データリーク防止 | 本番再現性の確保 |
Python 環境の準備
numpy、pandas、matplotlib、scikit-learnを使います。乱数シードは固定し、グラフはMarkdown変換後も読みやすい設定にします。
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
SEED = 42
rng = np.random.default_rng(SEED)
plt.rcParams["figure.figsize"] = (9, 4.5)
plt.rcParams["axes.unicode_minus"] = False
print("numpy:", np.__version__)
print("pandas:", pd.__version__)
print("matplotlib:", matplotlib.__version__)
numpy: 2.5.1
pandas: 3.0.3
matplotlib: 3.11.0
架空データの作成
2024年1月から2025年6月まで、8顧客×5商品の日次レコードを作ります。目的変数は受注数量 order_qty。曜日、月末、顧客規模、商品人気、販促、休日の影響を含めます。capacity_qty はその日に割り当て可能な標準能力です。外部データは使いません。
dates = pd.date_range("2024-01-01", "2025-06-30", freq="D")
customers = [f"C{i:02d}" for i in range(1, 9)]
products = [f"P{i:02d}" for i in range(1, 6)]
idx = pd.MultiIndex.from_product([dates, customers, products], names=["date", "customer_id", "product_id"])
df = idx.to_frame(index=False)
customer_scale = {c: v for c, v in zip(customers, [0.75, 0.9, 1.05, 1.25, 1.5, 0.85, 1.15, 1.35])}
product_base = {p: v for p, v in zip(products, [18, 25, 34, 43, 55])}
product_capacity = {p: v for p, v in zip(products, [52, 58, 68, 78, 90])}
df["promotion"] = (rng.random(len(df)) < 0.045).astype(int)
df["unit_price"] = df["product_id"].map(dict(zip(products, [1280, 1650, 2100, 2480, 3150])))
# 日本の祝日を模した架空カレンダー(外部カレンダーには依存しない)
holiday_dates = pd.to_datetime([
"2024-01-01", "2024-01-08", "2024-02-12", "2024-02-23", "2024-04-29",
"2024-05-03", "2024-05-06", "2024-07-15", "2024-08-12", "2024-09-16",
"2024-10-14", "2024-11-04", "2025-01-01", "2025-01-13", "2025-02-11",
"2025-02-24", "2025-04-29", "2025-05-05", "2025-05-06"
])
weekday = df["date"].dt.dayofweek
is_holiday = df["date"].isin(holiday_dates)
business = ((weekday < 5) & ~is_holiday).astype(int)
month_end = (df["date"].dt.day >= 25).astype(int)
season = 1 + 0.10 * np.sin(2 * np.pi * (df["date"].dt.month - 1) / 12)
mu = (df["product_id"].map(product_base) * df["customer_id"].map(customer_scale)
* season * (1 + 0.18 * month_end) * (1 + 0.28 * df["promotion"])
* np.where(business == 1, 1.0, 0.16))
df["order_qty"] = rng.poisson(mu).astype(int)
df["capacity_qty"] = df["product_id"].map(product_capacity)
df["revenue"] = df["order_qty"] * df["unit_price"]
print("形状:", df.shape)
display(df.head())
形状: (21880, 8)
| date | customer_id | product_id | promotion | unit_price | order_qty | capacity_qty | revenue | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2024-01-01 | C01 | P01 | 0 | 1280 | 4 | 52 | 5120 |
| 1 | 2024-01-01 | C01 | P02 | 0 | 1650 | 4 | 58 | 6600 |
| 2 | 2024-01-01 | C01 | P03 | 0 | 2100 | 3 | 68 | 6300 |
| 3 | 2024-01-01 | C01 | P04 | 0 | 2480 | 7 | 78 | 17360 |
| 4 | 2024-01-01 | C01 | P05 | 0 | 3150 | 7 | 90 | 22050 |
No.051:日付から曜日・月・四半期を作る
実務での意味
日付は単なる識別子ではなく、生産・出荷の周期を表します。曜日は配車や締め、月は季節需要、四半期は予算・棚卸の周期をモデルへ伝えます。
分析・モデル化の考え方
日付 から曜日 、月 、四半期 を作ります。番号には大小関係を誤学習する余地があるため、モデルによってはカテゴリとして扱います。
Pythonで確認する
df["day_of_week"] = df["date"].dt.dayofweek
df["day_name"] = df["date"].dt.day_name()
df["month"] = df["date"].dt.month
df["quarter"] = df["date"].dt.quarter
calendar_summary = df.groupby("day_name", observed=True)["order_qty"].mean().reindex(
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
display(calendar_summary.rename("mean_order_qty").round(2).to_frame())
calendar_summary.plot(kind="bar", color="#4472C4")
plt.title("Average order quantity by day of week")
plt.xlabel("Day of week"); plt.ylabel("Average order quantity"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
| mean_order_qty | |
|---|---|
| day_name | |
| Monday | 35.70 |
| Tuesday | 39.81 |
| Wednesday | 40.99 |
| Thursday | 41.38 |
| Friday | 40.45 |
| Saturday | 6.64 |
| Sunday | 6.51 |

結果の読み取り
平日と週末の水準差が明確です。曜日特徴量により、モデルは休日直前の生産量や週明けの出荷負荷を区別できます。ただし「曜日が原因」と断定せず、締め時刻や配送便との対応を現場確認します。
No.052:祝日・営業日フラグを作る
実務での意味
カレンダー日数と実際の稼働可能日数は一致しません。休日を明示すると、需要減と設備停止を通常日の変動から分離できます。
分析・モデル化の考え方
祝日集合への所属を 、週末を とすると、営業日フラグは です。本番では会社カレンダー、臨時稼働、顧客別休日も管理します。
Pythonで確認する
df["is_holiday"] = df["date"].isin(holiday_dates).astype(int)
df["is_business_day"] = ((df["day_of_week"] < 5) & (df["is_holiday"] == 0)).astype(int)
business_summary = df.groupby("is_business_day")["order_qty"].agg(["mean", "median", "count"])
business_summary.index = ["non-business", "business"]
display(business_summary.round(2))
business_summary["mean"].plot(kind="bar", color=["#A5A5A5", "#70AD47"])
plt.title("Orders on business and non-business days")
plt.xlabel("Calendar class"); plt.ylabel("Average order quantity"); plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
| mean | median | count | |
|---|---|---|---|
| non-business | 6.57 | 6.0 | 7000 |
| business | 41.35 | 38.0 | 14880 |

結果の読み取り
非営業日の平均受注は営業日より大幅に小さくなりました。営業日フラグは材料・人員計画の前提になります。受注日と希望納期のどちらのカレンダーを使うかは、目的変数に合わせて定義します。
No.053:ラグ特徴量を作る
実務での意味
昨日や前週同曜日の受注量は、短期計画の強い手掛かりです。急な増減を捉え、購買・生産担当者の経験則を再現します。
分析・モデル化の考え方
系列ごとに を作ります。ここでは顧客×商品単位で1日前と7日前を参照します。shiftを先に行うことが、当日の実績混入を防ぐ要点です。
Pythonで確認する
df = df.sort_values(["customer_id", "product_id", "date"]).copy()
series_keys = ["customer_id", "product_id"]
df["lag_1"] = df.groupby(series_keys)["order_qty"].shift(1)
df["lag_7"] = df.groupby(series_keys)["order_qty"].shift(7)
sample = df.query("customer_id == 'C05' and product_id == 'P05'").tail(12)
display(sample[["date", "order_qty", "lag_1", "lag_7"]])
plt.plot(sample["date"], sample["order_qty"], marker="o", label="actual")
plt.plot(sample["date"], sample["lag_7"], marker="s", label="lag 7")
plt.title("Actual orders and 7-day lag"); plt.xlabel("Date"); plt.ylabel("Order quantity")
plt.grid(alpha=.3); plt.legend(); plt.xticks(rotation=30); plt.tight_layout(); plt.show()
| date | order_qty | lag_1 | lag_7 | |
|---|---|---|---|---|
| 21424 | 2025-06-19 | 86 | 94.0 | 80.0 |
| 21464 | 2025-06-20 | 81 | 86.0 | 61.0 |
| 21504 | 2025-06-21 | 11 | 81.0 | 15.0 |
| 21544 | 2025-06-22 | 12 | 11.0 | 15.0 |
| 21584 | 2025-06-23 | 95 | 12.0 | 71.0 |
| 21624 | 2025-06-24 | 89 | 95.0 | 89.0 |
| 21664 | 2025-06-25 | 103 | 89.0 | 94.0 |
| 21704 | 2025-06-26 | 100 | 103.0 | 86.0 |
| 21744 | 2025-06-27 | 94 | 100.0 | 81.0 |
| 21784 | 2025-06-28 | 19 | 94.0 | 11.0 |
| 21824 | 2025-06-29 | 16 | 19.0 | 12.0 |
| 21864 | 2025-06-30 | 105 | 16.0 | 95.0 |

結果の読み取り
7日前の値は同じ曜日の水準を追随します。欠損は系列先頭で履歴がない印なので、機械的なゼロ補完ではなく、学習対象から外すか「履歴不足」フラグを併用します。
No.054:移動平均特徴量を作る
実務での意味
単日の受注は偶然変動します。移動平均は基準需要を安定させ、安全在庫や能力計画の判断を支えます。
分析・モデル化の考え方
直近 日の平均は 。当日を含めないため shift(1).rolling(w) の順で計算します。
Pythonで確認する
g = df.groupby(series_keys)["order_qty"]
df["rolling_mean_7"] = g.transform(lambda s: s.shift(1).rolling(7, min_periods=3).mean())
df["rolling_mean_28"] = g.transform(lambda s: s.shift(1).rolling(28, min_periods=7).mean())
sample = df.query("customer_id == 'C05' and product_id == 'P05'").tail(60)
plt.plot(sample["date"], sample["order_qty"], alpha=.45, label="actual")
plt.plot(sample["date"], sample["rolling_mean_7"], label="7-day mean")
plt.plot(sample["date"], sample["rolling_mean_28"], label="28-day mean")
plt.title("Short- and medium-term demand baselines"); plt.xlabel("Date"); plt.ylabel("Order quantity")
plt.grid(alpha=.3); plt.legend(); plt.xticks(rotation=30); plt.tight_layout(); plt.show()

結果の読み取り
7日平均は直近変化へ速く反応し、28日平均は安定しています。短期配員には前者、月次購買には後者など、意思決定のリードタイムに合わせて窓幅を選びます。
No.055:顧客別の集計特徴量を作る
実務での意味
顧客ごとに発注規模や変動性が異なります。過去平均とばらつきは、重点顧客の負荷見込みや予測区間の設計に使えます。
分析・モデル化の考え方
各顧客について予測日より前の累積統計を使います。拡張窓平均は であり、全期間平均を一括結合しない点が重要です。
Pythonで確認する
customer_daily = df.groupby(["date", "customer_id"], as_index=False)["order_qty"].sum()
customer_daily = customer_daily.sort_values(["customer_id", "date"])
customer_daily["customer_past_mean"] = customer_daily.groupby("customer_id")["order_qty"].transform(
lambda s: s.shift(1).expanding().mean())
customer_profile = customer_daily.groupby("customer_id").agg(
latest_past_mean=("customer_past_mean", "last"), observed_std=("order_qty", "std"))
display(customer_profile.round(2).sort_values("latest_past_mean", ascending=False))
customer_profile["latest_past_mean"].sort_values().plot(kind="barh", color="#ED7D31")
plt.title("Historical demand level by customer"); plt.xlabel("Past mean daily quantity"); plt.ylabel("Customer")
plt.grid(axis="x", alpha=.3); plt.tight_layout(); plt.show()
| latest_past_mean | observed_std | |
|---|---|---|
| customer_id | ||
| C05 | 206.99 | 115.22 |
| C08 | 184.89 | 102.48 |
| C04 | 171.93 | 95.56 |
| C07 | 158.00 | 87.48 |
| C03 | 144.15 | 79.93 |
| C02 | 122.89 | 68.19 |
| C06 | 116.22 | 64.75 |
| C01 | 102.44 | 57.00 |

結果の読み取り
C05など高水準顧客を識別できました。平均だけでなく標準偏差や最終発注からの日数を併用すると、能力確保と営業フォローを分けて判断できます。
No.056:商品別の集計特徴量を作る
実務での意味
商品ごとの需要規模・変動性は、材料所要量や段取り計画の基本情報です。品目コードを数値化するだけでは、この水準差を直接表せません。
分析・モデル化の考え方
商品別日次需要を作り、過去のみの累積平均を計算します。新商品では履歴が少ないため、商品群平均へ縮約する運用も検討します。
Pythonで確認する
product_daily = df.groupby(["date", "product_id"], as_index=False)["order_qty"].sum()
product_daily = product_daily.sort_values(["product_id", "date"])
product_daily["product_past_mean"] = product_daily.groupby("product_id")["order_qty"].transform(
lambda s: s.shift(1).expanding().mean())
product_profile = product_daily.groupby("product_id").agg(
latest_past_mean=("product_past_mean", "last"), max_daily=("order_qty", "max"))
display(product_profile.round(2))
product_profile["latest_past_mean"].plot(kind="bar", color="#5B9BD5")
plt.title("Historical demand level by product"); plt.xlabel("Product"); plt.ylabel("Past mean daily quantity")
plt.grid(axis="y", alpha=.3); plt.tight_layout(); plt.show()
| latest_past_mean | max_daily | |
|---|---|---|
| product_id | ||
| P01 | 124.02 | 228 |
| P02 | 172.20 | 318 |
| P03 | 235.02 | 428 |
| P04 | 296.70 | 555 |
| P05 | 379.57 | 672 |

結果の読み取り
P05はP01より大きな需要水準です。この差を特徴量化するとモデルは品目別の基準線を持てます。ただし廃番・価格改定後も古い履歴を同じ重みで使うかは見直しが必要です。
No.057:比率特徴量を作る
実務での意味
絶対数量だけでは「能力に対して重いか」を判断できません。負荷率や顧客内構成比は、逼迫と重要度を共通尺度で比較できます。
分析・モデル化の考え方
負荷率を とします。分母がゼロまたは未確定なら欠損として扱い、微小値で無理に割らないことが重要です。ここでは予測用として7日平均需要を能力で割ります。
Pythonで確認する
df["demand_capacity_ratio"] = df["rolling_mean_7"].div(df["capacity_qty"].replace(0, np.nan))
ratio_summary = df.groupby("product_id")["demand_capacity_ratio"].agg(["mean", "median", "max"])
display(ratio_summary.round(3))
plot_data = df.dropna(subset=["demand_capacity_ratio"]).sample(2500, random_state=SEED)
plt.hist(plot_data["demand_capacity_ratio"], bins=30, color="#FFC000", edgecolor="white")
plt.axvline(1.0, color="red", linestyle="--", label="capacity threshold")
plt.title("Distribution of expected load ratio"); plt.xlabel("7-day mean / standard capacity"); plt.ylabel("Frequency")
plt.grid(axis="y", alpha=.3); plt.legend(); plt.tight_layout(); plt.show()
| mean | median | max | |
|---|---|---|---|
| product_id | |||
| P01 | 0.298 | 0.291 | 0.585 |
| P02 | 0.371 | 0.362 | 0.687 |
| P03 | 0.433 | 0.422 | 0.845 |
| P04 | 0.476 | 0.462 | 0.842 |
| P05 | 0.528 | 0.514 | 0.941 |

結果の読み取り
1.0超は標準能力を上回る見込みです。高負荷の頻度を品目別に見ることで、残業・外注・前倒しの検討対象を絞れます。能力値の更新頻度と単位整合は必ず管理します。
No.058:交互作用特徴量を作る
実務での意味
販促効果は一律ではなく、月末や特定商品で強まることがあります。単独特徴量だけでは表しにくい「組合せ条件」を明示します。
分析・モデル化の考え方
2変数 の積 を追加します。ここでは promotion × month_end と business_day × lag_7 を作り、販促集中と営業日上の前週需要を表現します。
Pythonで確認する
df["is_month_end"] = (df["date"].dt.day >= 25).astype(int)
df["promotion_month_end"] = df["promotion"] * df["is_month_end"]
df["business_lag_7"] = df["is_business_day"] * df["lag_7"]
interaction_summary = df.groupby(["promotion", "is_month_end"])["order_qty"].mean().unstack()
interaction_summary.columns = ["not_month_end", "month_end"]
display(interaction_summary.round(2))
interaction_summary.plot(kind="bar", color=["#A5A5A5", "#C00000"])
plt.title("Interaction of promotion and month-end"); plt.xlabel("Promotion flag"); plt.ylabel("Average order quantity")
plt.grid(axis="y", alpha=.3); plt.legend(); plt.tight_layout(); plt.show()
| not_month_end | month_end | |
|---|---|---|
| promotion | ||
| 0 | 28.57 | 34.89 |
| 1 | 36.13 | 41.03 |

結果の読み取り
販促かつ月末の組合せで平均数量が最大です。交互作用は施策効果の仮説をモデルへ渡せますが、候補を無制限に増やすと過学習するため、現場仮説と検証で絞ります。
No.059:ターゲットエンコーディングを使う
実務での意味
顧客・商品・設備などカテゴリ数が多い場合、One-Hot Encodingは列数を増やします。過去の目的変数水準でカテゴリを1列に要約できます。
分析・モデル化の考え方
カテゴリ の値を平滑化平均 に置換します。ここでは各行より前だけで、顧客×商品の累積和・件数と全体事前平均から計算します。 は少数カテゴリを全体平均へ寄せる強さです。
Pythonで確認する
df = df.sort_values("date").copy()
cat_key = df["customer_id"] + "_" + df["product_id"]
past_sum = df.groupby(cat_key)["order_qty"].cumsum() - df["order_qty"]
past_count = df.groupby(cat_key).cumcount()
global_past_sum = df["order_qty"].cumsum() - df["order_qty"]
global_past_count = np.arange(len(df))
global_prior = global_past_sum.div(pd.Series(global_past_count, index=df.index).replace(0, np.nan))
smoothing = 30
df["target_encoding"] = (past_sum + smoothing * global_prior) / (past_count + smoothing)
te_summary = df.groupby(["customer_id", "product_id"])["target_encoding"].last().unstack()
display(te_summary.round(2))
plt.imshow(te_summary, aspect="auto", cmap="Blues")
plt.colorbar(label="Past target mean"); plt.xticks(range(len(te_summary.columns)), te_summary.columns)
plt.yticks(range(len(te_summary.index)), te_summary.index)
plt.title("Leakage-safe target encoding"); plt.xlabel("Product"); plt.ylabel("Customer")
plt.grid(False); plt.tight_layout(); plt.show()
| product_id | P01 | P02 | P03 | P04 | P05 |
|---|---|---|---|---|---|
| customer_id | |||||
| C01 | 11.62 | 15.37 | 20.65 | 25.38 | 31.95 |
| C02 | 13.37 | 17.92 | 24.23 | 30.20 | 38.64 |
| C03 | 15.59 | 21.16 | 28.28 | 34.99 | 44.49 |
| C04 | 18.31 | 25.00 | 33.46 | 41.62 | 52.44 |
| C05 | 21.55 | 29.67 | 39.95 | 49.89 | 63.02 |
| C06 | 12.86 | 17.41 | 22.76 | 28.41 | 36.60 |
| C07 | 17.20 | 22.92 | 30.61 | 38.53 | 48.38 |
| C08 | 19.65 | 26.36 | 35.42 | 44.82 | 56.87 |

結果の読み取り
顧客×商品の需要水準を1列で表せました。新規カテゴリは全体平均へフォールバックします。学習・検証を無作為分割してから全体平均を計算するとリークするため、時系列順またはfold外で算出します。
No.060:データリークを防ぐ
実務での意味
検証精度が高くても、本番時点で未知の情報を使っていれば計画には使えません。リーク防止はアルゴリズム選定より前の品質要件です。
分析・モデル化の考え方
時点 で学習し、 より後を検証します。危険な特徴量 leaky_target は結合ミスで残った当日の目的変数そのもの、安全な safe_roll は1日シフト後の移動平均です。両者のMAE差から過大評価を確認します。
Pythonで確認する
daily = df.groupby("date", as_index=False)["order_qty"].sum().sort_values("date")
daily["leaky_target"] = daily["order_qty"] # 予測時には未知。説明用の典型的リーク
daily["safe_roll"] = daily["order_qty"].shift(1).rolling(7, min_periods=7).mean()
daily = daily.dropna().copy()
cutoff = pd.Timestamp("2025-04-01")
train = daily[daily["date"] < cutoff]
test = daily[daily["date"] >= cutoff]
results = []
predictions = {}
for feature in ["leaky_target", "safe_roll"]:
model = RandomForestRegressor(n_estimators=150, min_samples_leaf=4, random_state=SEED, n_jobs=1)
model.fit(train[[feature]], train["order_qty"])
pred = model.predict(test[[feature]])
predictions[feature] = pred
results.append({"feature": feature, "MAE": mean_absolute_error(test["order_qty"], pred)})
display(pd.DataFrame(results).set_index("feature").round(2))
view = test.iloc[:45]
plt.plot(view["date"], view["order_qty"], label="actual", color="black")
plt.plot(view["date"], predictions["leaky_target"][:45], label="leaky feature")
plt.plot(view["date"], predictions["safe_roll"][:45], label="safe feature")
plt.title("Apparent accuracy caused by target leakage"); plt.xlabel("Date"); plt.ylabel("Total order quantity")
plt.grid(alpha=.3); plt.legend(); plt.xticks(rotation=30); plt.tight_layout(); plt.show()
| MAE | |
|---|---|
| feature | |
| leaky_target | 3.34 |
| safe_roll | 613.13 |

結果の読み取り
目的変数そのものを残した特徴量は不自然に良いMAEを示しますが、予測時には作れません。特徴量ごとに「発生時刻・確定時刻・取得時刻」を記録し、時系列分割で検証し、学習と推論で同じ生成処理を使うことが必要です。
対象ノックを通して見える実務上の示唆
特徴量設計は、データを増やす作業ではなく、業務の時間軸と意思決定をモデルへ写す作業です。暦特徴量は操業周期、ラグと移動平均は需要の記憶、顧客・商品集計は構造差、比率は制約、交互作用は業務仮説を表します。
精度改善の優先順位は、まず利用可能時点を守ること、次に意思決定のリードタイムと粒度を揃えること、その後に特徴量を増やすことです。検証値だけでなく、欠品、残業、在庫、計画変更回数といった業務KPIへの効果で採否を決めます。
実務導入する場合に必要なこと
- 予測仕様:対象、粒度、予測日、予測期間、計画確定時刻を定義する
- データ時刻管理:各項目の発生・確定・取得時刻と訂正ルールを管理する
- 特徴量台帳:定義、単位、窓幅、欠損処理、責任者を残す
- 再現可能な処理:学習と推論で同じコードを使い、履歴スナップショットを保存する
- 時系列検証:繁忙期・休暇期を含む複数期間で、MAEと業務KPIを評価する
- 監視と更新:新商品、顧客構成、能力、カレンダー変更による分布変化を監視する
まとめ
No.051〜060では、生の日付・受注履歴を製造計画に意味のある特徴量へ変換しました。最重要ポイントは、すべての特徴量を予測時点より前の情報で作ることです。小さく始めるなら、営業日、7日ラグ、7日移動平均、顧客・商品別の過去平均から導入し、時系列検証で効果を確認するとよいでしょう。
法人向けのご相談
需要予測や在庫・生産計画では、モデルそのものより、データ定義、特徴量、検証、現場運用の接続が成果を左右します。数理工房では、課題整理、データ診断、PoC、業務実装、企業研修まで、保有データと意思決定プロセスに合わせてご支援します。
📩 お問い合わせ: surikobo.co.jp/contact まずはお気軽にご相談ください。