新闻推荐之数据分析

新闻推荐之数据分析

目的:数据分析的价值主要在于熟悉了解整个数据集的基本情况包括每个文件里有哪些数据,具体的文件中的每个字段表示什么实际含义,以及数据集中特征之间的相关性,在推荐场景下主要就是分析用户本身的基本属性,文章基本属性,以及用户和文章交互的一些分布,这些都有利于后面的召回策略的选择,以及特征工程。

%matplotlib inline
import pandas as pd
import numpy as np
import datetime

import matplotlib.pyplot as plt
import seaborn as sns
plt.rc('font', family='Times New Roman', size=13)

import os,gc,re,warnings,sys
warnings.filterwarnings("ignore")

DATA_PATH = "./data/"

读取数据

训练集(日志)

#training 20万用户的日志数据
trn_click = pd.read_csv(DATA_PATH + "train_click_log.csv")
trn_click.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_type
0199999160417150702957019041171131
11999995408150702957147841171131
219999950823150702960147841171131
3199998157770150702953220041171255
419999896613150702967183141171255

数据特征说明

  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: 用户点击文章时,文章的来源
trn_click.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1112623 entries, 0 to 1112622
Data columns (total 9 columns):
 #   Column               Non-Null Count    Dtype
---  ------               --------------    -----
 0   user_id              1112623 non-null  int64
 1   click_article_id     1112623 non-null  int64
 2   click_timestamp      1112623 non-null  int64
 3   click_environment    1112623 non-null  int64
 4   click_deviceGroup    1112623 non-null  int64
 5   click_os             1112623 non-null  int64
 6   click_country        1112623 non-null  int64
 7   click_region         1112623 non-null  int64
 8   click_referrer_type  1112623 non-null  int64
dtypes: int64(9)
memory usage: 76.4 MB

文章信息

# 此处读取的文章信息中,其对应的文章ID与训练集中的应相同
item_df = pd.read_csv(DATA_PATH + "articles.csv")
item_df.head()
article_idcategory_idcreated_at_tswords_count
0001513144419000168
1111405341936000189
2211408667706000250
3311408468313000230
4411407071171000162
# 总共36万篇文章
item_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 364047 entries, 0 to 364046
Data columns (total 4 columns):
 #   Column         Non-Null Count   Dtype
---  ------         --------------   -----
 0   article_id     364047 non-null  int64
 1   category_id    364047 non-null  int64
 2   created_at_ts  364047 non-null  int64
 3   words_count    364047 non-null  int64
dtypes: int64(4)
memory usage: 11.1 MB

文章Embedding信息

item_emb_df = pd.read_csv(DATA_PATH + "articles_emb.csv")
item_emb_df.head()
article_idemb_0emb_1emb_2emb_3emb_4emb_5emb_6emb_7emb_8...emb_240emb_241emb_242emb_243emb_244emb_245emb_246emb_247emb_248emb_249
00-0.161183-0.957233-0.1379440.0508550.8300550.901365-0.335148-0.559561-0.500603...0.3212480.3139990.6364120.1691790.540524-0.8131820.286870-0.2316860.5974160.409623
11-0.523216-0.9740580.7386080.1552340.6262940.485297-0.715657-0.897996-0.359747...-0.4878430.8231240.412688-0.3386540.3207860.588643-0.5941370.1828280.397090-0.834364
22-0.619619-0.972960-0.207360-0.1288610.044748-0.387535-0.730477-0.066126-0.754899...0.4547560.4731840.377866-0.863887-0.3833650.137721-0.810877-0.4475800.805932-0.285284
33-0.740843-0.9757490.3916980.641738-0.2686450.191745-0.825593-0.710591-0.040099...0.2715350.0360400.480029-0.7631730.0226270.565165-0.910286-0.5378380.243541-0.885329
44-0.279052-0.9723150.6853740.1130560.2383150.271913-0.5688160.341194-0.600554...0.2382860.8092680.427521-0.615932-0.5036970.614450-0.917760-0.4240610.185484-0.580292

