基于Facebook Prophet的多SKU月度时间序列预测问题求助
问题描述
复刻多时间序列Prophet预测代码时遇到两个问题:
- 输入是月度时间序列,但预测输出为日度结果
- 预测值
yhat出现负值
数据集
{'Date': {0: '2019-01-01', 1: '2019-02-01', 2: '2019-03-01', 3: '2019-04-01', 4: '2019-05-01', 5: '2019-06-01', 6: '2019-07-01', 7: '2019-08-01', 8: '2019-09-01', 9: '2019-10-01', 10: '2019-11-01', 11: '2019-12-01', 12: '2020-01-01', 13: '2020-02-01', 14: '2020-03-01', 15: '2020-04-01', 16: '2020-05-01', 17: '2020-06-01', 18: '2020-07-01', 19: '2020-08-01', 20: '2020-09-01', 21: '2020-10-01', 22: '2020-11-01', 23: '2020-12-01', 24: '2021-01-01', 25: '2021-02-01', 26: '2021-03-01', 27: '2021-04-01', 28: '2021-05-01', 29: '2021-06-01', 30: '2021-07-01', 31: '2021-08-01', 32: '2021-09-01', 33: '2021-10-01', 34: '2021-11-01', 35: '2021-12-01', 36: '2022-01-01', 37: '2022-02-01', 38: '2022-03-01', 39: '2022-04-01', 40: '2022-05-01', 41: '2022-06-01', 42: '2022-07-01', 43: '2022-08-01', 44: '2022-09-01'}, 'XYZ|419': {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 791, 11: 833, 12: 478, 13: 343, 14: 543, 15: 560, 16: 427, 17: 302, 18: 391, 19: 279, 20: 405, 21: 580, 22: 824, 23: 767, 24: 1102, 25: 1000, 26: 1032, 27: 668, 28: 540, 29: 477, 30: 353, 31: 427, 32: 28, 33: 2, 34: 914, 35: 718, 36: 44, 37: 0, 38: 0, 39: 0, 40: 0, 41: 0, 42: 0, 43: 0, 44: 0}, 'XYZ|426': {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 29, 18: 374, 19: 330, 20: 402, 21: 1005, 22: 1533, 23: 1582, 24: 1824, 25: 1168, 26: 193, 27: 895, 28: 613, 29: 651, 30: 267, 31: 233, 32: 135, 33: 173, 34: 564, 35: 789, 36: 343, 37: 275, 38: 383, 39: 181, 40: 96, 41: 499, 42: 53, 43: 84, 44: 23}, 'XYZ|465': {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 44, 18: 292, 19: 240, 20: 364, 21: 806, 22: 1110, 23: 1232, 24: 1207, 25: 753, 26: 571, 27: 731, 28: 0, 29: 174, 30: 0, 31: 23, 32: 86, 33: 31, 34: 559, 35: 857, 36: 316, 37: 217, 38: 182, 39: 93, 40: 50, 41: 323, 42: 42, 43: 48, 44: 23}, 'XYZ|489': {0: 481, 1: 179, 2: 295, 3: 187, 4: 180, 5: 78, 6: 535, 7: 164, 8: 172, 9: 340, 10: 495, 11: 445, 12: 469, 13: 230, 14: 163, 15: 187, 16: 222, 17: 147, 18: 154, 19: 140, 20: 194, 21: 379, 22: 402, 23: 533, 24: 659, 25: 545, 26: 269, 27: 277, 28: 187, 29: 4, 30: 80, 31: 149, 32: 129, 33: 192, 34: 396, 35: 446, 36: 0, 37: 0, 38: 0, 39: 0, 40: 0, 41: 0, 42: 0, 43: 0, 44: 0}, 'XYZ|457': {0: 181, 1: 80, 2: 74, 3: 150, 4: 665, 5: 187, 6: 335, 7: 238, 8: 149, 9: 281, 10: 696, 11: 440, 12: 619, 13: 349, 14: 310, 15: 396, 16: 251, 17: 202, 18: 165, 19: 176, 20: 166, 21: 249, 22: 167, 23: 364, 24: 411, 25: 327, 26: 326, 27: 396, 28: 6, 29: 107, 30: 177, 31: 136, 32: 6, 33: 0, 34: 0, 35: 0, 36: 0, 37: 0, 38: 0, 39: 0, 40: 0, 41: 0, 42: 0, 43: 0, 44: 0}}
原始代码
import pandas as pd import numpy as np from prophet import Prophet import seaborn as sns import matplotlib.pyplot as plt from tqdm import tqdm from time import time df = pd.read_excel ('Sample_Data.xlsx') print (df) df = df.reset_index() Dataframe = pd.melt(df,id_vars='Date',value_vars=['XYZ|419','XYZ|426','XYZ|465','XYZ|489','XYZ|457']) SKU_List = ['XYZ|419','XYZ|426','XYZ|465','XYZ|489','XYZ|457'] Dataframe.columns = ['ds','SKU','y'] Dataframe.head() Dataframe.info() group_by_SKU = Dataframe.groupby('SKU') type(group_by_SKU) group_by_SKU.describe() group_by_SKU.groups.keys() def train_and_forecast(group): m=Prophet() m.fit(group) future=m.make_future_dataframe(periods=365) forecast=m.predict(future)[['ds','yhat','yhat_lower','yhat_upper']] forecast['SKU'] = group['SKU'].iloc[0] return forecast[['ds', 'SKU', 'yhat', 'yhat_upper', 'yhat_lower']] start_time=time() for_loop_forecast = pd.DataFrame() for SKU in SKU_List: group = group_by_SKU.get_group(SKU) forecast = train_and_forecast(group) for_loop_forecast=pd.concat((for_loop_forecast,forecast)) print('The time used for the for-loop forecast is ', time()-start_time) for_loop_forecast*
输出说明
- 加载Excel后的输出:

