Kaggle实战:从零到一构建零售销售时间序列预测模型

1. 初识Kaggle零售销售预测挑战

第一次接触Kaggle的Store Sales时间序列预测比赛时,我完全被数据量吓到了。300多万条销售记录,横跨54家门店、33种商品大类,还要整合油价、节假日等外部因素。这可比教科书上的"某城市月度用电量预测"复杂多了。

这个比赛的数据来自厄瓜多尔连锁超市Favorita,目标是预测未来15天每家门店每种商品大类的销售额。数据包里包含几个关键文件:

  • train.csv:2013-2017年的历史销售数据
  • stores.csv:门店地理位置和类型信息
  • oil.csv:每日国际油价(厄瓜多尔是石油出口国)
  • holidays_events.csv:当地节假日和特殊事件

提示:时间序列预测比赛中,理解业务背景和数据关系比算法选择更重要。比如油价波动对食品销售的影响程度,节假日促销的实际效果等。

2. 数据清洗的实战技巧

2.1 缺失值处理的正确姿势

原始数据中最棘手的是oil.csv里的油价缺失。直接均值填充会失真,因为油价本身波动剧烈。我的解决方案是:

# 前向填充+可视化验证
df_oil['dcoilwtico'] = df_oil['dcoilwtico'].fillna(method='ffill')
plt.plot(df_oil['date'], df_oil['dcoilwtico'])
plt.title('油价走势(填充后)')

更隐蔽的坑在transactions.csv的交易额数据——测试集里该字段全是NaN。后来发现训练集中交易额为0的日子也被记录为NaN,需要用0填充:

df_transactions.fillna(0, inplace=True)

2.2 特征工程的组合拳

合并多表数据时,我设计了一套特征组合策略:

  1. 时空特征 :将门店坐标转换为区域编码,捕捉地理位置影响
  2. 时间分解 :把日期拆解成年、月、周、星期等周期性特征
  3. 外部因素 :油价波动率、节假日类型(全国性/地区性)
  4. 业务特征 :促销商品占比、门店类型组合
# 时间特征生成示例
df_train['date'] = pd.to_datetime(df_train['date'])
df_train['day_of_week'] = df_train['date'].dt.dayofweek
df_train['is_weekend'] = df_train['day_of_week'].isin([5,6]).astype(int)

3. 模型选择的进化之路

3.1 从Baseline模型起步

先用最简单的随机森林建立baseline,发现几个关键现象:

  • MAE在70-100之间波动
  • 商品类型(family)的特征重要性最高
  • 油价影响比预期小,可能需做滞后处理
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(n_estimators=100, max_depth=10)
rf.fit(X_train, y_train)
print(f"MAE: {mean_absolute_error(y_test, rf.predict(X_test))}")

3.2 时间序列专属特征

传统机器学习模型处理时间序列的三大法宝:

  1. 滞后特征(Lag Features) :过去7/14/28天的销售数据
  2. 滚动统计(Rolling Stats) :近30天均值/标准差
  3. 扩展窗口(Expanding Window) :历史累计平均值
# 创建滞后特征
for lag in [7, 14, 21, 28]:
    df_train[f'sales_lag_{lag}'] = df_train.groupby(['store_nbr','family'])['sales'].shift(lag)

3.3 梯度提升树的威力

切换到XGBoost后效果显著提升,关键配置:

xgb_params = {
    'n_estimators': 500,
    'max_depth': 8,
    'learning_rate': 0.05,
    'subsample': 0.8,
    'colsample_bytree': 0.8,
    'objective': 'reg:squarederror'
}
model = xgb.XGBRegressor(**xgb_params)
model.fit(X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=20)

4. 比赛提分的秘密武器

4.1 目标变量变换技巧

原始销售额严重右偏,进行对数变换后模型效果提升15%:

# 对数变换+1避免零值
df_train['sales'] = np.log1p(df_train['sales'])

4.2 魔法特征:星期均值

最有效的特征竟是"该商品在该门店历史上同星期几的平均销售额":

weekday_avg = df_train.groupby(['store_nbr','family','day_of_week'])['sales'].mean().reset_index()
weekday_avg.columns = ['store_nbr','family','day_of_week','weekday_avg_sales']
df_train = pd.merge(df_train, weekday_avg, on=['store_nbr','family','day_of_week'])

4.3 数据筛选的玄学

意外发现只用最近1年数据反而比全量数据效果更好,说明:

  • 消费模式会随时间演变
  • 太久远的数据可能产生噪声
  • 需要平衡数据量和数据质量
recent_data = df_train[df_train['date'] > '2016-08-15']

5. 完整项目复盘与经验总结

整个项目走下来,最大的收获是认识到时间序列预测的复杂性。不同于结构化数据预测,它需要:

  • 对业务周期性的深刻理解(比如厄瓜多尔每月15日和月底发薪日对销售的影响)
  • 灵活的滞后特征设计(不同商品可能有不同的销售周期)
  • 外部因素的合理量化(如何用数值表示节假日的影响程度)

最终我的方案在Kaggle排名前15%,核心秘诀是:

  1. 构建了超过50个时间相关特征
  2. 采用两层模型堆叠:第一层预测趋势,第二层修正残差
  3. 对不同类型的商品(食品/日用品)分别建模

注意:比赛后期尝试了LSTM等深度学习模型,但效果不如特征工程到位的XGBoost,说明在数据量不够大时,传统方法仍有优势。

时间序列预测就像拼乐高,需要把各种特征模块巧妙组合。下次如果再参加类似比赛,我会更早开始做交叉验证,避免在特征重要性评估上走弯路。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值