Prophet办公出勤预测优化:解决预测值为负与拟合度不足问题
办公出勤人数Prophet建模优化方案
问题背景
使用Python的Prophet库对15分钟间隔的办公出勤人数建模,已纳入节假日因素,每日、周度趋势拟合准确,但存在两个核心问题:
- 预测值出现现实中不可能的负数
- 观测数据峰值远超预测置信区间
原建模代码如下:
m = Prophet(holidays=holidays, weekly_seasonality=True, growth="flat") # "logistic") p_df = sdf[["OCCUPANCY_AVG"]].reset_index() p_df.columns = ["ds", "y"] p_df["cap"] = 180 df["floor"] = 0 m.fit(p_df) future = m.make_future_dataframe(periods=24 * 60, freq="H") future["cap"] = 180 future["floor"] = 0 forecast = m.predict(future) fig1 = m.plot(forecast, xlabel="Date", ylabel="Headcount", include_legend=True) fig2 = m.plot_components(forecast)
解决非负约束问题
方法1:从建模层面约束(推荐)
切换为logistic增长模型,同时修正原代码的笔误(将df["floor"] = 0改为p_df["floor"] = 0,确保训练数据携带非负下限约束):
m = Prophet(holidays=holidays, weekly_seasonality=True, growth="logistic") p_df = sdf[["OCCUPANCY_AVG"]].reset_index() p_df.columns = ["ds", "y"] p_df["cap"] = 180 p_df["floor"] = 0 # 修正原代码的df为p_df,让训练数据生效约束 m.fit(p_df) # 未来数据保持一致约束 future = m.make_future_dataframe(periods=24 * 60, freq="H") future["cap"] = 180 future["floor"] = 0
方法2:后处理截断预测值
若不想切换增长模型,可直接对预测结果做非负截断,简单直接但约束性较弱:
forecast['yhat'] = forecast['yhat'].clip(lower=0) forecast['yhat_lower'] = forecast['yhat_lower'].clip(lower=0) forecast['yhat_upper'] = forecast['yhat_upper'].clip(lower=0)
提升拟合度与置信区间覆盖峰值
1. 增强季节性拟合能力
- 提高周度季节性的傅里叶阶数(默认阶数3,可调整为8-12),增强对周内出勤波动的捕捉
- 添加每日季节性(因数据是15分钟间隔,日内波动显著),同样可指定高阶数:
m = Prophet(holidays=holidays, weekly_seasonality=10, # 提高周度季节性复杂度 daily_seasonality=8, # 添加并增强每日季节性 growth="logistic")
2. 强化节假日效应
若节假日对出勤影响较大,可调高全局节假日权重(默认10,可尝试20-30),或针对特定节假日单独设置权重:
# 全局调高节假日权重 m = Prophet(holidays=holidays, weekly_seasonality=10, daily_seasonality=8, growth="logistic", holidays_prior_scale=20)
3. 添加外部回归因子
引入额外影响变量(如小时段、月度会议周期等),帮助模型捕捉更多波动:
# 给训练数据添加小时特征 p_df['hour'] = p_df['ds'].dt.hour # 未来数据同步添加 future['hour'] = future['ds'].dt.hour # 建模时加入外部回归 m = Prophet(holidays=holidays, weekly_seasonality=10, daily_seasonality=8, growth="logistic") m.add_regressor('hour') m.fit(p_df)
4. 调整置信区间宽度
将默认80%的置信区间调高至95%,让区间更宽以覆盖峰值:
m = Prophet(holidays=holidays, weekly_seasonality=10, daily_seasonality=8, growth="logistic", interval_width=0.95)
内容的提问来源于stack exchange,提问作者Ben
相关产品推荐
相关产品推荐

