哈罗,各位 ~ 本文对安泰杯 —— 跨境电商智能算法大赛数据进行了初探与可视化处理,便于各位更为直观理解赛题数据与建模目标。
赛题目标:通过用户历史订单数据,预测用户下一次购买的商品。
赛题数据:数据保存为四个文件中,训练数据(Antai_AE_round1_train_20190626.csv)、测试数据(Antai_AE_round1_test_20190626.csv)、商品信息(Antai_AE_round1_item_attr_20190626.csv)、提交示例(Antai_AE_round1_submit_20190715.csv)
训练数据:用户每次购买的商品id,订单日期以及用户国家标识
测试数据:较于训练数据,测试数据剔除了用户需要预测最后一次购买记录
商品信息:商品id、品类id、店铺id和商品价格
提交示例:预测用户购买商品Top30的item_id依概率从高到低排序,buyer_admin_id,predict 1,predict 2,…,predict 30
代码运行环境:
JupyterLab
Python 3.5
Pandas 0.24.2
matplotlib 3.0.2
seaborn 0.9.0
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import gc
%matplotlib inline
# 禁用科学计数法
pd.set_option('display.float_format',lambda x : '%.2f' % x)
item = pd.read_csv('../data/Antai_AE_round1_item_attr_20190626.csv')
train = pd.read_csv('../data/Antai_AE_round1_train_20190626.csv')
test = pd.read_csv('../data/Antai_AE_round1_test_20190626.csv')
submit = pd.read_csv('../data/Antai_AE_round1_submit_20190715.csv')
df = pd.concat([train.assign(is_train=1), test.assign(is_train=0)])
df['create_order_time'] = pd.to_datetime(df['create_order_time'])
df['date'] = df['create_order_time'].dt.date
df['day'] = df['create_order_time'].dt.day
df['hour'] = df['create_order_time'].dt.hour
df = pd.merge(df, item, how='left', on='item_id')
memory = df.memory_usage().sum() / 1024**2
print('Before memory usage of properties dataframe is :', memory, " MB")
dtype_dict = {'buyer_admin_id' : 'int32',
'item_id' : 'int32',
'store_id' : pd.Int32Dtype(),
'irank' : 'int16',
'item_price' : pd.Int16Dtype(),
'cate_id' : pd.Int16Dtype(),
'is_train' : 'int8',
'day' : 'int8',
'hour' : 'int8',
}
df = df.astype(dtype_dict)
memory = df.memory_usage().sum() / 1024**2
print('After memory usage of properties dataframe is :', memory, " MB")
del train,test; gc.collect()
for col in ['store_id', 'item_price', 'cate_id']:
df[col] = df[col].fillna(0).astype(np.int32).replace(0, np.nan)
df.to_hdf('../data/train_test.h5', '1.0')
%%time
df = pd.read_hdf('../data/train_test.h5', '1.0')
%%time
train = pd.read_csv('../data/Antai_AE_round1_train_20190626.csv')
test = pd.read_csv('../data/Antai_AE_round1_test_20190626.csv')
item = pd.read_csv('../data/Antai_AE_round1_item_attr_20190626.csv')
del train, test; gc.collect()
经过前处理后:
df.head()
# Null 空值统计
for pdf in [df, item]:
for col in pdf.columns:
print(col, pdf[col].isnull().sum())
df.describe()
item.describe()
数据内容:
下一步,我们依次对每个文件的特征进行基础统计和可视化处理,这是对数据进一步理解的基础。
[]~( ̄▽ ̄)~* Let's do it.
train = df['is_train']==1
test = df['is_train']==0
train_count = len(df[train])
print('训练集样本量是',train_count)
test_count = len(df[test])
print('测试集样本量是',test_count)
print('样本比例为:', train_count/test_count)
def groupby_cnt_ratio(df, col):
if isinstance(col, str):
col = [col]
key = ['is_train', 'buyer_country_id'] + col
# groupby function
cnt_stat = df.groupby(key).size().to_frame('count')
ratio_stat = (cnt_stat / cnt_stat.groupby(['is_train', 'buyer_country_id']).sum()).rename(columns={'count':'count_ratio'})
return pd.merge(cnt_stat, ratio_stat, on=key, how='outer').sort_values(by=['count'], ascending=False)
groupby_cnt_ratio(df, [])
plt.figure(figsize=(8,6))
sns.countplot(x='is_train', data = df, palette=['red', 'blue'], hue='buyer_country_id', order=[1, 0])
plt.xticks(np.arange(2), ('训练集', '测试集'))
plt.xlabel('数据文件')
plt.title('国家编号');
buyer_country_id 国家编号
本次比赛给出若干日内来自成熟国家的部分用户的行为数据,以及来自待成熟国家的A部分用户的行为数据,以及待成熟国家的B部分用户的行为数据去除每个用户的最后一条购买数据,让参赛人预测B部分用户的最后一条行为数据。
print('训练集中用户数量',len(df[train]['buyer_admin_id'].unique()))
print('测试集中用户数量',len(df[test]['buyer_admin_id'].unique()))
union = list(set(df[train]['buyer_admin_id'].unique()).intersection(set(df[test]['buyer_admin_id'].unique())))
print('同时在训练集测试集出现的有6位用户,id如下:',union)
df[train][df['buyer_admin_id'].isin(union)].sort_values(by=['buyer_admin_id','irank']).head(10)
df[test][df['buyer_admin_id'].isin(union)].sort_values(by=['buyer_admin_id','irank']).head(3)
df[(train) & (df['irank']==1) & (df['buyer_admin_id'].isin(['12858772','3106927','12368445']))]
emmm... 为啥同一个用户在训练集和测试集国家不一样了呢?但是其他信息能对上。。。,而且rank=1的结果直接给出来了。。。
id为12858772、3106927、12368445直接把结果给出来
可能是数据清洗出问题了,后面再看看怎么处理
admin_cnt = groupby_cnt_ratio(df, 'buyer_admin_id')
admin_cnt.groupby(['is_train','buyer_country_id']).head(3)
# 用户购买记录数——最多、最少、中位数
admin_cnt.groupby(['is_train','buyer_country_id'])['count'].agg(['max','min','median'])
fig, ax = plt.subplots(1, 2 ,figsize=(16,6))
ax[0].set(xlabel='用户记录数')
sns.kdeplot(admin_cnt.loc[(1, 'xx')]['count'].values, ax=ax[0]).set_title('训练集--xx国用户记录数')
ax[1].legend(labels=['训练集', '测试集'], loc="upper right")
ax[1].set(xlabel='用户记录数')
sns.kdeplot(admin_cnt[admin_cnt['count']<50].loc[(1, 'yy')]['count'].values, ax=ax[1]).set_title('yy国用户记录数')
sns.kdeplot(admin_cnt[admin_cnt['count']<50].loc[(0, 'yy')]['count'].values, ax=ax[1]);
用户记录数进行了一波简单的探查:
Notes: 验证集中用户最少仅有7条,是因为最后一条记录被抹去
从上面数据和图表看到,用户记录数大都都分布在0~50,少量用户记录甚至超过了10000条,下一步对用户记录数分布继续探索
admin_cnt.columns = ['记录数', '占比']
admin_user_cnt = groupby_cnt_ratio(admin_cnt, '记录数')
admin_user_cnt.columns = ['人数', '人数占比']
admin_user_cnt.head()
# xx国——用户记录数与用户数
admin_user_cnt.loc[(1,'xx')][['人数','人数占比']].T
# yy国——记录数与用户数占比
admin_user_cnt.loc[([1,0],'yy',slice(None))][['人数','人数占比']].unstack(0).drop('人数',1).head(10)
fig, ax = plt.subplots(2, 1, figsize=(16,10))
admin_plot = admin_user_cnt.reset_index()
sns.barplot(x='记录数', y='人数占比', data=admin_plot[(admin_plot['记录数']<50) & (admin_plot['buyer_country_id']=='xx')],
estimator=np.mean, ax=ax[0]).set_title('训练集——xx国记录数与人数占比');
sns.barplot(x='记录数', y='人数占比', hue='is_train', data=admin_plot[(admin_plot['记录数']<50) & (admin_plot['buyer_country_id']=='yy')],
estimator=np.mean, ax=ax[1]).set_title('yy国记录数与人数占比');
用户记录数进一步探查结论:
* 不管是训练集还是验证集,99%的用户购买记录都在50条内,这是比较符合正常逻辑
* TODO:对于发生大量购买行为的用户,后面再单独探查,是否有其他规律或疑似刷单现象
print('商品表中商品数:',len(item['item_id'].unique()))
print('训练集中商品数:',len(df[train]['item_id'].unique()))
print('验证集中商品数:',len(df[test]['item_id'].unique()))
print('仅训练集有的商品数:',len(list(set(df[train]['item_id'].unique()).difference(set(df[test]['item_id'].unique())))))
print('仅验证集有的商品数:',len(list(set(df[test]['item_id'].unique()).difference(set(df[train]['item_id'].unique())))))
print('训练集验证集共同商品数:',len(list(set(df[train]['item_id'].unique()).intersection(set(df[test]['item_id'].unique())))))
print('训练集中不在商品表的商品数:',len(list(set(df[train]['item_id'].unique()).difference(set(item['item_id'].unique())))))
print('验证集中不在商品表的商品数:',len(list(set(df[test]['item_id'].unique()).difference(set(item['item_id'].unique())))))
item_cnt = groupby_cnt_ratio(df, 'item_id')
item_cnt.columns=['销量', '总销量占比']
item_cnt.reset_index(inplace=True)
top_item_plot = item_cnt.groupby(['is_train','buyer_country_id']).head(10)
fig, ax = plt.subplots(2, 1, figsize=(16,12))
sns.barplot(x='item_id', y='销量', data=top_item_plot[top_item_plot['buyer_country_id']=='xx'],
order=top_item_plot['item_id'][top_item_plot['buyer_country_id']=='xx'], ax=ax[0], estimator=np.mean).set_title('xx国-TOP热销商品')
sns.barplot(x='item_id', y='销量', hue='is_train', data=top_item_plot[top_item_plot['buyer_country_id']=='yy'],
order=top_item_plot['item_id'][top_item_plot['buyer_country_id']=='yy'], ax=ax[1], estimator=np.mean).set_title('yy国-TOP热销商品');
初步数据发现:
item_order_cnt = groupby_cnt_ratio(item_cnt, '销量')
item_order_cnt.columns = ['商品数', '占比']
item_order_cnt.groupby(['is_train','buyer_country_id']).head(5).sort_values(by=['buyer_country_id','is_train'])
item_order_plot = item_order_cnt.reset_index()
item_order_plot = item_order_plot[item_order_plot['销量']<=8]
xx_item_order_plot = item_order_plot[item_order_plot['buyer_country_id']=='xx']
yy_item_order_plot = item_order_plot[item_order_plot['buyer_country_id']=='yy']
yy_item_order_plot_1 = yy_item_order_plot[yy_item_order_plot['is_train']==1]
yy_item_order_plot_0 = yy_item_order_plot[yy_item_order_plot['is_train']==0]
# 商品销量饼图
def text_style_func(pct, allvals):
absolute = int(pct/100.*np.sum(allvals))
return "{:.1f}%({:d})".format(pct, absolute)
def pie_param(ax, df, color_palette):
return ax.pie(df['占比'].values, autopct=lambda pct: text_style_func(pct, df['商品数']), labels = df['销量'],
explode = [0.1]+ np.zeros(len(df)-1).tolist(), pctdistance = 0.7, colors=sns.color_palette(color_palette, 8))
fig, ax = plt.subplots(1, 3, figsize=(16,12))
ax[0].set(xlabel='xx国-商品销量')
ax[0].set(ylabel='xx国-商品数量比例')
pie_param(ax[0], xx_item_order_plot, "coolwarm")
ax[1].set(xlabel='yy国-训练集商品销量')
pie_param(ax[1], yy_item_order_plot_1, "Set3")
ax[2].set(xlabel='yy国测试集集商品销量')
pie_param(ax[2], yy_item_order_plot_0, "Set3");
print(xx_item_order_plot.head(10)['占比'].sum())
print(yy_item_order_plot_1.head(10)['占比'].sum())
print(yy_item_order_plot_0.head(10)['占比'].sum())
总体来看,由于训练集数据远多于测试集数据:
print('商品品类数', len(item['cate_id'].unique()))
print('训练集商品品类数', len(df[train]['cate_id'].unique()))
print('测试集商品品类数', len(df[test]['cate_id'].unique()))
cate_cnt = item.groupby(['cate_id']).size().to_frame('count').reset_index()
cate_cnt.sort_values(by=['count'], ascending=False).head(5)
plt.figure(figsize=(12,4))
sns.kdeplot(data=cate_cnt[cate_cnt['count']<1000]['count']);
我们发现:
* 579品类一花独秀有17W个商品,可能是平台主营方向
* 大部分品类都在100个以上
print('商品店铺数', len(item['store_id'].unique()))
print('训练集店铺数', len(df[train]['store_id'].unique()))
print('测试集店铺数', len(df[train]['store_id'].unique()))
store_cate_cnt = item.groupby(['store_id'])['cate_id'].nunique().to_frame('count').reset_index()
store_cate_cnt.sort_values(by=['count'], ascending=False).head(5)
store_cnt_cate_cnt = store_cate_cnt.groupby(['count']).size().reset_index()
store_cnt_cate_cnt.columns = ['店铺品类数', '店铺数量']
plt.figure(figsize=(12,4))
sns.barplot(x='店铺品类数', y='店铺数量', data=store_cnt_cate_cnt[store_cnt_cate_cnt['店铺品类数']<50], estimator=np.mean);
store_item_cnt = item.groupby(['store_id'])['item_id'].nunique().to_frame('count').reset_index()
store_item_cnt.sort_values(by=['count'], ascending=False).head(5)
store_cnt_item_cnt = store_item_cnt.groupby(['count']).size().reset_index()
store_cnt_item_cnt.columns = ['店铺商品数', '店铺数量']
store_cnt_item_cnt.T
plt.figure(figsize=(16,4))
sns.barplot(x='店铺商品数', y='店铺数量', data=store_cnt_item_cnt[store_cnt_item_cnt['店铺商品数']<80], estimator=np.mean);
print(item['item_price'].max(), item['item_price'].min(), item['item_price'].mean(), item['item_price'].median())
plt.figure(figsize=(16,4))
plt.subplot(121)
sns.kdeplot(item['item_price'])
plt.subplot(122)
sns.kdeplot(item['item_price'][item['item_price']<1000]);
price_cnt = item.groupby(['item_price']).size().to_frame('count').reset_index()
price_cnt.sort_values(by=['count'], ascending=False).head(10)
关于商品价格:商品价格是通过函数转化成了从1开始的整数,最大值为20230,最小值为1。
* 经常对商品价格统计,大部门商品都是整百数,Top5价格200\500\100\400\300
* TODO:整百商品探查
print(df[train]['item_price'].max(), df[train]['item_price'].min(), df[train]['item_price'].mean(), df[train]['item_price'].median())
print(df[test]['item_price'].max(), df[test]['item_price'].min(), df[test]['item_price'].mean(), df[test]['item_price'].median())
plt.figure(figsize=(12,4))
sns.kdeplot(df[train][df[train]['item_price']<1000][['item_id','item_price']].drop_duplicates()['item_price'])
sns.kdeplot(df[test][df[test]['item_price']<1000][['item_id','item_price']].drop_duplicates()['item_price']);
商品价格与销量
df[train].groupby(['item_price'])['item_id'].nunique().to_frame('商品数量').head()
price_cnt = groupby_cnt_ratio(df, 'item_price')
price_cnt.groupby(['is_train', 'buyer_country_id']).head(5)
似乎价格与销量并无直接关系
* 但是价格为100、200、300、400、500整百数位居销量榜
* xx国,17844如此高价格的商品销量这么高?
print(df[train]['create_order_time'].min(), df[train]['create_order_time'].max())
print(df[test]['create_order_time'].min(), df[test]['create_order_time'].max())
train_df_seven = df[train][df[train]['create_order_time']<pd.to_datetime('2018-08-01')]
train_df_eight = df[train][df[train]['create_order_time']>pd.to_datetime('2018-08-01')]
train_df_seven = df[train][df[train]['create_order_time']<pd.to_datetime('2018-08-01')]
train_df_eight = df[train][df[train]['create_order_time']>pd.to_datetime('2018-08-01')]
print('7月数据量',len(df[train][df[train]['create_order_time']<pd.to_datetime('2018-08-01')]),
'\n8月数据量',len(df[train][df[train]['create_order_time']>pd.to_datetime('2018-08-02')]))
date_cnt = groupby_cnt_ratio(df, 'date')
date_cnt.columns = ['当天销量', "占比"]
date_cnt = date_cnt.reset_index()
fig, ax = plt.subplots(2, 1, figsize=(16,10))
sns.lineplot(x='date', y='当天销量', hue='buyer_country_id', data=date_cnt[(date_cnt['is_train']==1)],
estimator=np.mean, ax=ax[0]).set_title('训练集——每日销量');
sns.lineplot(x='date', y='当天销量', hue='is_train', data=date_cnt[(date_cnt['buyer_country_id']=='yy')],
estimator=np.mean, ax=ax[1]).set_title('yy国每日销量');
很明显:
seven = date_cnt[date_cnt['date']<pd.to_datetime('2018-08-02')]
eight = date_cnt[date_cnt['date']>=pd.to_datetime('2018-08-02')]
fig, ax = plt.subplots(2, 3, figsize=(20,16))
def barplot(ax, df, title):
df['date'] = df['date'].astype(str)
sns.barplot(y='date', x='当天销量' ,data=df, order=sorted(df['date'].unique()), ax=ax, estimator=np.mean)\
.set_title(title)
barplot(ax[0][0], seven[(seven['is_train']==1) & (seven['buyer_country_id']=='xx')], 'xx国7月份销量')
barplot(ax[1][0], eight[(eight['is_train']==1) & (eight['buyer_country_id']=='xx')], 'xx国8月份销量')
barplot(ax[0][1], seven[(seven['is_train']==1) & (seven['buyer_country_id']=='yy')], '训练集-yy国7月份销量')
barplot(ax[1][1], eight[(eight['is_train']==1) & (eight['buyer_country_id']=='yy')], '训练集-yy国8月份销量')
barplot(ax[0][2], seven[(seven['is_train']==0) & (seven['buyer_country_id']=='yy')], '测试集-yy国7月份销量')
barplot(ax[1][2], eight[(eight['is_train']==0) & (eight['buyer_country_id']=='yy')], '测试集-yy国8月份销量')
plt.tight_layout()
数据放大后看:
unique = df.groupby(['is_train', 'buyer_country_id', 'date']).agg({'buyer_admin_id':'nunique','item_id':['nunique','size']})
unique.columns = ['uv','商品数(去重)', '销量']
unique = unique.reset_index()
unique = pd.melt(unique, id_vars=['is_train', 'buyer_country_id', 'date'], value_vars=['uv', '商品数(去重)', '销量'])
unique['date'] = unique['date'].astype(str)
unique = unique[unique['date']>='2018-08-02']
fig, ax = plt.subplots(3, 1, figsize=(16,8), sharex=True)
sns.lineplot(x='date', y='value', hue='variable', data=unique[(unique['is_train']==1) & (unique['buyer_country_id']=='xx')],
estimator=np.mean, ax=ax[0]).set_title('xx国每日销售数据');
sns.lineplot(x='date', y='value', hue='variable', data=unique[(unique['is_train']==0) & (unique['buyer_country_id']=='yy')],
estimator=np.mean, ax=ax[1]).set_title('训练集-yy国每日销量');
sns.lineplot(x='date', y='value', hue='variable', data=unique[(unique['is_train']==1) & (unique['buyer_country_id']=='yy')],
estimator=np.mean, ax=ax[2]).set_title('测试集-yy国每日销量')
plt.xticks(rotation=90);
对每日的uv、商品数和销量作图发现:
选取用户近30次购买记录作为预测值,越近购买的商品放在越靠前的列,不够30次购买记录的用热销商品5595070填充
test = pd.read_csv('../data/Antai_AE_round1_test_20190626.csv')
tmp = test[test['irank']<=31].sort_values(by=['buyer_country_id', 'buyer_admin_id', 'irank'])[['buyer_admin_id','item_id','irank']]
sub = tmp.set_index(['buyer_admin_id', 'irank']).unstack(-1)
sub.fillna(5595070).astype(int).reset_index().to_csv('../submit/sub.csv', index=False, header=None)
# 最终提交文件格式
sub = pd.read_csv('../submit/sub.csv', header = None)
sub.head()