数据分析
数据分析的价值主要在于熟悉了解整个数据集的基本情况包括每个文件里有哪些数据,具体的文件中的每个字段表示什么实际含义,以及数据集中特征之间的相关性,在推荐场景下主要就是分析用户本身的基本属性,文章基本属性,以及用户和文章交互的一些分布,这些都有利于后面的召回策略的选择,以及特征工程。
建议:当特征工程和模型调参已经很难继续上分了,可以回来在重新从新的角度去分析这些数据,或许可以找到上分的灵感
导包
%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
#seaborn用于画图的包
plt.rc('font', family='SimHei', size=13)
#pyplot 使用 rc 配置文件来自定义图形的各种默认属性,被称为 rc 配置或 rc 参数。
#‘font.family’ 用于显示字体的名字
#‘font.style’ 字体风格,正常’normal’ 或斜体’italic’
#‘font.size’ 字体大小,整数字号或者’large’ ‘x-small’
import os,gc,re,warnings,sys
warnings.filterwarnings("ignore")
#屏蔽掉警告
读取数据
path = './data_raw/'
#####train
trn_click = pd.read_csv(path+'train_click_log.csv')
#trn_click = pd.read_csv(path+'train_click_log.csv', names=['user_id','item_id','click_time','click_environment',
#'click_deviceGroup','click_os','click_country','click_region','click_referrer_type'])
#上面的这个是给数据复制属性名字
#这个是训练文件。这个是用户的点击文件信息。也就是点击一次记录一次。也就是记录的是每个人的点击情况。
#训练文件里面的还是原来的9个特征。
item_df = pd.read_csv(path+'articles.csv')
#读取文件articles.csv。这个文件里面只有4个属性,article_id,category_id,created_at_ts,words_count
#这个文件就是每篇文章的信息。item_df就是文章的信息。
item_df = item_df.rename(columns={'article_id': 'click_article_id'}) #重命名,方便后续match
#文章信息文件中的article_id替换为click_article_id
item_emb_df = pd.read_csv(path+'articles_emb.csv')
#读取另一篇articles_emb文件,属性有article_id,emb_0,emb_1....这些都是数字。
#embedding。可以理解为把文章一个个字都翻译成了相应的数字。文章对应的词嵌入。
#####test
#这个是测试文件
tst_click = pd.read_csv(path+'testA_click_log.csv')
数据预处理
计算用户点击rank和点击次数
g = trn_click.groupby(['user_id'])
#g类型是DataFrameGroupBy
#print(g)#这个打印不出来
#print(g.size())
#g.size()统计每个分组成员的数量
h = trn_click.groupby(['user_id'])['click_timestamp']
#h类型是SeriesGroupBy
#print(h) #这个打印不出来
m = trn_click.groupby(['user_id'])['click_timestamp'].rank(ascending=False).astype(int)
#按降序进行排名
#m的类型是series.Series
#也就是说m得到的是原来序列的排名情况(经过降序排名的)
#print(m)
# 对每个用户的点击时间戳进行排序
#groupby参考链接https://blog.csdn.net/missyougoon/article/details/84022999
#rank参考链接https://blog.csdn.net/starter_____/article/details/79183595
trn_click['rank'] = trn_click.groupby(['user_id'])['click_timestamp'].rank(ascending=False).astype(int)
#在训练数据集中添加了一个新的属性rank。astype是类型转换。
tst_click['rank'] = tst_click.groupby(['user_id'])['click_timestamp'].rank(ascending=False).astype(int)
#计算用户点击文章的次数,并添加新的一列count
f = trn_click.groupby(['user_id'])['click_timestamp'].transform('count')
#f的类型是series.Series
#print(f)
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和item_df两个文件
trn_click = trn_click.merge(item_df, how='left', on=['click_article_id']) #根据文章的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: 用户点击文章时,文章的来源
#用户点击日志信息
trn_click.info()
#trn_click这个是合并后的文件。也就是用户点击文件加上文章信息文件
#训练集中的用户数量为20w
trn_click.user_id.nunique()
#nunique()的意思就是计算不同值的数量
#去用户id的不同值,也就是计算有多少用户
trn_click.groupby('user_id')['click_article_id'].count().min() # 训练集里面每个用户至少点击了两篇文章
#count() 计算非 NaN 值的数量
画直方图大体看一下基本的属性分布
plt.figure() #画布
plt.figure(figsize=(15, 20)) #画布大小
i = 1
#这里是10个属性,需要查看的10属性
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个图
i += 1
v = trn_click[col].value_counts().reset_index()[:10]
#trn_click的类型是DataFrame
#trn_click[col]的类型是Series
#trn_click[col].value_counts()的类型是Series
#trn_click[col].value_counts().reset_index()的类型是DataFrame
#也就是说v是各个属性的不同值的统计,前10名
#pandas的value_counts()函数可以对Series里面的每个值进行计数并且排序。
#可以使用reset_index()重置索引。
fig = sns.barplot(x=v['index'], y=v[col])
#横坐标是index,index是v的一个属性,纵坐标是具体的数值。
#画条形图。条形图主要展现的是每个矩形高度的数值变量的中心趋势的估计。
for item in fig.get_xticklabels():#拿到坐标轴上的标签
#print(item)
item.set_rotation(90) #将标签旋转90度。
plt.title(col) #标题是这些属性
plt.tight_layout() #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'])
#按照属性click_article_id来进行合并,item_df是文章信息文件
#合并测试数据集和item_df文件
tst_click.head()
我们可以看出训练集和测试集的用户是完全不一样的
训练集的用户ID由0 ~ 199999,而测试集A的用户ID由200000 ~ 249999。
#测试集中的用户数量为5w
tst_click.user_id.nunique()
#nunique()是不同的值,也就是有多少个用户
新闻文章信息数据表
#新闻文章数据集浏览
item_df.head().append(item_df.tail())
#item_df.head()表示的是前5条信息,item_df.tail()表示的后面的5条信息
#append表示的是添加
item_df['words_count'].value_counts()
#对words_count这个属性进行统计
#item_df['words_count'].value_counts()的类型是Series
print(item_df['category_id'].nunique()) # 461个文章主题
#category是类别的意思,也就是文章不同的主题有461个
item_df['category_id'].hist()
#画直方图
新闻文章embedding向量表示
数据分析
用户重复点击
#####merge
user_click_merge = trn_click.append(tst_click)
#user_click_merge将训练数据集和测试数据集合并起来
#用户重复点击
print(type(user_click_merge.groupby(['user_id', 'click_article_id'])['click_timestamp']))
user_click_count = user_click_merge.groupby(['user_id', 'click_article_id'])['click_timestamp'].agg({'count'}).reset_index()
#user_click_merge.groupby(['user_id', 'click_article_id'])的类型是DataFrameGroupBy
#user_click_merge.groupby(['user_id', 'click_article_id'])['click_timestamp']的类型是SeriesGroupBy
#先按user_id分组,在按click_article_id分组。对于一个人,时间戳是唯一标识。
user_click_count[:10]
user_click_count[user_click_count['count']>7]
#user_click_count['count']>7的类型是Series
#将重复数字大于7的显示出来
user_click_count['count'].unique()
#user_click_count是训练数据集和测试数据集的合集
#显示点击数量的不同值
#用户点击新闻次数
user_click_count.loc[:,'count'].value_counts()
#统计点击数量这个属性所对应的用户数量
#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(tst_click['user_id'].unique(), size=10, replace=False)
#随机选取了10个用户的id。
sample_users = user_click_merge[user_click_merge['user_id'].isin(sample_user_ids)]
#user_click_merge将训练数据集和测试数据集合并起来
#这个应该是取出样本用户的数据
#sample_users的类型是DataFrame
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)
#user_click_merge.groupby('user_id')['click_article_id']类型是SeriesGroupBy
#user_click_merge.groupby('user_id')['click_article_id'].count()的类型是Series
#user_click_item_count 的类型是list
#user_click_item_count是文章主题的统计
plt.plot(user_click_item_count)
可以根据用户的点击文章次数看出用户的活跃度
#点击次数在前50的用户
plt.plot(user_click_item_count[:50])
#点击次数排名在[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)
#user_click_merge是训练集和测试机的合集
plt.plot(item_click_count)
plt.plot(item_click_count[:100])
可以看出点击次数最多的前100篇新闻,点击次数大于1000次
plt.plot(item_click_count[:20])
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()
新闻文章信息
用户点击的新闻类型的偏好
此特征可以用于度量用户的兴趣是否广泛。
用户查看文章的长度的分布
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count'].mean(), reverse=True))
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'))
从上图可以发现不同用户点击文章的时间差是有差异的
# 前后点击文章的创建时间差的平均值
#这个用时有点长
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'))
# 用户前后点击文章的相似性分布
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)
总结
通过数据分析的过程, 我们目前可以得到以下几点重要的信息, 这个对于我们进行后面的特征制作和分析非常有帮助:
- 训练集和测试集的用户id没有重复,也就是测试集里面的用户没有模型是没有见过的
- 训练集中用户最少的点击文章数是2, 而测试集里面用户最少的点击文章数是1
- 用户对于文章存在重复点击的情况, 但这个都存在于训练集里面
- 同一用户的点击环境存在不唯一的情况,后面做这部分特征的时候可以采用统计特征
- 用户点击文章的次数有很大的区分度,后面可以根据这个制作衡量用户活跃度的特征
- 文章被用户点击的次数也有很大的区分度,后面可以根据这个制作衡量文章热度的特征
- 用户看的新闻,相关性是比较强的,所以往往我们判断用户是否对某篇文章感兴趣的时候, 在很大程度上会和他历史点击过的文章有关
- 用户点击的文章字数有比较大的区别, 这个可以反映用户对于文章字数的区别
- 用户点击过的文章主题也有很大的区别, 这个可以反映用户的主题偏好
- 不同用户点击文章的时间差也会有所区别, 这个可以反映用户对于文章时效性的偏好
所以根据上面的一些分析,可以更好的帮助我们后面做好特征工程, 充分挖掘数据的隐含信息。