目次
3.4. ニュース共起頻度:2つのニュース記事が連続して出現する回数
4.2. ユーザーが前後に閲覧した記事の類似性をチェックする
1. データの前処理
1.1. ユーザーのクリックランクとクリック数を計算する
%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 = '新建文件夹/推荐系统/零基础入门推荐系统 - 新闻推荐/'
# trn_click = pd.read_csv(path + 'train_click_log.csv')
trn_click = pd.read_csv(path+'train_click_log.csv',encoding= 'utf-8-sig',sep = r'\s*,\s*',header = 0)
trn_click.head()
item_df = trn_click = pd.read_csv(path + 'articles.csv',encoding= 'utf-8-sig',sep = r'\s*,\s*',header = 0)
item_df = item_df.rename(columns={'article_id': 'click_article_id'}) #重命名,方便后续match
item_df.head()
item_emb_df = pd.read_csv(path+'articles_emb.csv',encoding= 'utf-8-sig',sep = r'\s*,\s*',header = 0)
item_emb_df.head()
print(trn_click.columns.tolist())
Pandas チュートリアル | 超簡単に使える Groupby 使い方解説
# 对每个用户的点击时间戳进行排序
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')
2. データ閲覧
2.1、トレーニングセットユーザーのクリックログ
trn_click と item_df をユーザー クリック ログ トレーニング セットにマージします
#用户点击日志-训练集
trn_click = trn_click.merge(item_df, how='left', on=['click_article_id'])
trn_click.head()
trn_click.describe().T
trn_click.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1112623 エントリ、0 ~ 1112622 データ列 (合計 14 列): # Column Non-Null Count Dtype --- ------ ------ -------- ----- 0 user_id 1112623 null 以外の int64 1 click_article_id 1112623 null 以外の int64 2 click_timestamp 1112623 null 以外の int64 3 click_environment 1112623 null 以外の int64 4 click_deviceGroup 1112623 null 以外の int64 5 click_os 1112623 非 null int64 6 click_country 1112623 非 null int64 7 click_region 1112623 非 null int64 8 click_referrer_type 1112623 非 null int64 9 ランク 1112623 非 null int32 10 click_cnts 1112623 非 null int64 11 category_id 1112623 非 null int64 12 created_at_ts 1112623 非 null int64 13 words_count 1112623 非 null int64 dtype: int32(1)、int64(13) メモリ使用量: 123.1 MB
- 合計 20000 ユーザー
trn_click.user_id.nunique()#200000
- 各ユーザーがトレーニング セット内の少なくとも 2 つの記事をクリックした
trn_click.groupby('user_id')['click_article_id'].count().min() # 训练集里面每个用户至少点击了两篇文章
- クリック環境 click_environment は非常に安定して変化し、2102 回 (0.19%) のクリック環境のみが 1、25894 回のみ (2.3%) のクリック環境が 2、残り (97.6%) のクリック環境が 4 です。
trn_click['click_environment'].value_counts()
4 1084627 2 25894 1 2102 名前: click_environment、dtype: int64
- デバイス グループ click_deviceGroup をクリックすると、デバイス 1 が過半数 (61%) を占め、デバイス 3 が 36% を占めます。
trn_click['click_deviceGroup'].value_counts()
1 678187 3 395558 4 38731 5 141 2 6 名前: click_deviceGroup、dtype: int64
2.2、テスト セットのユーザー クリック ログ
#测试集用户点击日志
tst_click = tst_click.merge(item_df, how='left', on=['click_article_id'])
tst_click.head()
tst_click.describe()
tst_click.user_id.nunique()#50000
tst_click.groupby('user_id')['click_article_id'].count().min() # 注意测试集里面有只点击过一次文章的用户
トレーニング セットのユーザー ID の範囲は 0 ~ 199999 で、テスト セット A のユーザー ID の範囲は 200000 ~ 249999 です。
トレーニング中には、テスト セットのデータも含める必要があります。これは、全データ量と呼ばれます。
2.3. ニュース記事情報データテーブル
item_df.head().append(item_df.tail())
item_df.shape # 364047篇文章
- ニュースワード数
#新闻字数统计
item_df['words_count'].value_counts()
- ニュースカテゴリ
#新闻类别
item_df['category_id'].nunique()#461
item_df['category_id'].hist()
2.4. ベクトル表現を埋め込むニュース記事
item_emb_df.head()
item_emb_df.shape
3. データ分析
トレーニング セットとテスト セットのユーザー ログをマージする
user_click_merge = trn_click.append(tst_click)
3.1. ユーザーがニュースを繰り返しクリックする回数
#reset_index()重置索引。
user_click_count = user_click_merge.groupby(['user_id', 'click_article_id'])['click_timestamp'].agg({'count'}).reset_index()
user_click_count[:10]
ユーザーID | click_article_id | カウント | |
---|---|---|---|
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_click_count['count'].unique()
#array([ 1, 2, 4, 3, 6, 5, 10, 7, 13], dtype=int64)
ユーザーID | click_article_id | カウント | |
---|---|---|---|
311242 | 86295 | 74254 | 10 |
311243 | 86295 | 76268 | 10 |
393761 | 103237 | 205948 | 10 |
393763 | 103237 | 235689 | 10 |
576902 | 134850 | 69463 | 13 |
#用户重复点击新闻次数
user_click_count.loc[:,'count'].value_counts() #取count列所有行,统计不同的count出现的次数
#可以看出:有1605541(约占99.2%)的用户未重复阅读过文章,仅有极少数用户重复点击过某篇文章。 这个也可以单独制作成特征
1 1605541 2 11621 3 422 4 77 5 26 6 12 10 4 7 3 13 1 名前: カウント、dtype: int64
3.2. ニュースクリック数の異なるユーザー分布
3.2.1. アクティブなユーザー
クリック数上位 50 人のユーザーは全員、クリック数が 100 回を超えています。クリック数が100回以上のユーザーをアクティブユーザーと定義できます.
これは単純な処理のアイデアです.ユーザーのアクティビティを判断するには、クリック時間を組み合わせた方がより包括的です.後で、
数に基づいて決定します.クリック数とクリック時間。ユーザー アクティビティを判断します。
#用户点击次数分析
user_click_item_count = sorted(user_click_merge.groupby('user_id')['click_article_id'].count(), reverse=True)
plt.plot(user_click_item_count)
plt.xlabel('user_id')
plt.ylabel('click_article_id_counts')
#点击次数在前50的用户
plt.plot(user_click_item_count[:50])
plt.xlabel('before_50_user_id')
plt.ylabel('click_article_id_counts')
3.2.2. 非アクティブなユーザー
クリック数が 2 回以下のユーザーが多く、これらのユーザーは非アクティブなユーザーと見なすことができます。
#点击次数排名在[25000:50000]之间
plt.plot(user_click_item_count[25000:50000])
3.3. ニュースのクリック数
3.3.1 ホットニュース
'''クリック数が最も多いトップ 20 のニュース記事で、クリック数が 2500 を超えています。
アイデア: これらのニュースは、ホット ニュースとして定義することができます, これもそれに対処する簡単な方法です.
後で、クリック数と時間に応じて記事の人気度を分割します. '''
item_click_count = sorted(user_click_merge.groupby('click_article_id')['user_id'].count(), reverse=True)
plt.plot(item_click_count)
plt.xlabel('article_id')
plt.ylabel('click_count')
plt.plot(item_click_count[:100])
plt.xlabel('article_id')
plt.ylabel('click_count')
#点击率排名前100的新闻点击量都超过1000次
plt.plot(item_click_count[:20])
plt.xlabel('article_id')
plt.ylabel('click_count')
3.3.2. 人気のないニュース
多くのニュースは、1 回か 2 回クリックされるだけです。アイデア: これらのニュースは人気のないニュースであると定義できます
plt.plot(item_click_count[3500:])
3.4. ニュース共起頻度:2つのニュース記事が連続して出現する回数
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()
共起回数の平均は 3.18 回、最高は 2202 回です。2 つのニュース記事が連続して出現する確率が高く、ニュース ユーザーが読むニュースの相関性が高いことがわかります。
x = union_item['click_article_id']
y = union_item['count']
plt.scatter(x, y)
約 75,000 ペアが少なくとも 1 回は共起します
plt.plot(union_item['count'].values[40000:])
3.5. ニュース記事情報
3.5.1. 各種ニュースの出現時間
50 種類未満のニュースの発生回数が多い
#不同类型的新闻出现的次数
plt.plot(user_click_merge['category_id'].value_counts().values)
plt.xlabel('article_id')
plt.ylabel('appear_counts')
出現回数の少ないニュース
#出现次数比较少的新闻类型, 有些新闻类型,基本上就出现过几次
plt.plot(user_click_merge['category_id'].value_counts().values[150:])
plt.xlabel('article_id')
plt.ylabel('appear_counts')
3.5.2. ニュースワード数
#新闻字数的描述性统计
user_click_merge['words_count'].describe()
plt.plot(user_click_merge['words_count'].values)
3.5.3. ユーザーがクリックするニュースの種類の好み
この機能を使用して、ユーザーの関心が広範囲に及ぶかどうかを測定できます。
#用户偏好的新闻广泛程度
plt.plot(sorted(user_click_merge.groupby('user_id')['category_id'].nunique(), reverse=True))
plt.xlabel('user_id')
plt.ylabel('category_id')
図からわかるように、幅広い嗜好タイプを持つユーザーは少なく、ほとんどのユーザーは嗜好タイプが少なく、20 種類未満であることがわかります。
3.5.4. ユーザーが閲覧した記事の長さの分布
さまざまなユーザーがクリックしたニュースの平均単語数をカウントすることで、ユーザーが長い記事と短い記事のどちらに興味を持っているかを反映できます。
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count'].mean(),reverse = True))
plt.xlabel('user_id')
plt.ylabel('avg_words_count')
少数の人々が読む記事の平均単語数は非常に多く、少数の人々が読む平均単語数は非常に少ない.
ほとんどの人は、200 ~ 400 語のニュースを読むことを好みます。
plt.plot(sorted(user_click_merge.groupby('user_id')['words_count'].mean(),reverse = True)[1000:45000])
ほとんどの人の範囲では、単語数が 220 ~ 250 語のニュースを読むことを好みます。
3.6. ニュースをクリックするユーザーの時間分析
#为了更好的可视化,这里把时间进行归一化操作
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')
3.6.1. クリック時間差の平均値
def mean_diff_time_func(df, col):
df = pd.DataFrame(df, columns={col})
df['time_shift1'] = df[col].shift(1).fillna(0)#shift(1)是把数据向下移动1位
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))
3.6.2. 前後にクリックされた記事の作成時間差の平均値
# 前后点击的文章的创建时间差的平均值
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))
上の図から、記事をクリックするユーザー間の時間差が異なることがわかります. ユーザーが記事を連続してクリックすると、記事の作成時間も異なります.
4. ユーザー獲得前後の記事の類似リストをチェック
4.1. トレーニングニュースの単語ベクトル
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_timestamp排序
click_df = click_df.sort_values('click_timestamp')
# 将click_article_id转换成字符串才可以进行训练
click_df['click_article_id'] = click_df['click_article_id'].astype(str)
# 将click_article_id转换成句子的形式
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, vector_size=16, sg=1, window=5, seed=2020, workers=24, min_count=1, epochs=10)
# 保存成字典的形式
item_w2v_emb_dict = {k: w2v.wv[k] for k in click_df['click_article_id']}
return item_w2v_emb_dict
item_w2v_emb_dict = trian_item_word2vec(user_click_merge)
4.2. ユーザーが前後に閲覧した記事の類似性をチェックする
# 随机选择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)]# .isin()筛选行
sub_user_info.head()
4.3. ユーザー獲得前後の記事の類似リストをチェックする
# 得到用户前,后查看文章的相似度列表
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
4.4. ユーザー可視化前後の記事の類似度一覧を表示する
for _, user_df in sub_user_info.groupby('user_id'):
item_sim_list = get_item_sim_list(user_df)
plt.plot(item_sim_list)