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

最低0.47元/天 解锁文章
1945

被折叠的 条评论
为什么被折叠?