5 rows × 251 columns

测试集

# testing 5万用户的数据,且与训练集的数据不相重叠
tst_click = pd.read_csv(DATA_PATH + "testA_click_log.csv")
tst_click.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_type
0249999160974150695914282041171132
1249999160417150695917282041171132
2249998160974150695905606641121132
3249998202557150695908606641121132
4249997183665150695908861341171155
tst_click.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 518010 entries, 0 to 518009
Data columns (total 9 columns):
 #   Column               Non-Null Count   Dtype
---  ------               --------------   -----
 0   user_id              518010 non-null  int64
 1   click_article_id     518010 non-null  int64
 2   click_timestamp      518010 non-null  int64
 3   click_environment    518010 non-null  int64
 4   click_deviceGroup    518010 non-null  int64
 5   click_os             518010 non-null  int64
 6   click_country        518010 non-null  int64
 7   click_region         518010 non-null  int64
 8   click_referrer_type  518010 non-null  int64
dtypes: int64(9)
memory usage: 35.6 MB

数据预处理

# 修改对应特征名称
item_df.rename(columns={"article_id":"click_article_id"}, inplace=True)
# 对每一位用户按照时间序列进行rank, 时间越靠前级别越高
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)
# 统计每一个用户所点击的文章数目
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.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_typerankclick_cnts
01999991604171507029570190411711311111
119999954081507029571478411711311011
219999950823150702960147841171131911
31999981577701507029532200411712554040
4199998966131507029671831411712553940
# 训练集(合并文章属性特征)
trn_click = trn_click.merge(item_df, how="left", on=["click_article_id"])
trn_click.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_typerankclick_cntscategory_idcreated_at_tswords_count
019999916041715070295701904117113111112811506942089000173
11999995408150702957147841171131101141506994257000118
219999950823150702960147841171131911991507013614000213
319999815777015070295322004117125540402811506983935000201
41999989661315070296718314117125539402091506938444000185
trn_click.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1112623 entries, 0 to 1112622
Data columns (total 14 columns):
 #   Column               Non-Null Count    Dtype
---  ------               --------------    -----
 0   user_id              1112623 non-null  int64
 1   click_article_id     1112623 non-null  int64
 2   click_timestamp      1112623 non-null  int64
 3   click_environment    1112623 non-null  int64
 4   click_deviceGroup    1112623 non-null  int64
 5   click_os             1112623 non-null  int64
 6   click_country        1112623 non-null  int64
 7   click_region         1112623 non-null  int64
 8   click_referrer_type  1112623 non-null  int64
 9   rank                 1112623 non-null  int64
 10  click_cnts           1112623 non-null  int64
 11  category_id          1112623 non-null  int64
 12  created_at_ts        1112623 non-null  int64
 13  words_count          1112623 non-null  int64
