关于推荐系统:推荐系统入门之数据分析天池新闻推荐

3次阅读

共计 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)


从图中能够看出有些用户前后看的商品的类似度稳定比拟大,有些稳定比拟小,也是有肯定的区分度的。

总结

通过数据分析的过程,咱们目前能够失去以下几点重要的信息,这个对于咱们进行前面的特色制作和剖析十分有帮忙:

  1. 训练集和测试集的用户 id 没有反复,也就是测试集外面的用户模型是没有见过的。
  2. 训练集中用户起码的点击文章数是 2,而测试集外面用户起码的点击文章数是 1。
  3. 用户对于文章存在反复点击的状况,但这个都存在于训练集外面
  4. 同一用户的点击环境存在不惟一的状况,前面做这部分特色的时候能够采纳统计特色。
  5. 用户点击文章的次数有很大的区分度,前面能够依据这个制作掂量用户活跃度的特色。
  6. 文章被用户点击的次数也有很大的区分度,前面能够依据这个制作掂量文章热度的特色。
  7. 用户看的新闻,相关性是比拟强的,所以往往咱们判断用户是否对某篇文章感兴趣的时候,在很大水平上会和他历史点击过的文章无关
  8. 用户点击的文章字数有比拟大的区别,这个能够反映用户对于文章字数的区别。
  9. 用户点击过的文章主题也有很大的区别,这个能够反映用户的主题偏好。
  10. 不同用户点击文章的时间差也会有所区别,这个能够反映用户对于文章时效性的偏好。

所以依据下面的一些剖析,能够更好的帮忙咱们前面做好特色工程,充沛开掘数据的隐含信息。

[1] 天池新闻举荐入门赛之【数据分析】Task02

正文完
 0