新闻推荐 Task 2 数据分析
数据分析是机器学习/深度学习中很重要的一部分,俗话说的好,再好的模型,输入不行,那也得是 “garbage in,garbage out”。所以,在在特征工程之前,对数据有一个整体的认知是很重要的。这次我们就来探索一下本次比赛数据的整体情况。
首先,载入数据
%matplotlib inline
# 数据相关
import pandas as pd
import numpy as np
# 可视化相关
import matplotlib.pyplot as plt
import seaborn as sns
plt.rc('font', family='SimHei', size=13)
# 系统,通知相关
import os,gc,re,warnings,sys
warnings.filterwarnings("ignore")
# 数据存放路径
path = '/home/admin/jupyter/data/'
# 读取训练集
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'}) #重命名,方便后续match
item_emb_df = pd.read_csv(path+'articles_emb.csv')
# 读取测试集
tst_click = pd.read_csv(path+'testA_click_log.csv')
# 训练集包含的特征
trn_click.columns
Index(['user_id', 'click_article_id', 'click_timestamp', 'click_environment',
'click_deviceGroup', 'click_os', 'click_country', 'click_region',
'click_referrer_type'],
dtype='object')
train_click_log.csv文件数据中每个字段的含义
- user_id: 用户的唯一标识
- click_article_id: 用户点击的文章唯一标识
- click_timestamp: 用户点击文章时的时间戳
- click_environment: 用户点击文章的环境
- click_deviceGroup: 用户点击文章的设备组
- click_os: 用户点击文章时的操作系统
- click_country: 用户点击文章时的所在的国家
- click_region: 用户点击文章时所在的区域
- click_referrer_type: 用户点击文章时,文章的来源
数据预处理
# 对每个用户的点击时间戳进行排序
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)
# 计算用户点击文章的次数,并添加新的一列count
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')
trn_click.columns
Index(['user_id', 'click_article_id', 'click_timestamp', 'click_environment',
'click_deviceGroup', 'click_os', 'click_country', 'click_region',
'click_referrer_type', 'rank', 'click_cnts'],
dtype='object')
新增特征
- rank: 用户的点击时间排序
- click_cnts: 用户点击文章的次数
查看数据
整体统计信息
trn_click = trn_click.merge(item_df, how='left', on=['click_article_id'])
trn_click.head(3).append(trn_click.tail(3)) # 头尾各3条数据
user_id | click_article_id | click_timestamp | click_environment | click_deviceGroup | click_os | click_country | click_region | click_referrer_type | rank | click_cnts | category_id_x | created_at_ts_x | words_count_x | category_id_y | created_at_ts_y | words_count_y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 199999 | 160417 | 1507029570190 | 4 | 1 | 17 | 1 | 13 | 1 | 11 | 11 | 281 | 1506942089000 | 173 | 281 | 1506942089000 | 173 |
1 | 199999 | 5408 | 1507029571478 | 4 | 1 | 17 | 1 | 13 | 1 | 10 | 11 | 4 | 1506994257000 | 118 | 4 | 1506994257000 | 118 |
2 | 199999 | 50823 | 1507029601478 | 4 | 1 | 17 | 1 | 13 | 1 | 9 | 11 | 99 | 1507013614000 | 213 | 99 | 1507013614000 | 213 |
1112620 | 0 | 157507 | 1508211702520 | 4 | 1 | 17 | 1 | 25 | 2 | 1 | 2 | 281 | 1508236945000 | 370 | 281 | 1508236945000 | 370 |
1112621 | 199178 | 234481 | 1508211513583 | 4 | 3 | 2 | 1 | 25 | 2 | 2 | 14 | 375 | 1508181572000 | 212 | 375 | 1508181572000 | 212 |
1112622 | 199178 | 233578 | 1508211543583 | 4 | 3 | 2 | 1 | 25 | 2 | 1 | 14 | 375 | 1508176010000 | 174 | 375 | 1508176010000 | 174 |
# 用户点击数据信息
trn_click.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1112623 entries, 0 to 1112622
Data columns (total 17 columns):
user_id 1112623 non-null int64
click_article_id 1112623 non-null int64
click_timestamp 1112623 non-null int64
click_environment 1112623 non-null int64
click_deviceGroup 1112623 non-null int64
click_os 1112623 non-null int64
click_country 1112623 non-null int64
click_region 1112623 non-null int64
click_referrer_type 1112623 non-null int64
rank 1112623 non-null int64
click_cnts 1112623 non-null int64
category_id_x 1112623 non-null int64
created_at_ts_x 1112623 non-null int64
words_count_x 1112623 non-null int64
category_id_y 1112623 non-null int64
created_at_ts_y 1112623 non-null int64
words_count_y 1112623 non-null int64
dtypes: int64(17)
memory usage: 152.8 MB
可见无缺失值
trn_click.describe() # 查看数据分布情况
user_id | click_article_id | click_timestamp | click_environment | click_deviceGroup | click_os | click_country | click_region | click_referrer_type | rank | click_cnts | category_id_x | created_at_ts_x | words_count_x | category_id_y | created_at_ts_y | words_count_y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 | 1.112623e+06 |
mean | 1.221198e+05 | 1.951541e+05 | 1.507588e+12 | 3.947786e+00 | 1.815981e+00 | 1.301976e+01 | 1.310776e+00 | 1.813587e+01 | 1.910063e+00 | 7.118518e+00 | 1.323704e+01 | 3.056176e+02 | 1.506598e+12 | 2.011981e+02 | 3.056176e+02 | 1.506598e+12 | 2.011981e+02 |
std | 5.540349e+04 | 9.292286e+04 | 3.363466e+08 | 3.276715e-01 | 1.035170e+00 | 6.967844e+00 | 1.618264e+00 | 7.105832e+00 | 1.220012e+00 | 1.016095e+01 | 1.631503e+01 | 1.155791e+02 | 8.343066e+09 | 5.223881e+01 | 1.155791e+02 | 8.343066e+09 | 5.223881e+01 |
min | 0.000000e+00 | 3.000000e+00 | 1.507030e+12 | 1.000000e+00 | 1.000000e+00 | 2.000000e+00 | 1.000000e+00 | 1.000000e+00 | 1.000000e+00 | 1.000000e+00 | 2.000000e+00 | 1.000000e+00 | 1.166573e+12 | 0.000000e+00 | 1.000000e+00 | 1.166573e+12 | 0.000000e+00 |
25% | 7.934700e+04 | 1.239090e+05 | 1.507297e+12 | 4.000000e+00 | 1.000000e+00 | 2.000000e+00 | 1.000000e+00 | 1.300000e+01 | 1.000000e+00 | 2.000000e+00 | 4.000000e+00 | 2.500000e+02 | 1.507220e+12 | 1.700000e+02 | 2.500000e+02 | 1.507220e+12 | 1.700000e+02 |
50% | 1.309670e+05 | 2.038900e+05 | 1.507596e+12 | 4.000000e+00 | 1.000000e+00 | 1.700000e+01 | 1.000000e+00 | 2.100000e+01 | 2.000000e+00 | 4.000000e+00 | 8.000000e+00 | 3.280000e+02 | 1.507553e+12 | 1.970000e+02 | 3.280000e+02 | 1.507553e+12 | 1.970000e+02 |
75% | 1.704010e+05 | 2.777120e+05 | 1.507841e+12 | 4.000000e+00 | 3.000000e+00 | 1.700000e+01 | 1.000000e+00 | 2.500000e+01 | 2.000000e+00 | 8.000000e+00 | 1.600000e+01 | 4.100000e+02 | 1.507756e+12 | 2.280000e+02 | 4.100000e+02 | 1.507756e+12 | 2.280000e+02 |
max | 1.999990e+05 | 3.640460e+05 | 1.510603e+12 | 4.000000e+00 | 5.000000e+00 | 2.000000e+01 | 1.100000e+01 | 2.800000e+01 | 7.000000e+00 | 2.410000e+02 | 2.410000e+02 | 4.600000e+02 | 1.510666e+12 | 6.690000e+03 | 4.600000e+02 | 1.510666e+12 | 6.690000e+03 |
# 查看训练集中的用户数量
print("共{}名用户".format(trn_click.user_id.nunique()))
共200000名用户
# 训练集里面每个用户至少点击文章数
print("每位用户至少点击了{}篇文章".format(trn_click.groupby('user_id')['click_article_id'].count().min()))
每位用户至少点击了2篇文章
通过直方图查看基本属性分布
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%。
测试集数据情况
tst_click = tst_click.merge(item_df, how='left', on=['click_article_id'])
tst_click.head(3).append(tst_click.tail(3))
user_id | click_article_id | click_timestamp | click_environment | click_deviceGroup | click_os | click_country | click_region | click_referrer_type | rank | click_cnts | category_id_x | created_at_ts_x | words_count_x | category_id_y | created_at_ts_y | words_count_y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 249999 | 160974 | 1506959142820 | 4 | 1 | 17 | 1 | 13 | 2 | 19 | 19 | 281 | 1506912747000 | 259 | 281 | 1506912747000 | 259 |
1 | 249999 | 160417 | 1506959172820 | 4 | 1 | 17 | 1 | 13 | 2 | 18 | 19 | 281 | 1506942089000 | 173 | 281 | 1506942089000 | 173 |
2 | 249998 | 160974 | 1506959056066 | 4 | 1 | 12 | 1 | 13 | 2 | 5 | 5 | 281 | 1506912747000 | 259 | 281 | 1506912747000 | 259 |
518007 | 207823 | 234481 | 1508211850103 | 4 | 3 | 2 | 1 | 25 | 1 | 3 | 14 | 375 | 1508181572000 | 212 | 375 | 1508181572000 | 212 |
518008 | 207823 | 211442 | 1508212189949 | 4 | 3 | 2 | 1 | 25 | 1 | 2 | 14 | 340 | 1508187720000 | 240 | 340 | 1508187720000 | 240 |
518009 | 207823 | 211401 | 1508212315718 | 4 | 3 | 2 | 1 | 25 | 1 | 1 | 14 | 340 | 1507747434000 | 243 | 340 | 1507747434000 | 243 |
tst_click.describe()
user_id | click_article_id | click_timestamp | click_environment | click_deviceGroup | click_os | click_country | click_region | click_referrer_type | rank | click_cnts | category_id_x | created_at_ts_x | words_count_x | category_id_y | created_at_ts_y | words_count_y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 518010.000000 | 518010.000000 | 5.180100e+05 | 518010.000000 | 518010.000000 | 518010.000000 | 518010.000000 | 518010.000000 | 518010.000000 | 518010.000000 | 518010.000000 | 518010.000000 | 5.180100e+05 | 518010.000000 | 518010.000000 | 5.180100e+05 | 518010.000000 |
mean | 227342.428169 | 193803.792550 | 1.507387e+12 | 3.947300 | 1.738285 | 13.628467 | 1.348209 | 18.250250 | 1.819614 | 15.521785 | 30.043586 | 305.324961 | 1.506883e+12 | 210.966331 | 305.324961 | 1.506883e+12 | 210.966331 |
std | 14613.907188 | 88279.388177 | 3.706127e+08 | 0.323916 | 1.020858 | 6.625564 | 1.703524 | 7.060798 | 1.082657 | 33.957702 | 56.868021 | 110.411513 | 5.816668e+09 | 83.040065 | 110.411513 | 5.816668e+09 | 83.040065 |
min | 200000.000000 | 137.000000 | 1.506959e+12 | 1.000000 | 1.000000 | 2.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.265812e+12 | 0.000000 | 1.000000 | 1.265812e+12 | 0.000000 |
25% | 214926.000000 | 128551.000000 | 1.507026e+12 | 4.000000 | 1.000000 | 12.000000 | 1.000000 | 13.000000 | 1.000000 | 4.000000 | 10.000000 | 252.000000 | 1.506970e+12 | 176.000000 | 252.000000 | 1.506970e+12 | 176.000000 |
50% | 229109.000000 | 199197.000000 | 1.507308e+12 | 4.000000 | 1.000000 | 17.000000 | 1.000000 | 21.000000 | 2.000000 | 8.000000 | 19.000000 | 323.000000 | 1.507249e+12 | 199.000000 | 323.000000 | 1.507249e+12 | 199.000000 |
75% | 240182.000000 | 272143.000000 | 1.507666e+12 | 4.000000 | 3.000000 | 17.000000 | 1.000000 | 25.000000 | 2.000000 | 18.000000 | 35.000000 | 399.000000 | 1.507630e+12 | 232.000000 | 399.000000 | 1.507630e+12 | 232.000000 |
max | 249999.000000 | 364043.000000 | 1.508832e+12 | 4.000000 | 5.000000 | 20.000000 | 11.000000 | 28.000000 | 7.000000 | 938.000000 | 938.000000 | 460.000000 | 1.509949e+12 | 3082.000000 | 460.000000 | 1.509949e+12 | 3082.000000 |
# 查看测试集中的用户数量
print("共{}名用户".format(tst_click.user_id.nunique()))
共50000名用户
# 测试集里面每个用户至少点击文章数
print("每位用户至少点击了{}篇文章".format(tst_click.groupby('user_id')['click_article_id'].count().min()))
每位用户至少点击了1篇文章
新闻文章信息数据表
#新闻文章数据集浏览
item_df.head(3).append(item_df.tail(3))
click_article_id | category_id | created_at_ts | words_count | |
---|---|---|---|---|
0 | 0 | 0 | 1513144419000 | 168 |
1 | 1 | 1 | 1405341936000 | 189 |
2 | 2 | 1 | 1408667706000 | 250 |
364044 | 364044 | 460 | 1457974279000 | 177 |
364045 | 364045 | 460 | 1515964737000 | 126 |
364046 | 364046 | 460 | 1505811330000 | 479 |
item_df['words_count'].value_counts()
176 3485
182 3480
179 3463
178 3458
174 3456
183 3432
184 3427
173 3414
180 3403
177 3391
170 3387
187 3355
169 3352
185 3348
175 3346
181 3330
186 3328
189 3327
171 3327
172 3322
165 3308
188 3288
167 3269
190 3261
192 3257
168 3248
193 3225
166 3199
191 3182
194 3164
...
601 1
857 1
1977 1
1626 1
697 1
1720 1
696 1
706 1
592 1
1605 1
586 1
582 1
1606 1
972 1
716 1
584 1
1608 1
715 1
841 1
968 1
964 1
587 1
1099 1
1355 1
711 1
845 1
710 1
965 1
847 1
1535 1
Name: words_count, Length: 866, dtype: int64
print(item_df['category_id'].nunique()) # 461个文章主题
item_df['category_id'].hist()
461
item_df.shape # 364047篇文章
(364047, 4)
新闻文章embedding向量表示
item_emb_df.head(3).append(item_emb_df.tail(3))
article_id | emb_0 | emb_1 | emb_2 | emb_3 | emb_4 | emb_5 | emb_6 | emb_7 | emb_8 | ... | emb_240 | emb_241 | emb_242 | emb_243 | emb_244 | emb_245 | emb_246 | emb_247 | emb_248 | emb_249 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | -0.161183 | -0.957233 | -0.137944 | 0.050855 | 0.830055 | 0.901365 | -0.335148 | -0.559561 | -0.500603 | ... | 0.321248 | 0.313999 | 0.636412 | 0.169179 | 0.540524 | -0.813182 | 0.286870 | -0.231686 | 0.597416 | 0.409623 |
1 | 1 | -0.523216 | -0.974058 | 0.738608 | 0.155234 | 0.626294 | 0.485297 | -0.715657 | -0.897996 | -0.359747 | ... | -0.487843 | 0.823124 | 0.412688 | -0.338654 | 0.320786 | 0.588643 | -0.594137 | 0.182828 | 0.397090 | -0.834364 |
2 | 2 | -0.619619 | -0.972960 | -0.207360 | -0.128861 | 0.044748 | -0.387535 | -0.730477 | -0.066126 | -0.754899 | ... | 0.454756 | 0.473184 | 0.377866 | -0.863887 | -0.383365 | 0.137721 | -0.810877 | -0.447580 | 0.805932 | -0.285284 |
364044 | 364044 | -0.251390 | -0.976243 | 0.586097 | 0.643631 | -0.663359 | -0.093480 | 0.691553 | -0.588281 | 0.902999 | ... | -0.162220 | -0.242030 | -0.476131 | 0.352132 | -0.311279 | 0.460574 | -0.653077 | -0.143725 | 0.068093 | -0.705010 |
364045 | 364045 | 0.224342 | -0.923288 | -0.381742 | 0.687890 | -0.773911 | -0.103629 | -0.406486 | 0.246004 | 0.255191 | ... | -0.422999 | 0.390324 | 0.655911 | -0.646753 | -0.174031 | 0.698037 | -0.317102 | 0.687132 | -0.531512 | 0.010726 |
364046 | 364046 | -0.257134 | -0.994631 | 0.983792 | -0.190975 | -0.953720 | -0.893823 | 0.708974 | -0.557027 | 0.846842 | ... | -0.490481 | -0.689666 | -0.661846 | 0.490945 | 0.736526 | 0.667668 | 0.902130 | 0.983873 | -0.838183 | -0.179283 |
6 rows × 251 columns
item_emb_df.shape
(364047, 251)
数据分析
用户重复点击
# 综合分析
user_click_merge = trn_click.append(tst_click)
# 用户重复点击
user_click_count = user_click_merge.groupby(['user_id', 'click_article_id'])['click_timestamp'].agg({'count'}).reset_index()
user_click_count[:10]
user_id | click_article_id | count | |
---|---|---|---|
0 | 0 | 30760 | 1 |
1 | 0 | 157507 | 1 |
2 | 1 | 63746 | 1 |
3 | 1 | 289197 | 1 |
4 | 2 | 36162 | 1 |
5 | 2 | 168401 | 1 |
6 | 3 | 36162 | 1 |
7 | 3 | 50644 | 1 |
8 | 4 | 39894 | 1 |
9 | 4 | 42567 | 1 |
user_click_count[user_click_count['count']>7]
user_id | click_article_id | count | |
---|---|---|---|
311242 | 86295 | 74254 | 10 |
311243 | 86295 | 76268 | 10 |
393761 | 103237 | 205948 | 10 |
393763 | 103237 | 235689 | 10 |
576902 | 134850 | 69463 | 13 |
user_click_count['count'].unique()
array([ 1, 2, 4, 3, 6, 5, 10, 7, 13])
# 用户点击新闻次数
user_click_count.loc[:,'count'].value_counts()
1 1605541
2 11621
3 422
4 77
5 26
6 12
10 4
7 3
13 1
Name: count, dtype: int64
可以看出:有1605541(约占99.2%)的用户未重复阅读过文章,仅有极少数用户重复点击过某篇文章。 这个也可以单独制作成特征
用户点击环境变化分析
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()
# 分析用户点击环境变化是否明显,这里随机采样10个用户分析这些用户的点击环境分布
sample_user_ids = np.random.choice(tst_click['user_id'].unique(), size=10, 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)
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
<Figure size 432x288 with 0 Axes>
可以看出绝大多数数的用户的点击环境是比较固定的。思路:可以基于这些环境的统计特征来代表该用户本身的属性。
### 用户点击新闻数量的分布
user_click_item_count = sorted(user_click_merge.groupby('user_id')['click_article_id'].count(), reverse=True)
plt.plot(user_click_item_count)
[<matplotlib.lines.Line2D at 0x7fbc87832da0>]
可以根据用户的点击文章次数看出用户的活跃度
# 点击次数在前50的用户
plt.plot(user_click_item_count[:50])
[<matplotlib.lines.Line2D at 0x7fbc9183a240>]
点击次数排前50的用户的点击次数都在100次以上。思路:我们可以定义点击次数大于等于100次的用户为活跃用户,这是一种简单的处理思路, 判断用户活跃度,更加全面的是再结合上点击时间,后面我们会基于点击次数和点击时间两个方面来判断用户活跃度。
# 点击次数排名在[25000:50000]之间
plt.plot(user_click_item_count[25000:50000])
[<matplotlib.lines.Line2D at 0x7fbc918be908>]
可以看出点击次数小于等于两次的用户非常的多,这些用户可以认为是非活跃用户
新闻点击次数分析
item_click_count = sorted(user_click_merge.groupby('click_article_id')['user_id'].count(), reverse=True)
plt.plot(item_click_count)
[<matplotlib.lines.Line2D at 0x7fbc8a07acc0>]
plt.plot(item_click_count[:100])
[<matplotlib.lines.Line2D at 0x7fbc8f025358>]
可以看出点击次数最多的前100篇新闻,点击次数大于1000次
plt.plot(item_click_count[:20])
[<matplotlib.lines.Line2D at 0x7fbc940354a8>]
点击次数最多的前20篇新闻,点击次数大于2500。思路:可以定义这些新闻为热门新闻, 这个也是简单的处理方式,后面我们也是根据点击次数和时间进行文章热度的一个划分
plt.plot(item_click_count[3500:])
[<matplotlib.lines.Line2D at 0x7fbc940b97f0>]
可以发现很多新闻只被点击过一两次。思路:可以定义这些新闻是冷门新闻
新闻共现频次:两篇新闻连续出现的次数
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()
count | |
---|---|
count | 433597.000000 |
mean | 3.184139 |
std | 18.851753 |
min | 1.000000 |
25% | 1.000000 |
50% | 1.000000 |
75% | 2.000000 |
max | 2202.000000 |
由统计数据可以看出,平均共现次数2.88,最高为1687。
说明用户看的新闻,相关性是比较强的。
#画个图直观地看一看
x = union_item['click_article_id']
y = union_item['count']
plt.scatter(x, y)
<matplotlib.collections.PathCollection at 0x7fbc9186cd30>
plt.plot(union_item['count'].values[40000:])
[<matplotlib.lines.Line2D at 0x7fbbcda35630>]
大概有70000个pair至少共现一次
新闻文章信息
user_click_merge.columns
Index(['user_id', 'click_article_id', 'click_timestamp', 'click_environment',
'click_deviceGroup', 'click_os', 'click_country', 'click_region',
'click_referrer_type', 'rank', 'click_cnts', 'category_id_x',
'created_at_ts_x', 'words_count_x', 'category_id_y', 'created_at_ts_y',
'words_count_y'],
dtype='object')
# 不同类型的新闻出现的次数
plt.plot(user_click_merge['category_id_x'].value_counts().values)
[<matplotlib.lines.Line2D at 0x7fbc8a142710>]
# 出现次数比较少的新闻类型, 有些新闻类型,基本上就出现过几次
plt.plot(user_click_merge['category_id_y'].value_counts().values[150:])
[<matplotlib.lines.Line2D at 0x7fbc8a184f60>]
#新闻字数的描述性统计
user_click_merge['words_count_x'].describe()
count 1.630633e+06
mean 2.043012e+02
std 6.382198e+01
min 0.000000e+00
25% 1.720000e+02
50% 1.970000e+02
75% 2.290000e+02
max 6.690000e+03
Name: words_count_x, dtype: float64
plt.plot(user_click_merge['words_count_x'].values)
[<matplotlib.lines.Line2D at 0x7fbc8a0c9e10>]
用户点击的新闻类型的偏好
此特征可以用于度量用户的兴趣是否广泛。
plt.plot(sorted(user_click_merge.groupby('user_id')['category_id_x'].nunique(), reverse=True))
[<matplotlib.lines.Line2D at 0x7fbc8f0a7908>]
从上图中可以看出有一小部分用户阅读类型是极其广泛的,大部分人都处在20个新闻类型以下。
user_click_merge.groupby('user_id')['category_id_x'].nunique().reset_index().describe()
user_id | category_id_x | |
---|---|---|
count | 250000.000000 | 250000.000000 |
mean | 124999.500000 | 4.573188 |
std | 72168.927986 | 4.419800 |
min | 0.000000 | 1.000000 |
25% | 62499.750000 | 2.000000 |
50% | 124999.500000 | 3.000000 |
75% | 187499.250000 | 6.000000 |
max | 249999.000000 | 95.000000 |
用户查看文章的长度的分布
通过统计不同用户点击新闻的平均字数,这个可以反映用户是对长文更感兴趣还是对短文更感兴趣。
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count_x'].mean(), reverse=True))
[<matplotlib.lines.Line2D at 0x7fbbcd964a90>]
从上图中可以发现有一小部分人看的文章平均词数非常高,也有一小部分人看的平均文章次数非常低。
大多数人偏好于阅读字数在200-400字之间的新闻。
#挑出大多数人的区间仔细看看
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count_x'].mean(), reverse=True)[1000:45000])
[<matplotlib.lines.Line2D at 0x7fbbccf4e4e0>]
可以发现大多数人都是看250字以下的文章
# 更加详细的参数
user_click_merge.groupby('user_id')['words_count_x'].mean().reset_index().describe()
user_id | words_count_x | |
---|---|---|
count | 250000.000000 | 250000.000000 |
mean | 124999.500000 | 205.830189 |
std | 72168.927986 | 47.174030 |
min | 0.000000 | 8.000000 |
25% | 62499.750000 | 187.500000 |
50% | 124999.500000 | 202.000000 |
75% | 187499.250000 | 217.750000 |
max | 249999.000000 | 3434.500000 |
用户点击新闻的时间分析
# 为了更好的可视化,这里把时间进行归一化操作
from sklearn.preprocessing import MinMaxScaler
mm = MinMaxScaler()
user_click_merge['click_timestamp'] = mm.fit_transform(user_click_merge[['click_timestamp']])
user_click_merge['created_at_ts_x'] = mm.fit_transform(user_click_merge[['created_at_ts_x']])
user_click_merge = user_click_merge.sort_values('click_timestamp')
user_click_merge.head()
user_id | click_article_id | click_timestamp | click_environment | click_deviceGroup | click_os | click_country | click_region | click_referrer_type | rank | click_cnts | category_id_x | created_at_ts_x | words_count_x | category_id_y | created_at_ts_y | words_count_y | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
18 | 249990 | 162300 | 0.000000 | 4 | 3 | 20 | 1 | 25 | 2 | 5 | 5 | 281 | 0.989186 | 193 | 281 | 1506945129000 | 193 |
2 | 249998 | 160974 | 0.000002 | 4 | 1 | 12 | 1 | 13 | 2 | 5 | 5 | 281 | 0.989092 | 259 | 281 | 1506912747000 | 259 |
30 | 249985 | 160974 | 0.000003 | 4 | 1 | 17 | 1 | 8 | 2 | 8 | 8 | 281 | 0.989092 | 259 | 281 | 1506912747000 | 259 |
50 | 249979 | 162300 | 0.000004 | 4 | 1 | 17 | 1 | 25 | 2 | 2 | 2 | 281 | 0.989186 | 193 | 281 | 1506945129000 | 193 |
25 | 249988 | 160974 | 0.000004 | 4 | 1 | 17 | 1 | 21 | 2 | 17 | 17 | 281 | 0.989092 | 259 | 281 | 1506912747000 | 259 |
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()
# 点击时间差的平均值
mean_diff_click_time = user_click_merge.groupby('user_id')['click_timestamp', 'created_at_ts_x'].apply(lambda x: mean_diff_time_func(x, 'click_timestamp'))
plt.plot(sorted(mean_diff_click_time.values, reverse=True))
从上图可以发现不同用户点击文章的时间差是有差异的
# 前后点击文章的创建时间差的平均值
mean_diff_created_time = user_click_merge.groupby('user_id')['click_timestamp', 'created_at_ts_x'].apply(lambda x: mean_diff_time_func(x, 'created_at_ts_x'))
plt.plot(sorted(mean_diff_created_time.values, reverse=True))
从图中可以发现用户先后点击文章,文章的创建时间也是有差异的
# 用户前后点击文章的相似性分布
item_idx_2_rawid_dict = dict(zip(item_emb_df['article_id'], item_emb_df.index))
del item_emb_df['article_id']
item_emb_np = np.ascontiguousarray(item_emb_df.values, dtype=np.float32)
# 随机选择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()
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
for _, user_df in sub_user_info.groupby('user_id'):
item_sim_list = get_item_sim_list(user_df)
plt.plot(item_sim_list)
从图中可以看出有些用户前后看的商品的相似度波动比较大,有些波动比较小,也是有一定的区分度的