dtypes: int64(14)
memory usage: 127.3 MB
trn_click.describe().T
countmeanstdmin25%50%75%max
user_id1112623.01.221198e+055.540349e+040.000000e+007.934700e+041.309670e+051.704010e+051.999990e+05
click_article_id1112623.01.951541e+059.292286e+043.000000e+001.239090e+052.038900e+052.777120e+053.640460e+05
click_timestamp1112623.01.507588e+123.363466e+081.507030e+121.507297e+121.507596e+121.507841e+121.510603e+12
click_environment1112623.03.947786e+003.276715e-011.000000e+004.000000e+004.000000e+004.000000e+004.000000e+00
click_deviceGroup1112623.01.815981e+001.035170e+001.000000e+001.000000e+001.000000e+003.000000e+005.000000e+00
click_os1112623.01.301976e+016.967844e+002.000000e+002.000000e+001.700000e+011.700000e+012.000000e+01
click_country1112623.01.310776e+001.618264e+001.000000e+001.000000e+001.000000e+001.000000e+001.100000e+01
click_region1112623.01.813587e+017.105832e+001.000000e+001.300000e+012.100000e+012.500000e+012.800000e+01
click_referrer_type1112623.01.910063e+001.220012e+001.000000e+001.000000e+002.000000e+002.000000e+007.000000e+00
rank1112623.07.118518e+001.016095e+011.000000e+002.000000e+004.000000e+008.000000e+002.410000e+02
click_cnts1112623.01.323704e+011.631503e+012.000000e+004.000000e+008.000000e+001.600000e+012.410000e+02
category_id1112623.03.056176e+021.155791e+021.000000e+002.500000e+023.280000e+024.100000e+024.600000e+02
created_at_ts1112623.01.506598e+128.343066e+091.166573e+121.507220e+121.507553e+121.507756e+121.510666e+12
words_count1112623.02.011981e+025.223881e+010.000000e+001.700000e+021.970000e+022.280000e+026.690000e+03
# 用户数目
trn_click["user_id"].nunique()
200000
# 每一位用户至少阅读两篇文章
trn_click.groupby("user_id")["click_timestamp"].count().min()
2

统计用户的使用特征数目

trn_click["click_os"].value_counts().reset_index()
indexclick_os
017648777
12310298
220121057
31220308
4138721
5192914
65531
7317
plt.figure(figsize=(12, 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(normalize=True).reset_index()[:10]
    fig = sns.barplot(x=v["index"], y=v[col])
    for f in fig.get_xticklabels():
        f.set_rotation(90)
    plt.title(col)
    
plt.tight_layout()
plt.savefig("imgs/数据分布情况.png")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d5sZ87k1-1610603535687)(output_27_0.png)]

测试集用户日志

# 测试与训练的中用户ID没有重复
tst_click = tst_click.merge(item_df, how='left', on=['click_article_id'])
tst_click.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_typerankclick_cntscategory_idcreated_at_tswords_count
024999916097415069591428204117113219192811506912747000259
124999916041715069591728204117113218192811506942089000173
2249998160974150695905606641121132552811506912747000259
3249998202557150695908606641121132453271506938401000219
4249997183665150695908861341171155773011500895686000256
tst_click.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 518010 entries, 0 to 518009
Data columns (total 14 columns):
 #   Column               Non-Null Count   Dtype
---  ------               --------------   -----
 0   user_id              518010 non-null  int64
 1   click_article_id     518010 non-null  int64
 2   click_timestamp      518010 non-null  int64
 3   click_environment    518010 non-null  int64
 4   click_deviceGroup    518010 non-null  int64
 5   click_os             518010 non-null  int64
 6   click_country        518010 non-null  int64
 7   click_region         518010 non-null  int64
 8   click_referrer_type  518010 non-null  int64
 9   rank                 518010 non-null  int64
 10  click_cnts           518010 non-null  int64
 11  category_id          518010 non-null  int64
 12  created_at_ts        518010 non-null  int64
 13  words_count          518010 non-null  int64
