共计 8645 个字符,预计需要花费 22 分钟才能阅读完成。
数据分析
在上一节的内容中,曾经应用了 ItemCF 构建了一个 baseline,并失去了一个后果。如果咱们须要在 baseline 的根底上进一步晋升,就须要对数据进行进一步的剖析。
导入库
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
import re
import warnings
import sys
%matplotlib inline
# 不打印 ignore 级别的音讯
warnings.filterwarnings("ignore")
读取数据
data_dir = './data'
# train data
train_user_click_df = pd.read_csv(data_dir+'/train_click_log.csv')
item_df = pd.read_csv(data_dir+'/articles.csv')
item_df = item_df.rename(columns={'article_id': 'click_article_id'}) #重命名,不便后续 match
item_emb_df = pd.read_csv(data_dir+'articles_emb.csv')
# test data
test_user_click_df = pd.read_csv(data_dir+'testA_click_log.csv')
数据预处理
对每个用户的点击工夫戳进行降序排序,失去排名
train_user_click_df['rank'] = train_user_click_df.groupby(['user_id'])['click_timestamp'].rank(ascending=False).astype(int)
test_user_click_df['rank'] = test_user_click_df.groupby(['user_id'])['click_timestamp'].rank(ascending=False).astype(int)
计算用户点击文章的次数,并增加新的一列 count
train_user_click_df['click_cnts'] = train_user_click_df.groupby(['user_id'])['click_timestamp'].transform('count')
test_user_click_df['click_cnts'] = test_user_click_df.groupby(['user_id'])['click_timestamp'].transform('count')
数据浏览
用户点击日志文件_训练集
train_user_click_df = trn_click.merge(item_df, how='left', on=['click_article_id'])
train_user_click_df.head()
train_click_log.csv 文件数据中每个字段的含意
1. user_id: 用户的惟一标识
2. click_article_id: 用户点击的文章惟一标识
3. click_timestamp: 用户点击文章时的工夫戳
4. click_environment: 用户点击文章的环境
5. click_deviceGroup: 用户点击文章的设备组
6. click_os: 用户点击文章时的操作系统
7. click_country: 用户点击文章时的所在的国家
8. click_region: 用户点击文章时所在的区域
9. click_referrer_type: 用户点击文章时,文章的起源
# 用户点击日志信息
train_user_click_df.info()
train_user_click_df.describe()
train_user_click_df.user_id.nunique(), train_user_click_df.groupby('user_id')['click_article_id'].count().min()
200000, 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 = train_user_click_df[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%。
测试集用户点击日志
test_user_click_df = train_user_click_df.merge(item_df, how='left', on=['click_article_id'])
tst_click.head()
test_user_click_df.describe()
能够看出训练集和测试集的用户是齐全不一样的
训练集的用户 ID 由 0 ~ 199999,而测试集 A 的用户 ID 由 200000 ~ 249999。
# 测试集中的用户数量为 5w
test_user_click_df.user_id.nunique(), test_user_click_df.groupby('user_id')['click_article_id'].count().min()
50000, 1
新闻文章信息数据表
# 新闻文章数据集浏览
item_df.head().append(item_df.tail())
item_df['words_count'].value_counts()
item_df.shape
(364047, 4)
新闻文章 embedding 向量示意
item_emb_df.head()
item_emb_df.shape
(364047, 251)
数据分析
用户反复点击
# merge
user_click_merge = train_user_click_df.append(test_user_click_df)
#用户反复点击
user_click_count = user_click_merge.groupby(['user_id', 'click_article_id'])['click_timestamp'].agg({'count'}).reset_index()
user_click_count[:10]
user_click_count[user_click_count['count']>7]
user_click_count['count'].unique()
# 用户点击新闻次数
user_click_count.loc[:,'count'].value_counts()
能够看出:有 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(test_user_click_df['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)
能够看出绝大多数数的用户的点击环境是比拟固定的。思路:能够基于这些环境的统计特色来代表该用户自身的属性
用户点击新闻数量的散布
user_click_item_count = sorted(user_click_merge.groupby('user_id')['click_article_id'].count(), reverse=True)
plt.plot(user_click_item_count)
能够依据用户的点击文章次数看出用户的活跃度
# 点击次数在前 50 的用户
plt.plot(user_click_item_count[:50])
点击次数排前 50 的用户的点击次数都在 100 次以上。思路:咱们能够定义点击次数大于等于 100 次的用户为沉闷用户,这是一种简略的解决思路,判断用户活跃度,更加全面的是再联合上点击工夫,前面咱们会基于点击次数和点击工夫两个方面来判断用户活跃度。
# 点击次数排名在 [25000:50000] 之间
plt.plot(user_click_item_count[25000:50000])
能够看出点击次数小于等于两次的用户十分的多,这些用户能够认为是非沉闷用户
新闻点击次数剖析
item_click_count = sorted(user_click_merge.groupby('click_article_id')['user_id'].count(), reverse=True)
plt.plot(item_click_count)
plt.plot(item_click_count[:100])
能够看出点击次数最多的前 100 篇新闻,点击次数大于 1000 次
plt.plot(item_click_count[:20])
点击次数最多的前 20 篇新闻,点击次数大于 2500。思路:能够定义这些新闻为热门新闻,这个也是简略的解决形式,前面咱们也是依据点击次数和工夫进行文章热度的一个划分。
plt.plot(item_click_count[3500:])
能够发现很多新闻只被点击过一两次。思路:能够定义这些新闻是冷门新闻。
新闻共现频次:两篇新闻间断呈现的次数
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。
阐明用户看的新闻,相关性是比拟强的。
# 画个图直观地看一看
x = union_item['click_article_id']
y = union_item['count']
plt.scatter(x, y)
plt.plot(union_item['count'].values[40000:])
大略有 70000 个 pair 至多共现一次。
新闻文章信息
# 不同类型的新闻呈现的次数
plt.plot(user_click_merge['category_id'].value_counts().values)
# 呈现次数比拟少的新闻类型, 有些新闻类型,基本上就呈现过几次
plt.plot(user_click_merge['category_id'].value_counts().values[150:])
plt.plot(user_click_merge['words_count'].values)
用户点击的新闻类型的偏好
此特色能够用于度量用户的趣味是否宽泛。
plt.plot(sorted(user_click_merge.groupby('user_id')['category_id'].nunique(), reverse=True))
从上图中能够看出有一小部分用户浏览类型是极其宽泛的,大部分人都处在 20 个新闻类型以下。
user_click_merge.groupby('user_id')['category_id'].nunique().reset_index().describe()
用户查看文章的长度的散布
通过统计不同用户点击新闻的均匀字数,这个能够反映用户是对长文更感兴趣还是对短文更感兴趣。
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count'].mean(), reverse=True))
从上图中能够发现有一小部分人看的文章均匀词数十分高,也有一小部分人看的均匀文章次数非常低。
大多数人偏好于浏览字数在 200-400 字之间的新闻。
# 挑出大多数人的区间认真看看
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count'].mean(), reverse=True)[1000:45000])
能够发现大多数人都是看 250 字以下的文章
# 更加具体的参数
user_click_merge.groupby('user_id')['words_count'].mean().reset_index().describe()
用户点击新闻的工夫剖析
# 为了更好的可视化,这里把工夫进行归一化操作
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'] = mm.fit_transform(user_click_merge[['created_at_ts']])
user_click_merge = user_click_merge.sort_values('click_timestamp')
user_click_merge.head()
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'].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'].apply(lambda x: mean_diff_time_func(x, 'created_at_ts'))
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))
# 随机抉择 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)
从图中能够看出有些用户前后看的商品的类似度稳定比拟大,有些稳定比拟小,也是有肯定的区分度的。
总结
通过数据分析的过程,咱们目前能够失去以下几点重要的信息,这个对于咱们进行前面的特色制作和剖析十分有帮忙:
- 训练集和测试集的用户 id 没有反复,也就是测试集外面的用户模型是没有见过的。
- 训练集中用户起码的点击文章数是 2,而测试集外面用户起码的点击文章数是 1。
- 用户对于文章存在反复点击的状况,但这个都存在于训练集外面
- 同一用户的点击环境存在不惟一的状况,前面做这部分特色的时候能够采纳统计特色。
- 用户点击文章的次数有很大的区分度,前面能够依据这个制作掂量用户活跃度的特色。
- 文章被用户点击的次数也有很大的区分度,前面能够依据这个制作掂量文章热度的特色。
- 用户看的新闻,相关性是比拟强的,所以往往咱们判断用户是否对某篇文章感兴趣的时候,在很大水平上会和他历史点击过的文章无关
- 用户点击的文章字数有比拟大的区别,这个能够反映用户对于文章字数的区别。
- 用户点击过的文章主题也有很大的区别,这个能够反映用户的主题偏好。
- 不同用户点击文章的时间差也会有所区别,这个能够反映用户对于文章时效性的偏好。
所以依据下面的一些剖析,能够更好的帮忙咱们前面做好特色工程,充沛开掘数据的隐含信息。
[1] 天池新闻举荐入门赛之【数据分析】Task02