- 数据融合后的输出:

- DataFrame.info()输出:

- 模型拟合后的最终输出:

问题原因分析
- 日度预测输出:
make_future_dataframe(periods=365)默认采用日度频率生成未来日期,因此输出结果为日度数据,与输入的月度序列不匹配。此外,原始代码未将ds列转换为datetime类型,可能导致Prophet对时间序列的识别出现偏差。 - 负值预测:Prophet默认使用线性增长模型,当历史数据存在下降趋势或大量0值时,模型可能会外推出负值。同时,原始代码未对预测值设置非负约束。
解决方法
1. 生成月度预测输出
- 先将
ds列转换为datetime类型,确保Prophet正确识别时间序列:Dataframe['ds'] = pd.to_datetime(Dataframe['ds']) - 修改
make_future_dataframe的参数,指定月度频率freq='M',并调整periods为需要预测的月份数(例如12个月):future = m.make_future_dataframe(periods=12, freq='M')
2. 避免预测值为负
有两种可行方案:
方案一:使用Logistic增长模型约束非负
在训练数据和未来数据中添加floor(下限设为0)和cap(上限可设为历史最大值的1.2倍,避免过度约束),并指定growth='logistic':
def train_and_forecast(group): group['floor'] = 0 group['cap'] = group['y'].max() * 1.2 m = Prophet(growth='logistic') m.fit(group) future = m.make_future_dataframe(periods=12, freq='M') future['floor'] = 0 future['cap'] = group['y'].max() * 1.2 forecast = m.predict(future)[['ds','yhat','yhat_lower','yhat_upper']] forecast['SKU'] = group['SKU'].iloc[0] return forecast[['ds', 'SKU', 'yhat', 'yhat_upper', 'yhat_lower']]
方案二:预测后修正负值
如果不想修改增长模型,可以在预测完成后将所有负值替换为0:
forecast['yhat'] = forecast['yhat'].apply(lambda x: max(x, 0)) forecast['yhat_lower'] = forecast['yhat_lower'].apply(lambda x: max(x, 0)) forecast['yhat_upper'] = forecast['yhat_upper'].apply(lambda x: max(x, 0))
完整修正代码
import pandas as pd import numpy as np from prophet import Prophet import seaborn as sns import matplotlib.pyplot as plt from tqdm import tqdm from time import time df = pd.read_excel('Sample_Data.xlsx') # 转换日期列为datetime类型 df['Date'] = pd.to_datetime(df['Date']) Dataframe = pd.melt(df, id_vars='Date', value_vars=['XYZ|419','XYZ|426','XYZ|465','XYZ|489','XYZ|457']) SKU_List = ['XYZ|419','XYZ|426','XYZ|465','XYZ|489','XYZ|457'] Dataframe.columns = ['ds','SKU','y'] # 确保ds列是datetime类型 Dataframe['ds'] = pd.to_datetime(Dataframe['ds']) group_by_SKU = Dataframe.groupby('SKU') def train_and_forecast(group): # 使用Logistic增长约束预测值非负 group['floor'] = 0 group['cap'] = group['y'].max() * 1.2 m = Prophet(growth='logistic') m.fit(group) # 生成12个月的未来月度数据 future = m.make_f
相关产品推荐
相关产品推荐