dtypes: int64(14)
memory usage: 59.3 MB
tst_click.describe().T
countmeanstdmin25%50%75%max
user_id518010.02.273424e+051.461391e+042.000000e+052.149260e+052.291090e+052.401820e+052.499990e+05
click_article_id518010.01.938038e+058.827939e+041.370000e+021.285510e+051.991970e+052.721430e+053.640430e+05
click_timestamp518010.01.507387e+123.706127e+081.506959e+121.507026e+121.507308e+121.507666e+121.508832e+12
click_environment518010.03.947300e+003.239161e-011.000000e+004.000000e+004.000000e+004.000000e+004.000000e+00
click_deviceGroup518010.01.738285e+001.020858e+001.000000e+001.000000e+001.000000e+003.000000e+005.000000e+00
click_os518010.01.362847e+016.625564e+002.000000e+001.200000e+011.700000e+011.700000e+012.000000e+01
click_country518010.01.348209e+001.703524e+001.000000e+001.000000e+001.000000e+001.000000e+001.100000e+01
click_region518010.01.825025e+017.060798e+001.000000e+001.300000e+012.100000e+012.500000e+012.800000e+01
click_referrer_type518010.01.819614e+001.082657e+001.000000e+001.000000e+002.000000e+002.000000e+007.000000e+00
rank518010.01.552179e+013.395770e+011.000000e+004.000000e+008.000000e+001.800000e+019.380000e+02
click_cnts518010.03.004359e+015.686802e+011.000000e+001.000000e+011.900000e+013.500000e+019.380000e+02
category_id518010.03.053250e+021.104115e+021.000000e+002.520000e+023.230000e+023.990000e+024.600000e+02
created_at_ts518010.01.506883e+125.816668e+091.265812e+121.506970e+121.507249e+121.507630e+121.509949e+12
words_count518010.02.109663e+028.304006e+010.000000e+001.760000e+021.990000e+022.320000e+023.082000e+03
# 五万测试用户
tst_click.user_id.nunique()
50000
# 测试否有训练集重复用户
tst_click.user_id.isin(trn_click.user_id).sum()
0
# 测试集中,每一位用户至少点击一篇文章
tst_click.groupby('user_id')['click_article_id'].count().min()
1

新闻文章信息数据表

item_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 364047 entries, 0 to 364046
Data columns (total 4 columns):
 #   Column            Non-Null Count   Dtype
---  ------            --------------   -----
 0   click_article_id  364047 non-null  int64
 1   category_id       364047 non-null  int64
 2   created_at_ts     364047 non-null  int64
 3   words_count       364047 non-null  int64
dtypes: int64(4)
memory usage: 11.1 MB
item_df.head().append(item_df.tail())
click_article_idcategory_idcreated_at_tswords_count
0001513144419000168
1111405341936000189
2211408667706000250
3311408468313000230
4411407071171000162
3640423640424601434034118000144
3640433640434601434148472000463
3640443640444601457974279000177
3640453640454601515964737000126
3640463640464601505811330000479
value = item_df["words_count"].value_counts()
plt.hist(value.values, bins=20)
(array([644.,  34.,  21.,  16.,  12.,  13.,   7.,  10.,   8.,   7.,   7.,
          9.,   5.,   8.,   7.,   9.,   7.,  12.,  10.,  20.]),
 array([1.0000e+00, 1.7520e+02, 3.4940e+02, 5.2360e+02, 6.9780e+02,
        8.7200e+02, 1.0462e+03, 1.2204e+03, 1.3946e+03, 1.5688e+03,
        1.7430e+03, 1.9172e+03, 2.0914e+03, 2.2656e+03, 2.4398e+03,
        2.6140e+03, 2.7882e+03, 2.9624e+03, 3.1366e+03, 3.3108e+03,
        3.4850e+03]),
 <BarContainer object of 20 artists>)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAYnelCK-1610603535692)(output_38_1.png)]

知识点

频数分布直方图(hist:连续)条与条之间无间隔,而条形统计图(bar:离散)有。
1)条形统计图中,横轴上的数据是孤立的,是一个具体的数据。而直方图中,横轴上的数据是连续的,是一个范围。

2)条形统计图是用条形的高度表示频数的大小。而直方图是用长方形的面积表示频数,长方形的面积越大,就表示这组数据的频数越大;只有当长方形的宽都相等时,才可以用长方形的高表示频数的大小。

3)条形统计图中,各个数据之间是相对独立的,各个条形之间是有空隙的。而在直方图中,各长方形对应的是一个范围,由于每两个相邻范围之间不重叠、不遗漏,因此在直方图中,长方形之间没有空隙

# 文章的分类主题更加倾向于id靠后的数据
print(item_df['category_id'].nunique())  # 461个文章主题
item_df['category_id'].hist()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hJSE7obt-1610603535695)(output_40_2.png)]

