机器学习实战:沃尔玛销量预测(M5竞赛)

数据集地址:M5 Forecasting - Accuracy | Kaggle

 

 M系列竞赛是针对于时间序列预测的竞赛,从1982年至今已经举办了5届了。M5(最后一届)举行于2020年,使用了来自美国三个洲,三种产品类别,七个部门,3049种商品从2011到2016年的历史销量数据,选手们将要用它们用以预测之后28日的销售数据。具体数据集内容如图所示:

图片来自: 血泪参赛史 - M5时间序列预测(一)历史与概况 - 知乎

 我将使用给定数据集中的最后28日作为测试集,剩下的部分作为训练集和验证集,使用各类回归模型这个问题进行预测。

我的探索数据以及特征工程在很大程度上参考了血泪参赛史 - M5时间序列预测(一)历史与概况 - 知乎这一系列的文章;以及这位叫Konstantin的老哥的策略M5 - Three shades of Dark: Darker magic | Kaggle(这只是他所有出列最后建模的步骤,其他步骤还需要在Code栏种搜索他的名字)。

一、数据探索

import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

df1 = pd.read_csv("calendar.csv")
df2 = pd.read_csv("sell_prices.csv")
df3 = pd.read_csv("sales_train_evaluation.csv")

df3_all = df3.copy()
df3_all = df3_all.loc[:,df3_all.columns[6:]]
zeros_percentage = df3_all.apply(lambda x:x.value_counts()[0]/x.value_counts().sum(),axis=0)

mpl.rcParams['font.family']='SimHei'
mpl.rcParams['axes.unicode_minus']=False

每个日期的销售额被编码为以前缀d_开头的列。 这些是每天售出的商品数量(不是总金额)。其中有不少的零值。

ax = plt.gca()
#n,bins,patches = plt.hist(zeros_percentage,bins=20)
sns.kdeplot(zeros_percentage)
plt.xlim(0,1)
plt.xticks(np.arange(0,1,0.1))
ax.xaxis.set_major_formatter(mpl.ticker.PercentFormatter(xmax=1))
plt.title("当天销售为0的商品类别条目数量(%)")
plt.show()

由于数据量过于庞大,我的硬件资源以及时间上暂时不支持将所有店铺与商品作预测,故只选取了德克萨斯州(TX)的家居一类(HOUSEHOLD1)商品。

df_hh1 = df3[df3.dept_id=="HOUSEHOLD_1"]
df_hh1 = df_hh1[df_hh1.state_id == 'TX']

白噪声检验

首先将所有总销量作为序列作白噪声检验

df_all = df_hh1.sum()

from statsmodels.stats.diagnostic import acorr_ljungbox

print(acorr_ljungbox(df_all))

'''
        lb_stat      lb_pvalue
1   1158.326786  6.953198e-254
2   1755.185421   0.000000e+00
3   2112.885102   0.000000e+00
4   2457.553665   0.000000e+00
5   3006.669919   0.000000e+00
6   4056.484892   0.000000e+00
7   5491.180820   0.000000e+00
8   6534.241632   0.000000e+00
9   7068.818539   0.000000e+00
10  7387.428123   0.000000e+00
'''

p值<0.05,出现显著的自回归关系,不认为是白噪声。

时间序列分解

from statsmodels.tsa.seasonal import seasonal_decompose
df_all.index = df1.date[0:-28].astype(np.datetime64)
df_all
result = seasonal_decompose(df_all)
result.plot()

ax = plt.gca()
plt.plot(result.seasonal[0:31])
ax.xaxis.set_visible(False)
plt.show()

 从趋势图来看,2011~2013年时家居类商品销量有明显上升趋势,而在之后趋于平稳。

而从结节性变化来看,销售的则是会集中在每周的某几天来买的,对此作进一步的验证:

df_cal_all = pd.DataFrame(columns=['date','wk_no','mth','data'])
df_cal_all.loc[:,'date']=pd.Series(df_all.index)
df_cal_all.loc[:,'wk_no']=pd.Series(df_all.index).dt.weekday
df_cal_all.wk_no = df_cal_all.wk_no+1
df_cal_all.loc[:,'mth']=pd.Series(df_all.index).dt.month
df_cal_all.loc[:,'data']=df_all.values
df_cal_all = df_cal_all.groupby(["wk_no","mth"]).sum()["data"]
df_cal_all = df_cal_all.reset_index()
sns.heatmap(pd.pivot(df_cal_all,'wk_no','mth'))

 从热力图上来看,周六和周日(wk_no=6/7)的销量明显比周一至周五的销量更大。

节假日

将节假日分为四个类别:宗教类,文化类,国家纪念类以及体育活动类;观察其是否存在对销量的显著影响。

#某些不怎么熟悉的节日
#MemorialDay:阵亡纪念日,会放假? Purim End、Pesach End:犹太人的节日,会庆祝 Ramadan:伊斯兰教斋月,似乎不会有太大影响 VeteransDay:老兵节
#EidAlAdha:伊斯兰教开斋节和Eid al-Fitr是同一节日 Chanukah:犹太光明节 Cinco De Mayo:五五节

#对每一类别的节日,将它们前14日的销售量作出折线图保存
idx_Religious = [i for i in df1.index if df1.event_type_1[i]=="Religious" or df1.event_type_2[i]=="Religious"]
date_Religious = df1.date[idx_Religious]
date_Religious.index = range(len(date_Religious))
df_all_Reg = pd.DataFrame(columns=["date","data","Regilious_date","holiday"])
for date in date_Religious:
    t = pd.to_datetime(date)
    h = list(df1[df1["date"]==date].event_name_1)[0]
    for i in df_all.index:
        if (i-t).days>=-14 and (i-t).days<=0:
            tmp = pd.Series({"date":i,"data":df_all[i],"Regilious_date":t,"holiday":h})
            df_all_Reg = df_all_Reg.append(tmp,ignore_index=True)
        elif (i-t).days>0:
            break
            

for i in range(int(df_all_Reg.shape[0]/15)):
    plt.plot([(j-df_all_Reg.loc[(i+1)*15-1,"date"]).days for j in  df_all_Reg.loc[i*15:(i+1)*15-1,"date"]],df_all_Reg.loc[i*15:(i+1)*15-1,"data"])
    plt.title(df_all_Reg.holiday[i*15])
    plt.savefig('./Regilious/'+df_all_Reg.holiday[i*15]+"_"+str(i)+'.png')
    print(i,"finish")
    plt.close()


idx_National = [i for i in df1.index if df1.event_type_1[i]=="National" or df1.event_type_2[i]=="National"]
date_National = df1.date[idx_National]
date_National.index = range(len(date_National))
df_all_National = pd.DataFrame(columns=["date","data","National_date","holiday"])
for date in date_National:
    t = pd.to_datetime(date)
    h = list(df1[df1["date"]==date].event_name_1)[0]
    for i in df_all.index:
        if (i-t).days>=-14 and (i-t).days<=0:
            tmp = pd.Series({"date":i,"data":df_all[i],"National_date":t,"holiday":h})
            df_all_National = df_all_National.append(
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值