# 数据分析
数据分析的价值主要在于熟悉了解整个数据集的基本情况包括每个文件里有哪些数据,具体的文件中的每个字段表示什么实际含义,以及数据集中特征之间的相关性,在推荐场景下主要就是分析用户本身的基本属性,文章基本属性,以及用户和文章交互的一些分布,这些都有利于后面的召回策略的选择,以及特征工程。
建议:当特征工程和模型调参已经很难继续上分了,可以回来在重新从新的角度去分析这些数据,或许可以找到上分的灵感
导包 1 2 3 4 5 6 7 8 9 10 11 %matplotlib inline import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport seaborn as snsplt.rc('font' , family='SimHei' , size=13 ) import os,gc,re,warnings,syswarnings.filterwarnings("ignore" )
读取数据 1 2 3 4 5 6 7 8 9 10 path = './data_raw/' trn_click = pd.read_csv(path+'train_click_log.csv' ) item_df = pd.read_csv(path+'articles.csv' ) item_df = item_df.rename(columns={'article_id' : 'click_article_id' }) item_emb_df = pd.read_csv(path+'articles_emb.csv' ) tst_click = pd.read_csv(path+'testA_click_log.csv' )
数据预处理 计算用户点击rank和点击次数
1 2 3 trn_click['rank' ] = trn_click.groupby(['user_id' ])['click_timestamp' ].rank(ascending=False ).astype(int ) tst_click['rank' ] = tst_click.groupby(['user_id' ])['click_timestamp' ].rank(ascending=False ).astype(int )
1 2 3 trn_click['click_cnts' ] = trn_click.groupby(['user_id' ])['click_timestamp' ].transform('count' ) tst_click['click_cnts' ] = tst_click.groupby(['user_id' ])['click_timestamp' ].transform('count' )
数据浏览 用户点击日志文件_训练集 1 2 trn_click = trn_click.merge(item_df, how='left' , on=['click_article_id' ]) trn_click.head()
train_click_log.csv文件数据中每个字段的含义
user_id: 用户的唯一标识
click_article_id: 用户点击的文章唯一标识
click_timestamp: 用户点击文章时的时间戳
click_environment: 用户点击文章的环境
click_deviceGroup: 用户点击文章的设备组
click_os: 用户点击文章时的操作系统
click_country: 用户点击文章时的所在的国家
click_region: 用户点击文章时所在的区域
click_referrer_type: 用户点击文章时,文章的来源
1 2 trn_click.user_id.nunique()
200000
1 trn_click.groupby('user_id' )['click_article_id' ].count().min ()
2
画直方图大体看一下基本的属性分布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 plt.figure() plt.figure(figsize=(15 , 20 )) i = 1 for col in ['click_article_id' , 'click_timestamp' , 'click_environment' , 'click_deviceGroup' , 'click_os' , 'click_country' , 'click_region' , 'click_referrer_type' , 'rank' , 'click_cnts' ]: plot_envs = plt.subplot(5 , 2 , i) i += 1 v = trn_click[col].value_counts().reset_index()[:10 ] fig = sns.barplot(x=v['index' ], y=v[col]) for item in fig.get_xticklabels(): item.set_rotation(90 ) plt.title(col) plt.tight_layout() plt.show()
从点击时间clik_timestamp来看,分布较为平均,可不做特殊处理。由于时间戳是13位的,后续将时间格式转换成10位方便计算。
从点击环境click_environment来看,仅有1922次(占0.1%)点击环境为1;仅有24617次(占2.3%)点击环境为2;剩余(占97.6%)点击环境为4。
从点击设备组click_deviceGroup来看,设备1占大部分(60.4%),设备3占36%。
测试集用户点击日志 1 2 tst_click = tst_click.merge(item_df, how='left' , on=['click_article_id' ]) tst_click.head()
我们可以看出训练集和测试集的用户是完全不一样的
训练集的用户ID由0 ~ 199999,而测试集A的用户ID由200000 ~ 249999。
1 2 tst_click.user_id.nunique()
50000
1 tst_click.groupby('user_id' )['click_article_id' ].count().min ()
1
新闻文章信息数据表 1 2 item_df.head().append(item_df.tail())
1 item_df['words_count' ].value_counts()
1 2 print(item_df['category_id' ].nunique()) item_df['category_id' ].hist()
(364047, 4)
新闻文章embedding向量表示
(364047, 251)
数据分析 用户重复点击 1 2 user_click_merge = trn_click.append(tst_click)
1 2 3 user_click_count = user_click_merge.groupby(['user_id' , 'click_article_id' ])['click_timestamp' ].agg({'count' }).reset_index() user_click_count[:10 ]
1 user_click_count[user_click_count['count' ]>7 ]
1 user_click_count['count' ].unique()
1 2 user_click_count.loc[:,'count' ].value_counts()
可以看出:有1605541(约占99.2%)的用户未重复阅读过文章,仅有极少数用户重复点击过某篇文章。 这个也可以单独制作成特征
用户点击环境变化分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 def plot_envs (df, cols, r, c ): plt.figure() plt.figure(figsize=(10 , 5 )) i = 1 for col in cols: plt.subplot(r, c, i) i += 1 v = df[col].value_counts().reset_index() fig = sns.barplot(x=v['index' ], y=v[col]) for item in fig.get_xticklabels(): item.set_rotation(90 ) plt.title(col) plt.tight_layout() plt.show()
1 2 3 4 5 6 sample_user_ids = np.random.choice(tst_click['user_id' ].unique(), size=5 , replace=False ) sample_users = user_click_merge[user_click_merge['user_id' ].isin(sample_user_ids)] cols = ['click_environment' ,'click_deviceGroup' , 'click_os' , 'click_country' , 'click_region' ,'click_referrer_type' ] for _, user_df in sample_users.groupby('user_id' ): plot_envs(user_df, cols, 2 , 3 )
可以看出绝大多数数的用户的点击环境是比较固定的。思路:可以基于这些环境的统计特征来代表该用户本身的属性
用户点击新闻数量的分布 1 2 user_click_item_count = sorted (user_click_merge.groupby('user_id' )['click_article_id' ].count(), reverse=True ) plt.plot(user_click_item_count)
可以根据用户的点击文章次数看出用户的活跃度
1 2 plt.plot(user_click_item_count[:50 ])
点击次数排前50的用户的点击次数都在100次以上。思路:我们可以定义点击次数大于等于100次的用户为活跃用户,这是一种简单的处理思路, 判断用户活跃度,更加全面的是再结合上点击时间,后面我们会基于点击次数和点击时间两个方面来判断用户活跃度。
1 2 plt.plot(user_click_item_count[25000 :50000 ])
可以看出点击次数小于等于两次的用户非常的多,这些用户可以认为是非活跃用户
新闻点击次数分析 1 item_click_count = sorted (user_click_merge.groupby('click_article_id' )['user_id' ].count(), reverse=True )
1 plt.plot(item_click_count)
1 plt.plot(item_click_count[:100 ])
可以看出点击次数最多的前100篇新闻,点击次数大于1000次
1 plt.plot(item_click_count[:20 ])
点击次数最多的前20篇新闻,点击次数大于2500。思路:可以定义这些新闻为热门新闻, 这个也是简单的处理方式,后面我们也是根据点击次数和时间进行文章热度的一个划分。
1 plt.plot(item_click_count[3500 :])
可以发现很多新闻只被点击过一两次。思路:可以定义这些新闻是冷门新闻。
新闻共现频次:两篇新闻连续出现的次数 1 2 3 4 tmp = user_click_merge.sort_values('click_timestamp' ) tmp['next_item' ] = tmp.groupby(['user_id' ])['click_article_id' ].transform(lambda x:x.shift(-1 )) union_item = tmp.groupby(['click_article_id' ,'next_item' ])['click_timestamp' ].agg({'count' }).reset_index().sort_values('count' , ascending=False ) union_item[['count' ]].describe()
由统计数据可以看出,平均共现次数2.88,最高为1687。
说明用户看的新闻,相关性是比较强的。
1 2 3 4 x = union_item['click_article_id' ] y = union_item['count' ] plt.scatter(x, y)
1 plt.plot(union_item['count' ].values[40000 :])
大概有70000个pair至少共现一次。
新闻文章信息 1 2 plt.plot(user_click_merge['category_id' ].value_counts().values)
1 2 plt.plot(user_click_merge['category_id' ].value_counts().values[150 :])
1 2 user_click_merge['words_count' ].describe()
1 plt.plot(user_click_merge['words_count' ].values)
用户点击的新闻类型的偏好 此特征可以用于度量用户的兴趣是否广泛。
1 plt.plot(sorted (user_click_merge.groupby('user_id' )['category_id' ].nunique(), reverse=True ))
从上图中可以看出有一小部分用户阅读类型是极其广泛的,大部分人都处在20个新闻类型以下。
1 user_click_merge.groupby('user_id' )['category_id' ].nunique().reset_index().describe()
用户查看文章的长度的分布 通过统计不同用户点击新闻的平均字数,这个可以反映用户是对长文更感兴趣还是对短文更感兴趣。
1 plt.plot(sorted (user_click_merge.groupby('user_id' )['words_count' ].mean(), reverse=True ))
从上图中可以发现有一小部分人看的文章平均词数非常高,也有一小部分人看的平均文章次数非常低。
大多数人偏好于阅读字数在200-400字之间的新闻。
1 2 plt.plot(sorted (user_click_merge.groupby('user_id' )['words_count' ].mean(), reverse=True )[1000 :45000 ])
可以发现大多数人都是看250字以下的文章
1 2 user_click_merge.groupby('user_id' )['words_count' ].mean().reset_index().describe()
用户点击新闻的时间分析 1 2 3 4 5 6 7 from sklearn.preprocessing import MinMaxScalermm = MinMaxScaler() user_click_merge['click_timestamp' ] = mm.fit_transform(user_click_merge[['click_timestamp' ]]) user_click_merge['created_at_ts' ] = mm.fit_transform(user_click_merge[['created_at_ts' ]]) user_click_merge = user_click_merge.sort_values('click_timestamp' )
1 2 3 4 5 def mean_diff_time_func (df, col ): df = pd.DataFrame(df, columns={col}) df['time_shift1' ] = df[col].shift(1 ).fillna(0 ) df['diff_time' ] = abs (df[col] - df['time_shift1' ]) return df['diff_time' ].mean()
1 2 mean_diff_click_time = user_click_merge.groupby('user_id' )['click_timestamp' , 'created_at_ts' ].apply(lambda x: mean_diff_time_func(x, 'click_timestamp' ))
1 plt.plot(sorted (mean_diff_click_time.values, reverse=True ))
从上图可以发现不同用户点击文章的时间差是有差异的。
1 2 mean_diff_created_time = user_click_merge.groupby('user_id' )['click_timestamp' , 'created_at_ts' ].apply(lambda x: mean_diff_time_func(x, 'created_at_ts' ))
1 plt.plot(sorted (mean_diff_created_time.values, reverse=True ))
从图中可以发现用户先后点击文章,文章的创建时间也是有差异的
1 2 item_idx_2_rawid_dict = dict (zip (item_emb_df['article_id' ], item_emb_df.index))
1 del item_emb_df['article_id' ]
1 item_emb_np = np.ascontiguousarray(item_emb_df.values, dtype=np.float32)
1 2 3 4 5 sub_user_ids = np.random.choice(user_click_merge.user_id.unique(), size=15 , replace=False ) sub_user_info = user_click_merge[user_click_merge['user_id' ].isin(sub_user_ids)] sub_user_info.head()
1 2 3 4 5 6 7 8 9 def get_item_sim_list (df ): sim_list = [] item_list = df['click_article_id' ].values for i in range (0 , len (item_list)-1 ): emb1 = item_emb_np[item_idx_2_rawid_dict[item_list[i]]] emb2 = item_emb_np[item_idx_2_rawid_dict[item_list[i+1 ]]] sim_list.append(np.dot(emb1,emb2)/(np.linalg.norm(emb1)*(np.linalg.norm(emb2)))) sim_list.append(0 ) return sim_list
1 2 3 for _, user_df in sub_user_info.groupby('user_id' ): item_sim_list = get_item_sim_list(user_df) plt.plot(item_sim_list)
从图中可以看出有些用户前后看的商品的相似度波动比较大,有些波动比较小,也是有一定的区分度的。
总结 通过数据分析的过程, 我们目前可以得到以下几点重要的信息, 这个对于我们进行后面的特征制作和分析非常有帮助:
训练集和测试集的用户id没有重复,也就是测试集里面的用户没有模型是没有见过的
训练集中用户最少的点击文章数是2, 而测试集里面用户最少的点击文章数是1
用户对于文章存在重复点击的情况, 但这个都存在于训练集里面
同一用户的点击环境存在不唯一的情况,后面做这部分特征的时候可以采用统计特征
用户点击文章的次数有很大的区分度,后面可以根据这个制作衡量用户活跃度的特征
文章被用户点击的次数也有很大的区分度,后面可以根据这个制作衡量文章热度的特征
用户看的新闻,相关性是比较强的,所以往往我们判断用户是否对某篇文章感兴趣的时候, 在很大程度上会和他历史点击过的文章有关
用户点击的文章字数有比较大的区别, 这个可以反映用户对于文章字数的区别
用户点击过的文章主题也有很大的区别, 这个可以反映用户的主题偏好 10.不同用户点击文章的时间差也会有所区别, 这个可以反映用户对于文章时效性的偏好
所以根据上面的一些分析,可以更好的帮助我们后面做好特征工程, 充分挖掘数据的隐含信息。