# 连续性随机变量可以绘制其概括密度曲线
sns.kdeplot(item_df['category_id'], shade=True)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wzkvaugR-1610603535700)(output_41_1.png)]

结论:文章创建的主题大多分布在id400-500之间。

用户是否重复点击

# 全量数据
user_click_df = trn_click.append(tst_click)
user_click_df = user_click_df.reset_index(drop=True)
user_click_df.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_typerankclick_cntscategory_idcreated_at_tswords_count
019999916041715070295701904117113111112811506942089000173
11999995408150702957147841171131101141506994257000118
219999950823150702960147841171131911991507013614000213
319999815777015070295322004117125540402811506983935000201
41999989661315070296718314117125539402091506938444000185
# 统计同一用户在不同时间段所点击的同一用户的次数
dub_cnts = user_click_df.groupby(["user_id", "click_article_id"])["click_timestamp"].agg(["count"])
dub_cnts = dub_cnts.reset_index()
# 用户重复点击次数超过七次的数据
dub_cnts[dub_cnts["count"]>7]
user_idclick_article_idcount
311242862957425410
311243862957626810
39376110323720594810
39376310323723568910
5769021348506946313
dub_cnts["count"].unique()
array([ 1,  2,  4,  3,  6,  5, 10,  7, 13])
dub_cnts["count"].value_counts(normalize=True)
1     9.924795e-01
2     7.183625e-03
3     2.608631e-04
4     4.759824e-05
5     1.607213e-05
6     7.417907e-06
10    2.472636e-06
7     1.854477e-06
13    6.181589e-07
Name: count, dtype: float64

结论:约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()
        v.rename(columns={"index":"classes"}, inplace=True)
        fig = sns.barplot(x=v['classes'], 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_df[user_click_df['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)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6fzfdcb-1610603535705)(output_52_1.png)]

结论:通过采样数据发现,用户的点击环境、点击设备组、点击操作系统和点击的国家集中稳定在同一类型中,而点击区域和点击来源有轻微的波动,整体趋于稳定,以上说明用户自身的点击环境表现出相对稳定的状态。

用户点击的文章数目情况

目的:查看用户的点击情况,分析活跃用户和不活跃用户。

user_click_item_count = user_click_df.groupby("user_id")["click_article_id"].count()
plt.plot(sorted(user_click_item_count, reverse=True))
[<matplotlib.lines.Line2D at 0x7f75b999d1f0>]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lCXmDLsX-1610603535735)(output_55_1.png)]

活跃用户

plt.plot(sorted(user_click_item_count, reverse=True)[:50])
[<matplotlib.lines.Line2D at 0x7f75b97d5d30>]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OFEh4dZ9-1610603535736)(output_57_1.png)]

结论:点击次数排前50的用户的点击次数都在100次以上。
思路我们可以定义点击次数大于等于100次的用户为活跃用户,这是一种简单的处理思路, 判断用户活跃度,更加全面的是再结合上点击时间,后面我们会基于点击次数和点击时间两个方面来判断用户活跃度(区分累计用户活跃度和单位时间内的用户活跃度)。

不活跃用户

plt.plot(sorted(user_click_item_count, reverse=True)[25000:50000])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QlULHfAq-1610603535737)(output_60_1.png)]

sns.kdeplot(sorted(user_click_item_count, reverse=True)[:100], shade=True)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0QYMd4o-1610603535738)(output_61_1.png)]

结论:大多数用户点击文章次数较少,服从长尾分布, 因此可以在推荐过程中考虑去除活跃用户对整体推荐影响。

新闻点击次数分析(热门文章)

目的:统计每一篇新闻所阅读的用户数目,查看用户点击最多文章的点击数目。

