所谓的EDA,即为数据探索,这里主要指的是赛前的数据探索(第二种是对模型的分析,包括LightGBM/XgBoost的feature importance,LR,SVM的coeff等)。那木赛前数据的EDA要做那些呢,第一个是对数据集的宏观分析,包括数据缺失,数据重复,异常值检测以及一些数据的清洗工作。还有就是变量之间相互关系的分析,包括计算相关性,变量可视化等等,合适的EDA可以帮助我们发现数据中的规律,而预处理可以清洗构造出一个更可用的数据,这对于特征工程和模型构造训练是很重要的一个环节。
对于Predict Future Sales这个赛题,首先读入数据:
test = pd.read_csv('test.csv', dtype={'ID': 'int32', 'shop_id': 'int32',
'item_id': 'int32'})
item_categories = pd.read_csv('item_categories.csv',
dtype={'item_category_name': 'str', 'item_category_id': 'int32'})
items = pd.read_csv('items.csv', dtype={'item_name': 'str', 'item_id': 'int32',
'item_category_id': 'int32'})
shops = pd.read_csv('shops.csv', dtype={'shop_name': 'str', 'shop_id': 'int32'})
sales = pd.read_csv('sales_train_v2.csv', parse_dates=['date'],
dtype={'date': 'str', 'date_block_num': 'int32', 'shop_id': 'int32',
'item_id': 'int32', 'item_price': 'float32', 'item_cnt_day': 'int32'})
这里有一点扩展的是pandas的read_csv函数在读取大数据集的时候是比较费时的,这里可以参考阿水的建议:Pandas常见的性能优化方法。
将训练集的补充数据和sales合并到一起,组成训练数据。
train = sales.join(items, on='item_id', rsuffix='_').join(shops, on='shop_id', rsuffix='_').join(item_categories, on='item_category_id', rsuffix='_').drop(['item_id_', 'shop_id_', 'item_category_id_'], axis=1)
这里的rsuffix是用来标记重复的column列,合并之后会被drop掉。我们查看一下训练数据:
这里由于原始数据date列是02.01.2013这样的格式,在读取数据的时候之间转换成date型会将月份和日子调换,所以这里的2013-02-01实际上是2013年1月2日,由于后面我并没有用到这个date,而是用到用于月份计数的date_block_num,所以这里就没有跟正了,仅作说明,不影响后面。
可以看到训练数据一共有2935849个样本,10列原始特征列,记录的销量日期从2013年1月到2015年10月,而这里需要我们预测的是2015年11月的销量信息。测试集如下:
数据leakages:
什么是leakages?这里实际上是个tricks,从测试集可以看出,测试集只有shop_id和item_id,且行数少于训练集行数,考虑模型只训练测试集所包含的(shop_id,item_id)对,其他的匹配对不予以考虑,在数据竞赛中这个tricks可以提升不少分数。
# 数据泄漏
test_shop_ids = test['shop_id'].unique()
test_item_ids = test['item_id'].unique()
lk_train = train[train['shop_id'].isin(test_shop_ids)]
lk_train = lk_train[lk_train['item_id'].isin(test_item_ids)]
train_monthly = lk_train[['date', 'date_block_num', 'shop_id', 'item_category_id', 'item_id', 'item_price', 'item_cnt_day']]
train_monthly.head().append(train_monthly.tail())
这里只考虑用于构造特征的列,现在的数据如下:
查看一下重复值和缺失值:
## 重复值
print(train_monthly.duplicated().any())
print('**'*30)
## 缺失值
print(train_monthly.isnull().sum())
这里无重复值和缺失值。
为方便后续的EDA,这里按照月份,商店和商品来计算出销量和价格的总值和均值:
train_monthly = train_monthly.sort_values('date').groupby(['date_block_num', 'shop_id', 'item_category_id', 'item_id'], as_index=False)
train_monthly = train_monthly.agg({'item_price':['sum', 'mean'], 'item_cnt_day':['sum', 'mean','count']})
# Rename features.
train_monthly.columns = ['date_block_num', 'shop_id', 'item_category_id', 'item_id', 'item_price', 'mean_item_price', 'item_cnt', 'mean_item_cnt', 'transactions']
train_monthly.head().append(train_monthly.tail())
考虑到测试集可能会有不同的商店和商品的组合,这里我们对训练数据按照shop_id和item_id的组合进行扩充,缺失数据进行零填充,同时构造出具体的年,月信息:
shop_ids = train_monthly['shop_id'].unique()
item_ids = train_monthly['item_id'].unique()
empty_df = []
for i in range(34):
for shop in shop_ids:
for item in item_ids:
empty_df.append([i, shop, item])
empty_df = pd.DataFrame(empty_df, columns=['date_block_num','shop_id','item_id'])
train_monthly = pd.merge(empty_df, train_monthly, on=['date_block_num','shop_id','item_id'], how='left')
train_monthly.fillna(0, inplace=True)
train_monthly['year'] = train_monthly['date_block_num'].apply(lambda x: ((x//12) + 2013))
train_monthly['month'] = train_monthly['date_block_num'].apply(lambda x: (x % 12))
EDA
首先,分别按照month,category和shop分组,得到各组的销量。
gp_month_mean = train_monthly.groupby(['month'], as_index=False)['item_cnt'].mean()
gp_month_sum = train_monthly.groupby(['month'], as_index=False)['item_cnt'].sum()
gp_category_mean = train_monthly.groupby(['item_category_id'], as_index=False)['item_cnt'].mean()
gp_category_sum = train_monthly.groupby(['item_category_id'], as_index=False)['item_cnt'].sum()
gp_shop_mean = train_monthly.groupby(['shop_id'], as_index=False)['item_cnt'].mean()
gp_shop_sum = train_monthly.groupby(['shop_id'], as_index=False)['item_cnt'].sum()
分别查看各组和销量之间的关系曲线:
f, axes = plt.subplots(2, 1, figsize=(22, 10), sharex=True)
sns.lineplot(x="month", y="item_cnt", data=gp_month_mean, ax=axes[0]).set_title("Monthly mean")
sns.lineplot(x="month", y="item_cnt", data=gp_month_sum, ax=axes[1]).set_title("Monthly sum")
plt.show()
可以看到时间上,下半年(年末)的销量是增加的。
f, axes = plt.subplots(2, 1, figsize=(22, 10), sharex=True)
sns.barplot(x="item_category_id", y="item_cnt", data=gp_category_mean, ax=axes[0], palette="rocket").set_title("Monthly mean")
sns.barplot(x="item_category_id", y="item_cnt", data=gp_category_sum, ax=axes[1], palette="rocket").set_title("Monthly sum")
plt.show()
我们发现只有部分category对销量具有突出的贡献。
f, axes = plt.subplots(2, 1, figsize=(22, 10), sharex=True)
sns.barplot(x="shop_id", y="item_cnt", data=gp_shop_mean, ax=axes[0], palette="rocket").set_title("Monthly mean")
sns.barplot(x="shop_id", y="item_cnt", data=gp_shop_sum, ax=axes[1], palette="rocket").set_title("Monthly sum")
plt.show()
有三个突出的商店销量比较高,考虑到可能是大商场或者比较出名的零售店,可以用来后续特征工程的构造。
通过散点关联和箱图查看异常值(这里是最直接的方法,更多的异常值检测算法是比较麻烦的,可以自行尝试)
这里,从EDA,我们认为销量不在[0,20],售价超过40000的为异常值,剔除异常值即可。预处理之后的训练集如下所示:
接下来一篇需要构造更多的特征,即特征工程。