背景
在汽后行业的供应链管理中,精准的需求预测是后续管理及决策的基础。各个汽后配件即为一个库存单位(SKU,Stock Keeping Unit)。如果可以准确预知未来对于各个配件的市场需求,就可以提前将库存放在靠近需求的仓库中,从而降低库存成本,同时保证订单的按时履约。
在需求预测问题中,一般通过历史一段时间的订单情况,结合各配件的属性,得到未来一段时间的预测值。汽后行业所面临的需求预测问题可以分为两类:
- 点预测:通过分析历史需求,得到未来一段时间的确定性预估。常用的评价指标为1-wmape和1-smape,定义如下:
1 − wmape = 1 − ∑ ∣ y i − y ^ i ∣ ∑ y i 1 - \text{wmape} = 1 - \frac{\sum |y_i - \hat{y}_i|}{\sum y_i} 1−wmape=1−∑yi∑∣yi−y^i∣
1 − smape = 1 − 2 × ∑ ∣ y i − y ^ i ∣ ∑ ( y i + y ^ i ) 1 - \text{smape} = 1 - 2 \times \frac{\sum |y_i - \hat{y}_i|}{\sum (y_i + \hat{y}_i)} 1−smape=1−2×∑(yi+y^i)∑∣yi−y^i∣
其中, y i y_i yi为样本点的实际值, h a t y i hat{y}_i hatyi为样本点的预测值,N为预测样本点的数目。
- 区间预测:通过分析历史需求的分布情况,得到未来一段时间的区间预测,对未来的不确定性(分位数)进行预估。例如给出90%的分位数预测,表示我们认为未来需求量不超过该预测值的概率为90%。
以上两类预测问题均有着广泛的应用背景。然而,汽后领域会面临历史需求数据的诸多挑战,其中一个比较普遍的问题是数据的间断性,即在时间序列中存在大量的0值,造成整体需求序列的不稳定性。对于此类需求序列的划分及处理策略,往往决定着汽后配件需求预测的准确性。
问题
附件为一份某汽后商家的“历史配件订单表”,包含了2022年1月1日至2023年7月31日wh1仓库的订单,订单中已指明配件(SKU)编码及需求量,可用于预测后续各配件在本仓库的需求量。其中同一天、同一仓库、同一配件,可能一天会存在多个订单,则当天该仓库对该配件的需求量可进行加和处理,若不存在订单,则当天需求量为0。
根据以上信息,请你们建立数学模型完成以下问题:
-
问题 1:使用“历史配件订单表”中的数据,预测出各商家在本仓库的配件2023年8月1日至2023年8月31日的需求量。请将预测结果以表格的形式列在正文中,并说明你们如何评价历史回测期间的准确率。
-
问题 2:使用“历史配件订单表”中的数据,对各商家在本仓库的配件2023年8月1日至2023年8月15日的需求量进行区间预测,分别给出各配件在本仓库10%、30%、70%、90%分位数的预测值。请将预测结果以表格的形式列在正文中。请说明解决此问题时,给出了哪些基本假设。同时讨论你们如何评价历史回测期间,各分位数预测的准确率。
-
问题 3:使用“历史配件订单表”中的数据,根据数据分析及建模过程,这些由配件日需求量形成的时间序列如何分类,研究每一类的特征,从而帮助你们进行更加精准的预测。
详细思路(供参考)
数据预处理
-
读取数据:
- 读取“历史配件订单表”,处理编码问题确保数据正确读取。
-
数据清洗:
- 合并同一天、同一仓库、同一配件的订单,计算每天的需求总量。
- 处理缺失值,将没有订单的日期需求量记为0。
-
生成完整日期数据:
- 生成2022年1月1日至2023年7月31日的所有日期。
- 确保每个配件在每个日期都有记录,将缺失日期的需求量记为0。
import pandas as pd
# 读取数据
data = pd.read_csv('历史配件订单表.csv', encoding='gbk')
# 重命名列名
data.columns = ['仓库编码', '配件编码', '日期', '需求量']
# 转换日期格式
data['日期'] = pd.to_datetime(data['日期'], format='%Y/%m/%d')
# 汇总同一天、同一仓库、同一配件的需求量
data_agg = data.groupby(['仓库编码', '配件编码', '日期']).sum().reset_index()
# 生成所有日期
all_dates = pd.date_range(start='2022-01-01', end='2023-07-31')
# 生成仓库和配件的唯一组合
unique_pairs = data_agg[['仓库编码', '配件编码']].drop_duplicates()
# 创建所有日期和配件组合的笛卡尔积
full_index = pd.MultiIndex.from_product([unique_pairs['仓库编码'], unique_pairs['配件编码'], all_dates], names=['仓库编码', '配件编码', '日期'])
# 创建完整的数据框
full_data = pd.DataFrame(index=full_index).reset_index()
# 合并原始数据和完整的数据框
full_data = pd.merge(full_data, data_agg, on=['仓库编码', '配件编码', '日期'], how='left')
# 填补缺失值
full_data['需求量'] = full_data['需求量'].fillna(0)
# 显示数据前几行
full_data.head()
如下:
问题 1: 点预测
步骤和思路:
-
特征工程:
- 提取时间序列特征,如月份、星期、节假日等。
- 考虑滞后特征,如前几天的需求量。
-
模型选择:
- 选择适合时间序列预测的模型,如ARIMA、SARIMA、Prophet等。
- 结合机器学习模型,如随机森林、XGBoost等进行建模,利用交叉验证选择最佳模型。
-
模型训练和预测:
- 使用2022年1月1日至2023年7月31日的数据进行训练。
- 预测2023年8月1日至2023年8月31日的需求量。
-
模型评价:
- 使用1-wmape和1-smape作为评价指标,计算历史回测期间的准确率。
from statsmodels.tsa.statespace.sarimax import SARIMAX
from sklearn.metrics import mean_absolute_error
# 定义评价指标
def wmape(y_true, y_pred):
return 1 - (abs(y_true - y_pred).sum() / y_true.sum())
def smape(y_true, y_pred):
return 1 - 2 * (abs(y_true - y_pred).sum() / (y_true + y_pred).sum())
# 训练SARIMA模型并预测
model = SARIMAX(full_data['需求量'], order=(1, 1, 1), seasonal_order=(1, 1, 1, 12))
result = model.fit()
# 预测
forecast = result.predict(start=len(full_data), end=len(full_data) + 30)
# 计算评价指标
y_true = full_data['需求量'][-31:]
y_pred = forecast[:31]
wmape_score = wmape(y_true, y_pred)
smape_score = smape(y_true, y_pred)
print(f"1-wmape: {wmape_score}, 1-smape: {smape_score}")
问题 2: 区间预测
步骤和思路:
-
特征工程和模型选择:
- 同问题1的数据预处理和特征工程。
- 选择适合区间预测的模型,如Quantile Regression Forests、Gradient Boosting等。
- 使用分位数回归方法,分别预测10%、30%、70%、90%的分位数。
-
模型训练和预测:
- 使用2022年1月1日至2023年7月31日的数据进行训练。
- 预测2023年8月1日至2023年8月15日的需求量的分位数。
-
模型评价:
- 对历史数据进行回测,计算各分位数的预测准确率。
- 使用如Pinball Loss等分位数预测的评价指标。
基本假设:
- 历史需求数据能够代表未来的需求分布。
- 各个时间点的需求是独立同分布的。
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor
# 定义分位数回归模型
quantiles = [0.1, 0.3, 0.7, 0.9]
models = {q: GradientBoostingRegressor(loss='quantile', alpha=q) for q in quantiles}
# 训练模型
X_train = full_data.drop(columns=['需求量'])
y_train = full_data['需求量']
for q in quantiles:
models[q].fit(X_train, y_train)
# 预测
X_test = ... # 生成预测区间的特征数据
predictions = {q: models[q].predict(X_test) for q in quantiles}
# 计算分位数预测的Pinball Loss
def pinball_loss(y_true, y_pred, q):
return np.mean([max(q * (yt - yp), (q - 1) * (yt - yp)) for yt, yp in zip(y_true, y_pred)])
y_test = ... # 提供真实需求量数据
pinball_losses = {q: pinball_loss(y_test, predictions[q], q) for q in quantiles}
print(pinball_losses)
问题 3: 时间序列分类
步骤和思路:
-
数据预处理: 同问题1的数据预处理。
-
时间序列聚类:
- 使用时间序列聚类方法,如K-means、DBSCAN等对SKU进行聚类。
- 提取时间序列特征,如季节性、趋势性、波动性等,进行分类。
-
特征分析:
- 对每一类的时间序列特征进行分析,总结各类的特征。
- 根据各类特征选择适合的预测模型。
from tslearn.clustering import TimeSeriesKMeans
from tslearn.preprocessing import TimeSeriesScalerMeanVariance
# 数据标准化
scaler = TimeSeriesScalerMeanVariance()
data_scaled = scaler.fit_transform(full_data['需求量'].values.reshape(-1, 1))
# 时间序列聚类
kmeans = TimeSeriesKMeans(n_clusters=3, metric="dtw")
clusters = kmeans.fit_predict(data_scaled)
# 添加聚类结果到数据框
full_data['聚类'] = clusters
# 分析每一类的特征
for cluster in range(3):
cluster_data = full_data[full_data['聚类'] == cluster]
print(cluster_data.describe())
更多数学建模支持
- 查看http://share.mosha.cloud 网站使用gpt4来获取更多的数学建模支持和资源。
- 书籍推荐:《Python3编程从零基础到实战》 ,《Python网络爬虫入门到实战》
- 社群:592697532