# 热门文章分析
article_cnts = sorted(user_click_df.groupby("click_article_id")["user_id"].count(), reverse=True)
plt.plot(article_cnts)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MM73dwWH-1610603535739)(output_64_1.png)]

plt.plot(article_cnts[:100])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7YfIg6e-1610603535741)(output_65_1.png)]

结论:点击次数最多的前100篇新闻,点击次数都是大于1000,因此可以设定文章点击次数的阈值,从而对热门文章进行区分,此外可以引入时间特征来区分短是时间内的热门文章和长时间的热门文章

热门新闻

# 热门新闻可以在多路召回过程中进行候选推荐
plt.plot(article_cnts[:20])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CMW8pRYN-1610603535742)(output_68_1.png)]

冷门新闻

# 大多数新闻被用户极少读到
plt.plot(article_cnts[3500:])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q3Uct5rE-1610603535743)(output_70_1.png)]

# 新闻点击情况呈现偏态分布
sns.kdeplot(article_cnts, shade=True)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a23AcAjM-1610603535747)(output_71_1.png)]

结论:召回阶段可以考虑对热门新闻进行召回,能够体现新闻"新"的特点。

新闻共现频率

# 同一新闻下一次被点击的次数
tmp = user_click_df.sort_values("click_timestamp")
tmp["next_item"] = tmp.groupby("user_id")["click_article_id"].transform(lambda x:x.shift(-1))
union_tmp = tmp.groupby(["click_article_id", "next_item"]
                       )["click_timestamp"].agg(["count"]
                                               ).reset_index().sort_values("count", ascending=False)
union_tmp.describe().T
countmeanstdmin25%50%75%max
click_article_id433597.0187340.53746295745.85986769.0108856.0198659.0272145.0364046.0
next_item433597.0188408.26223796211.4860603.0107253.0202289.0272428.0364043.0
count433597.03.18413918.8517531.01.01.02.02202.0
# 从中可以看出大多数文章都具有共现性
plt.scatter(x=union_tmp.click_article_id, y=union_tmp["count"]
            , c="r"
            , alpha=0.6
           )

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Be4GU5Hc-1610603535750)(output_76_1.png)]

# 至少有70000对数据出现了至少一次共现的情况
plt.plot(union_tmp["count"].values[40000:])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJ5yGICC-1610603535751)(output_77_1.png)]

新闻文章类别信息

# 前100个热门文章类型的出现较为频繁
plt.plot(user_click_df["category_id"].value_counts().values[:100])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fnQHkbZM-1610603535752)(output_79_1.png)]

# 冷门文章类型的数目较少
plt.plot(user_click_df["category_id"].value_counts().values[150:])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqLinQwn-1610603535753)(output_80_1.png)]

plt.plot(user_click_df['words_count'].values)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z790LPQz-1610603535754)(output_81_1.png)]

# 大多数文章的字数篇幅都在600子之内
plt.hist(user_click_df['words_count'].values, bins=20)
(array([1.615704e+06, 7.795000e+03, 6.977000e+03, 8.800000e+01,
        5.000000e+01, 6.000000e+00, 0.000000e+00, 0.000000e+00,
        5.000000e+00, 5.000000e+00, 0.000000e+00, 2.000000e+00,
        0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00,
        0.000000e+00, 0.000000e+00, 0.000000e+00, 1.000000e+00]),
 array([   0. ,  334.5,  669. , 1003.5, 1338. , 1672.5, 2007. , 2341.5,
        2676. , 3010.5, 3345. , 3679.5, 4014. , 4348.5, 4683. , 5017.5,
        5352. , 5686.5, 6021. , 6355.5, 6690. ]),
 <BarContainer object of 20 artists>)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0FCKNBGD-1610603535756)(output_82_1.png)]

用户点击新闻类型的偏好

目的:查看用户所观看的文章类型,找出共性,且此特征可以用于度量用户的兴趣是否广泛

plt.plot(sorted(user_click_df.groupby("user_id")["category_id"].nunique(), reverse=True))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1sCGyMDh-1610603535757)(output_84_1.png)]

# 每位用户阅读文章的多样性
bar_data = user_click_df.groupby("user_id")["category_id"].nunique().value_counts(normalize=True)[:10]
plt.bar(bar_data.index, bar_data.values, color="r", alpha=0.4)
plt.xticks(bar_data.index)
plt.ylabel("percent(%)")
plt.xlabel("the number of user diversity")
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KHT5g1bW-1610603535758)(output_85_0.png)]

结论:用户喜欢点击新闻种类数目范围不大,其中30%的用户喜欢点击两种类型的文章,因此在推荐的过程中其多样性不能过于分散。

用户查看文章的长度分布

目的:通过统计不同用户点击新闻的平均字数,这个可以反映用户是对长文更感兴趣还是对短文更感兴趣。

plt.plot(sorted(user_click_df.groupby('user_id')['words_count'].mean(), reverse=True))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNHMbLJM-1610603535760)(output_88_1.png)]

#挑出大多数人的区间仔细看看
plt.plot(sorted(user_click_df.groupby('user_id')['words_count'].mean(), reverse=True)[1000:45000])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wcu5cEhH-1610603535761)(output_89_1.png)]

结论:可以考虑在文章推荐的过程中,其它特征情况类似的条件下,优先推荐文章篇幅较少的文章。

知识点:箱线图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0jn7FKi-1610603535762)(./imgs/Boxplot_vs_PDF.svg)]

sns.boxplot(sorted(user_click_df.groupby('user_id')['words_count'].mean(), reverse=True)[1000:45000], orient="v")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuoKxBMa-1610603535763)(output_92_1.png)]

用户点击新闻的时间分析

#为了更好的可视化,这里把时间进行归一化操作
from sklearn.preprocessing import MinMaxScaler
mm = MinMaxScaler()
user_click_df['click_timestamp'] = mm.fit_transform(user_click_df[['click_timestamp']])
user_click_df['created_at_ts'] = mm.fit_transform(user_click_df[['created_at_ts']])

# 按照时间序列进行排序
user_click_df = user_click_df.sort_values('click_timestamp')
user_click_df
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_typerankclick_cntscategory_idcreated_at_tswords_count
11126412499901623000.00000043201252552810.989186193
11126252499981609740.00000241121132552810.989092259
11126532499851609740.0000034117182882810.989092259
11126732499791623000.00000441171252222810.989186193
11126482499881609740.0000044117121217172810.989092259
.............................................
7056561211582241480.779105411711321543540.997195212
925954702542076720.860177411712022143310.998288242
92595570254963330.860185411712021142090.998272299
109117724652035380.9999924117122283270.999741275
109117824651453091.0000004117122182691.000000216

1630633 rows × 14 columns

# 每一位用户的平均点击时差均值
def mean_diff_time_fun(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_df.groupby("user_id")["created_at_ts", "click_timestamp"].apply(lambda x: mean_diff_time_fun(x, "click_timestamp"))
plt.plot(sorted(mean_diff_click_time.values, reverse=True))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sp8eomro-1610603535764)(output_98_1.png)]

结论:不同用户点击新闻所产生的时间差具有一定的差异性,可以将用户的时间差作为新闻点击的依据。

# 前后点击文章的创建时间差的平均值
mean_diff_created_time = user_click_df.groupby('user_id')['click_timestamp', 'created_at_ts'].apply(lambda x: mean_diff_time_fun(x, 'created_at_ts'))
plt.plot(sorted(mean_diff_created_time.values, reverse=True))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4NmjE1t-1610603535766)(output_101_1.png)]

Word2Vec

from gensim.models import Word2Vec
import logging, pickle
# 需要注意这里模型只迭代了一次
def trian_item_word2vec(click_df, embed_size=16, save_name='item_w2v_emb.pkl', split_char=' '):
    click_df = click_df.sort_values('click_timestamp')
    # 只有转换成字符串才可以进行训练
    click_df['click_article_id'] = click_df['click_article_id'].astype(str)
    # 转换成句子的形式
    #[['30760', '157507'],
    #  ['289197', '63746'],
    #  ['36162', '168401'].....]
    docs = click_df.groupby(['user_id'])['click_article_id'].apply(lambda x: list(x)).reset_index()
    docs = docs['click_article_id'].values.tolist()

    # 为了方便查看训练的进度,这里设定一个log信息
    logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)

    # 这里的参数对训练得到的向量影响也很大,默认负采样为5
    w2v = Word2Vec(docs, size=16, sg=1, window=5, seed=2020, workers=24, min_count=1, iter=10)
    
    # 保存成字典的形式
    item_w2v_emb_dict = {k: w2v[k] for k in click_df['click_article_id']}
    
    return item_w2v_emb_dict
# 得到词向量
item_w2v_emb_dict = trian_item_word2vec(user_click_df)
# 随机选择15个用户,查看这些用户前后查看文章的相似性
sub_user_ids = np.random.choice(user_click_df.user_id.unique(), size=15, replace=False)
sub_user_info = user_click_df[user_click_df['user_id'].isin(sub_user_ids)]

sub_user_info.head()
user_idclick_article_idclick_timestampclick_environmentclick_deviceGroupclick_osclick_countryclick_regionclick_referrer_typerankclick_cntscategory_idcreated_at_tswords_count
12177482109422853430.00934041121256114120.989285226
1236666204343643290.01696143211282221340.989259199
12366672043431565600.01700743211281122810.989222185
432231848221566190.02578041171252442810.989436275
432241848221991970.02578841171252343230.989421238
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_w2v_emb_dict[str(item_list[i])] # 需要注意的是word2vec训练时候使用的是str类型的数据
        emb2 = item_w2v_emb_dict[str(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
# 同一用户的文章大多相似
plt.figure(figsize=(14, 6))

for i, (_, user_df) in enumerate(sub_user_info.groupby('user_id')):
    item_sim_list = get_item_sim_list(user_df)
    plt.plot(item_sim_list, marker="o", linestyle="-.", label="user"+str(i+1))

plt.legend()
plt.ylabel("releration rete(%)")

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g0Spje93-1610603535768)(output_108_1.png)]

结论:同一用户所喜欢点击的文章基本高度相似,因此针对某一用户可以展开个性化的推荐,如召回阶段对物品相似度给与更多的权值。

总结

通过数据分析的过程, 我们目前可以得到以下几点重要的信息, 这个对于我们进行后面的特征制作和分析非常有帮助:

  1. 训练集和测试集的用户id没有重复,也就是测试集里面的用户模型是没有见过的
  2. 训练集中用户最少的点击文章数是2, 而测试集里面用户最少的点击文章数是1
  3. 用户对于文章存在重复点击的情况, 但这个都存在于训练集里面
  4. 同一用户的点击环境存在不唯一的情况,后面做这部分特征的时候可以采用统计特征
  5. 用户点击文章的次数有很大的区分度,后面可以根据这个制作衡量用户活跃度的特征
  6. 文章被用户点击的次数也有很大的区分度,后面可以根据这个制作衡量文章热度的特征
  7. 用户看的新闻,相关性是比较强的,所以往往我们判断用户是否对某篇文章感兴趣的时候, 在很大程度上会和他历史点击过的文章有关
  8. 用户点击的文章字数有比较大的区别, 这个可以反映用户对于文章字数的区别
  9. 用户点击过的文章主题也有很大的区别, 这个可以反映用户的主题偏好
    10.不同用户点击文章的时间差也会有所区别, 这个可以反映用户对于文章时效性的偏好

所以根据上面的一些分析,可以更好的帮助我们后面做好特征工程, 充分挖掘数据的隐含信息。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值