kaggleチャレンジ13日目 アニメのデータセットとクラスタリングで推薦システムを作る

シェアする

  • このエントリーをはてなブックマークに追加
スポンサーリンク

はじめに

 今回はKaggleに公開されているアニメのデータセットを使っていきたいとおもいます。今回は、クラスタリングを使って推薦システムを作っていきたいと思います。クラスタリングを使って推薦システムを作ること自体は興味があったんですが、なかなかやる機会がなかったので今回次のカーネルを参考にして勉強していきたいと思います。

今回のデータセットのリンク

Anime Recommendations Database

今回の参考カーネル

User Clustering for anime recommendation

合わせて読みたい記事:

データセットの説明

データセットの内訳

  • 総アニメ数:12,294
  • ユーザー数:73,516

Anime.csv

  • anime_id:アニメの固有ID
  • name:アニメの名前
  • genre:アニメのジャンル
  • type:アニメの形式(例:movie, TV, OVA)
  • episodes:何話あるか(映画の場合1).
  • rating:平均レート(10段階評価)
  • members:このアニメの「グループ」に含まれるコミュニティメンバーの数

Rating.csv

  • user_id:ランダムに生成されたユーザーID
  • anime_id:このユーザーが評価したアニメID
  • rating:このユーザーが割り当てた10段階の評価(ユーザーがそのアニメを見たが、評価をしなかった場合は-1)

Anime Recommendations Database

今回参考にしたカーネルでは次のものを扱っています。

  • Data wrangling
  • K mean clustering
  • Characteristic of each cluster

ソースコード

まず、GoogleColaboratory上でデータを扱えるようにします。データセットリンクよりダウンロードしてきたzipファイルを、GoogleColaboratory上の「表示」から「目次」を選び、「ファイル」タブを選択します。そうしたら、「アップロード」という表記が見えると思うので、そこからzipファイルを選択し、アップロードしてください。

データセットの解凍

In[1]:

!unzip anime-recommendations-database.zip

ライブラリの準備

In[2]:

import numpy as np  
import pandas as pd  
import matplotlib.pyplot as plt 
from mpl_toolkits.mplot3d import Axes3D 
%matplotlib inline 
plt.rcParams['figure.figsize'] = (6, 4) 
plt.style.use('ggplot') 
%config InlineBackend.figure_formats = {'png', 'retina'}

データ読み込み

In[3]:

anime.head()
Out[3]:
anime_id name genre type episodes rating members
0 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630
1 5114 Fullmetal Alchemist: Brotherhood Action, Adventure, Drama, Fantasy, Magic, Mili… TV 64 9.26 793665
2 28977 Gintama° Action, Comedy, Historical, Parody, Samurai, S… TV 51 9.25 114262
3 9253 Steins;Gate Sci-Fi, Thriller TV 24 9.17 673572
4 9969 Gintama' Action, Comedy, Historical, Parody, Samurai, S… TV 51 9.16 151266

「君の名は」が1位で人気がすさまじいですね。ちなみに私は見ていないのでわかりません。

In[4]:

print(anime.shape)
Out[4]:
(12294, 7)

ユーザーのレーティング情報の方も読み込んで出力させてみましょう。

In[5]:

user = pd.read_csv('rating.csv')
In [6]:
user.head(10)
Out[6]:
user_id anime_id rating
0 1 20 -1
1 1 24 -1
2 1 79 -1
3 1 226 -1
4 1 241 -1
5 1 355 -1
6 1 356 -1
7 1 442 -1
8 1 487 -1
9 1 846 -1

In[7]:

print(user.shape)
Out[7]:
(7813737, 3)

好きなものの定義

ユーザーが多いため、それぞれアニメを評価するための基準が違います。そのため、各ユーザーの平均評価を求めることにしました。

ユーザー1の平均評価はマイナスでした。

In[8]:

user[user['user_id']==1].rating.mean()
Out[8]:
-0.7124183006535948
ユーザー2の平均評価は2.6と10段階評価なので低いですね。
In [9]:
user[user['user_id']==2].rating.mean()
Out[9]:
2.6666666666666665
ユーザー5は平均評価が4.26と10段階評価の半分の5に近い値となっています。
In [10]:
user[user['user_id']==5].rating.mean()
Out[10]:
4.263383297644539

ユーザーあたりの平均評価を計算する

MRPU = user.groupby(['user_id']).mean().reset_index()
MRPU['mean_rating'] = MRPU['rating']
MRPU.drop(['anime_id','rating'],axis=1, inplace=True)
In [12]:
MRPU.head(10)
Out[12]:
user_id mean_rating
0 1 -0.712418
1 2 2.666667
2 3 7.382979
3 4 -1.000000
4 5 4.263383
5 6 -1.000000
6 7 7.387755
7 8 8.333333
8 9 8.000000
9 10 2.875000

データフレームuserにMRPUをkeyをuser_idとしてマージします。

In [13]:
user = pd.merge(user,MRPU,on=['user_id','user_id'])
In [14]:
user.head(5)
Out[14]:
user_id anime_id rating mean_rating
0 1 20 -1 -0.712418
1 1 24 -1 -0.712418
2 1 79 -1 -0.712418
3 1 226 -1 -0.712418
4 1 241 -1 -0.712418

これで、データフレームuserにmean_ratingを追加することができました。次に平均評価より低い評価の物を取り除きます。

In [15]:
user = user.drop(user[user.rating < user.mean_rating].index)
In [16]:
user[user['user_id']== 1].head(10)
Out[16]:
user_id anime_id rating mean_rating
47 1 8074 10 -0.712418
81 1 11617 10 -0.712418
83 1 11757 10 -0.712418
101 1 15451 10 -0.712418

ユーザー1の評価が-1の物が取り除かれているのが確認できます。

In [17]:
user[user['user_id']== 2].head(10)
Out[17]:
user_id anime_id rating mean_rating
153 2 11771 10 2.666667

これにより、ユーザー2のお気に入りのアニメが1つであることがわかりました。

In [18]:
user[user['user_id']== 5].head(10)
Out[18]:
user_id anime_id rating mean_rating
302 5 6 8 4.263383
303 5 15 6 4.263383
304 5 17 6 4.263383
305 5 18 6 4.263383
306 5 20 6 4.263383
307 5 22 5 4.263383
310 5 45 7 4.263383
311 5 47 8 4.263383
312 5 57 7 4.263383
314 5 67 6 4.263383

In [19]:
print(user.shape)
Out[19]:
(4262566, 4)
In [20]:
user["user_id"].unique()
Out[20]:
array([    1,     2,     3, ..., 73514, 73515, 73516])
In [21]:
user = user.rename({'rating':'userRating'}, axis='columns')

2つのデータセットを組み合わせる

anime_idをkeyにしてマージします。また、user_idが20,000以下のみにします。

In [22]:
mergedata = pd.merge(anime,user,on=['anime_id','anime_id'])
mergedata= mergedata[mergedata.user_id <= 20000]
mergedata.head(10)
Out[22]:
anime_id name genre type episodes rating members user_id userRating mean_rating
0 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 152 10 7.699301
1 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 244 10 8.729242
2 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 271 10 7.372287
3 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 322 10 8.356322
4 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 398 10 -0.832298
5 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 462 8 7.374593
6 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 490 10 8.062500
7 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 548 10 8.112360
8 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 570 10 8.388889
9 32281 Kimi no Na wa. Drama, Romance, School, Supernatural Movie 1 9.37 200630 598 10 8.680328
animeタイトルの数をそれぞれ調べます。
In [23]:
len(mergedata['anime_id'].unique())
Out[23]:
7852
In [24]:
len(anime['anime_id'].unique())
Out[24]:
12294

ユーザーidで制限をかけた分減っているのがわかりますね。

クロステーブルを作る

pandas.crosstab()関数を使うとクロス集計分析ができます。

In [25]:
user_anime = pd.crosstab(mergedata['user_id'], mergedata['name'])
user_anime.head(10)
Out[25]:
name &quot;Bungaku Shoujo&quot; Kyou no Oyatsu: Hatsukoi &quot;Bungaku Shoujo&quot; Memoire xxxHOLiC Shunmuki
user_id
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
4 0 0 0 0
5 0 0 0 0
6 0 0 0 0
7 0 0 0 0
8 0 0 0 0
9 0 0 0 0
10 0 0 0 0
In [26]:
user_anime.shape
Out[26]:
(20000, 7852)

主成分分析

主成分分析は、元の変数を元の変数セットの線形結合である新しい変数セットに変換します。
目的としては、クラスタリングと可視化のためにデータの次元を減らすことにあります。

In [27]:

from sklearn.decomposition import PCA
pca = PCA(n_components=3)
pca.fit(user_anime)
pca_samples = pca.transform(user_anime)
In [28]:
ps = pd.DataFrame(pca_samples)
ps.head()
Out[28]:
0 1 2
0 -1.579129 -0.500240 0.415759
1 -1.773553 -0.272593 0.116388
2 0.218814 -1.232281 -0.985819
3 0.199435 -0.291005 0.681064
4 3.532125 -0.184796 -0.743320

In [29]:
tocluster = pd.DataFrame(ps[[0,1,2]])
プロットしてみます。
In [30]:
plt.rcParams['figure.figsize'] = (16, 9)
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(tocluster[0], tocluster[2], tocluster[1])

plt.title('Data points in 3D PCA axis', fontsize=20)
plt.show()

kの数を選ぶ

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

scores = []
inertia_list = np.empty(8)

for i in range(2,8):
    kmeans = KMeans(n_clusters=i)
    kmeans.fit(tocluster)
    inertia_list[i] = kmeans.inertia_
    scores.append(silhouette_score(tocluster, kmeans.labels_))
プロットしてみます。
In [32]:
plt.plot(range(0,8),inertia_list,'-o')
plt.xlabel('Number of cluster')
plt.axvline(x=4, color='blue', linestyle='--')
plt.ylabel('Inertia')
plt.show()

In [33]:

plt.plot(range(2,8), scores);
plt.title('Results KMeans')
plt.xlabel('n_clusters');
plt.axvline(x=4, color='blue', linestyle='--')ylabel('Silhouette Score');
plt.show()

k:クラスタの数は4で行きたいと思います。

K mean clustering

どのように動作するか説明をここに入れようとおもいましたが、以下のリンクで動作原理を理解するために可視化していたので、こちらを紹介しておきます。文で表現するより直観的に理解できると思います。

In[34]:

from sklearn.cluster import KMeans
clusterer = KMeans(n_clusters=4,random_state=30).fit(tocluster)
centers = clusterer.cluster_centers_
c_preds = clusterer.predict(tocluster)
print(centers)
Out[34]:
[[-1.08874971 -0.04026583  0.06666421]
 [ 7.61700382 -0.64256845  0.8395559 ]
 [ 1.6784451   2.31533837 -0.02522454]
 [ 1.97875213 -1.12654221 -0.4351472 ]]
プロットします。
In [35]:
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(tocluster[0], tocluster[2], tocluster[1], c = c_preds)
plt.title('Data points in 3D PCA axis', fontsize=20)
plt.show()

In[36]:

fig = plt.figure(figsize=(10,8))
plt.scatter(tocluster[1],tocluster[0],c = c_preds)
for ci,c in enumerate(centers):
    plt.plot(c[1], c[0], 'o', markersize=8, color='red', alpha=1)
plt.xlabel('x_values')
plt.ylabel('y_values')
plt.title('Data points in 2D PCA axis', fontsize=20)
plt.show()

次に、クロステーブルにクラスタカラムを追加しておきます。

In[37]:

user_anime['cluster'] = c_preds
user_anime.head(10)
Out[37]:
name &quot;Bungaku Shoujo&quot; Kyou no Oyatsu: Hatsukoi &quot;Bungaku Shoujo&quot; Memoire xxxHOLiC Shunmuki cluster
user_id
1 0 0 0 0
2 0 0 0 0
3 0 0 0 3
4 0 0 0 0
5 0 0 0 3
6 0 0 0 0
7 0 0 0 3
8 0 0 0 0
9 0 0 0 0
10 0 0 0 0
In [38]:
user_anime.info()
Out[38]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 20000 entries, 1 to 20000
Columns: 7853 entries, &quot;Bungaku Shoujo&quot; Kyou no Oyatsu: Hatsukoi to cluster
dtypes: int32(1), int64(7852)
memory usage: 1.2 GB

各クラスタの特性

In[39]:

c0 = user_anime[user_anime['cluster']==0].drop('cluster',axis=1).mean()
c1 = user_anime[user_anime['cluster']==1].drop('cluster',axis=1).mean()
c2 = user_anime[user_anime['cluster']==2].drop('cluster',axis=1).mean()
c3 = user_anime[user_anime['cluster']==3].drop('cluster',axis=1).mean()

Cluster 0

このクラスターの特徴を説明するトップ15のアニメを出力させてみます。

In[40]:

c0.sort_values(ascending=False)[0:15]
Out[40]:
name
Death Note                            0.320642
Shingeki no Kyojin                    0.230444
Sword Art Online                      0.198824
Fullmetal Alchemist: Brotherhood      0.181616
Code Geass: Hangyaku no Lelouch       0.178820
Sen to Chihiro no Kamikakushi         0.159963
Angel Beats!                          0.157095
Fullmetal Alchemist                   0.155661
Code Geass: Hangyaku no Lelouch R2    0.153510
Naruto                                0.146125
Elfen Lied                            0.144045
Ouran Koukou Host Club                0.133792
Mirai Nikki (TV)                      0.121388
Toradora!                             0.117803
Howl no Ugoku Shiro                   0.114433
dtype: float64

エピソード、ジャンル、メンバー、レーティングといったアニメの情報をそれぞれリストにして返す関数を作ります。

In[41]:

def createAnimeInfoList(animelist):
    episode_list = list()
    genre_list = list()
    member_list = list()
    rating_list= list()
    for x in anime['name']:
        if x in animelist:
            episode_list.append(anime[anime['name']==x].episodes.values.astype(int))
            member_list.append(anime[anime['name']==x].members.values.astype(int))
            rating_list.append(anime[anime['name']==x].rating.values.astype(int))
            for y in anime[anime['name']==x].genre.values:
                 genre_list.append(y)
    return genre_list,episode_list,rating_list,member_list
         
次に、データフレームとカラムを渡して、そのカラムに出てくるキーワードと頻度を返す関数を作ります。
In [42]:
def count_word(df, ref_col, liste):
    keyword_count = dict()
    for s in liste: keyword_count[s] = 0
    for liste_keywords in df[ref_col].str.split(','):        
        if type(liste_keywords) == float and pd.isnull(liste_keywords): continue        
        for s in [s for s in liste_keywords if s in liste]: 
            if pd.notnull(s): keyword_count[s] += 1
    #______________________________________________________________________
    # convert the dictionary in a list to sort the keywords by frequency
    keyword_occurences = []
    for k,v in keyword_count.items():
        keyword_occurences.append([k,v])
    keyword_occurences.sort(key = lambda x:x[1], reverse = True)
    return keyword_occurences, keyword_count
まず、クラスタ0に入っているアニメの情報をリストにします。
In [43]:
animelist = list(c0.index)
data = pd.DataFrame()
data['genre'],data['episode'],data['rating'],data['member'] =  createAnimeInfoList(animelist)
次に、ジャンルからキーワードをとってきます。
In [44]:
set_keywords = set()
for liste_keywords in data['genre'].str.split(',').values:
    if isinstance(liste_keywords, float): continue  # only happen if liste_keywords = NaN
    set_keywords = set_keywords.union(liste_keywords)
このクラスタのお気に入りのジャンル
次に、ワードクラウドで、クラスタ0で出てきたキーワードを可視化します。
!pip install wordcloud
In [45]:
from wordcloud import WordCloud

def makeCloud(Dict,name,color):
    words = dict()
    for s in Dict:
        words[s[0]] = s[1]

        wordcloud = WordCloud(
                      width=1500,
                      height=500, 
                      background_color=color, 
                      max_words=20,
                      max_font_size=500, 
                      normalize_plurals=False)
        wordcloud.generate_from_frequencies(words)


    fig = plt.figure(figsize=(12, 8))
    plt.title(name)
    plt.imshow(wordcloud)
    plt.axis('off')

    plt.show()

In [46]:

c0_animelist = list(c0.sort_values(ascending=False)[0:15].index)
c0_data = pd.DataFrame()
c0_data['genre'],c0_data['episode'],c0_data['rating'],c0_data['member'] =  createAnimeInfoList(c0_animelist)
c0_data.iloc[:,1:4] = c0_data.iloc[:,1:4].astype(int) # change to numeric object to integer
keyword_occurences, dum = count_word(c0_data, 'genre', set_keywords)
makeCloud(keyword_occurences[0:10],"cluster 0","lemonchiffon")

キーワードと頻度が高い順で5件出力させてみます。

In[47]:

keyword_occurences[0:5]

Out[47]:

[['Action', 10],
 [' Drama', 8],
 [' Fantasy', 5],
 [' Supernatural', 5],
 [' Romance', 5]]

このクラスタのユーザが好きなアニメの情報の平均

In[48]:

print('cluster 0\nAVG episode : {0}\nAVG movie rating : {1}\nAVG member : {2}'
      .format(c0_data['episode'].mean(), c0_data['rating'].mean(),c0_data['member'].mean()))
Out[48]:
cluster 0
AVG episode : 38.46666666666667
AVG movie rating : 7.866666666666666
AVG member : 668177.0666666667

Cluster 1

他のクラスタもみていきます。やることはクラスタ0と同じです。

In[49]:

c1.sort_values(ascending=False)[0:15]
Out[49]:
name
Angel Beats!                                           0.836524
No Game No Life                                        0.818851
Toradora!                                              0.815906
Code Geass: Hangyaku no Lelouch                        0.804124
Steins;Gate                                            0.792342
Shingeki no Kyojin                                     0.783505
Code Geass: Hangyaku no Lelouch R2                     0.777614
Sword Art Online                                       0.768778
Bakemonogatari                                         0.756996
Death Note                                             0.755523
Hataraku Maou-sama!                                    0.730486
Fullmetal Alchemist: Brotherhood                       0.730486
Clannad                                                0.724595
Yahari Ore no Seishun Love Comedy wa Machigatteiru.    0.720177
Chuunibyou demo Koi ga Shitai!                         0.718704
dtype: float64
In [50]:
c1_animelist = list(c1.sort_values(ascending=False)[0:15].index)
c1_data = pd.DataFrame()
c1_data['genre'],c1_data['episode'],c1_data['rating'],c1_data['member'] =  createAnimeInfoList(c1_animelist)
c1_data.iloc[:,1:4] = c1_data.iloc[:,1:4].astype(int)
keyword_occurences, dum = count_word(c1_data, 'genre', set_keywords)
makeCloud(keyword_occurences[0:10],"cluster 1","white")
このクラスタでは、ロマンスやドラマといったキーワードが目立ちますね。
In [51]:
keyword_occurences[0:5]
Out[51]:
[[' Drama', 7], [' Romance', 7], [' School', 6], ['Action', 6], ['Comedy', 5]]
In [52]:
print('cluster 1\nAVG episode : {0}\nAVG movie rating : {1}\nAVG member : {2}'
      .format(c1_data['episode'].mean(), c1_data['rating'].mean(),c1_data['member'].mean())
Out[52]:
cluster 1
AVG episode : 23.4
AVG movie rating : 8.0
AVG member : 647913.0666666667

クラスタ0と比べて、エピソードの平均が短く、平均評価が高いですね。アクションものはエピソードが長いものが多そうな気がします。

Cluster 2

In[53]:

c2.sort_values(ascending=False)[0:15]
Out[53]:
name
Death Note                            0.680644
Code Geass: Hangyaku no Lelouch       0.668454
Code Geass: Hangyaku no Lelouch R2    0.609459
Fullmetal Alchemist                   0.600195
Sen to Chihiro no Kamikakushi         0.583618
Suzumiya Haruhi no Yuuutsu            0.551438
Neon Genesis Evangelion               0.537786
Tengen Toppa Gurren Lagann            0.529498
Cowboy Bebop                          0.525110
Fullmetal Alchemist: Brotherhood      0.509995
Mononoke Hime                         0.499756
Toradora!                             0.489030
Elfen Lied                            0.484154
Howl no Ugoku Shiro                   0.479766
Samurai Champloo                      0.477328
dtype: float64
In [54]:
c2_animelist = list(c2.sort_values(ascending=False)[0:15].index)
c2_data = pd.DataFrame()
c2_data['genre'],c2_data['episode'],c2_data['rating'],c2_data['member'] =  createAnimeInfoList(c2_animelist)
c2_data.iloc[:,1:4] = c2_data.iloc[:,1:4].astype(int)
keyword_occurences, dum = count_word(c2_data, 'genre', set_keywords)
makeCloud(keyword_occurences[0:10],"cluster 2","black")
アクションやドラマといったキーワードが目立つようですね。クラスタ0と似たキーワードがおおいですね。
In [55]:
keyword_occurences[0:5]
Out[55]:
[['Action', 10],
 [' Drama', 8],
 [' Adventure', 6],
 [' Sci-Fi', 6],
 [' Military', 4]]
In [56]:
c2_data['episode'].mean()
Out[56]:
24.133333333333333
In [57]:
print('cluster 2\nAVG episode : {0}\nAVG movie rating : {1}\nAVG member : {2}'
      .format(c2_data['episode'].mean(), c2_data['rating'].mean(),c2_data['member'].mean()))
cluster 2
AVG episode : 24.133333333333333
AVG movie rating : 8.0
AVG member : 561513.7333333333

平均評価は今まで出てきたクラスタはどれもあまり変わらないですね。先ほど、アクションだとエピソードが長い?と予想を立てましたが、そうではないみたいです。クラスタ0との違いはロマンス、ファンタジーといったものなので、アクションとファンタジー、ロマンスが重なったらエピソードが長い傾向にあるのかもしれません。

Cluster 3

In[58]:

c3.sort_values(ascending=False)[0:15]
Out[58]:
name
Shingeki no Kyojin                                          0.726753
No Game No Life                                             0.644598
Sword Art Online                                            0.613602
Angel Beats!                                                0.598255
Death Note                                                  0.591032
Steins;Gate                                                 0.551610
Fullmetal Alchemist: Brotherhood                            0.539874
Toradora!                                                   0.517605
Mirai Nikki (TV)                                            0.513993
Code Geass: Hangyaku no Lelouch                             0.506169
Code Geass: Hangyaku no Lelouch R2                          0.478182
Noragami                                                    0.464941
One Punch Man                                               0.464039
Tokyo Ghoul                                                 0.462534
Ano Hi Mita Hana no Namae wo Bokutachi wa Mada Shiranai.    0.429732
dtype: float64
In [59]:
c3_animelist = list(c3.sort_values(ascending=False)[0:15].index)
c3_data = pd.DataFrame()
c3_data['genre'],c3_data['episode'],c3_data['rating'],c3_data['member'] =  createAnimeInfoList(c3_animelist)
c3_data.iloc[:,1:4] = c3_data.iloc[:,1:4].astype(int)
keyword_occurences, dum = count_word(c3_data, 'genre', set_keywords)
makeCloud(keyword_occurences[0:10],"cluster 3","snow")

keyword_occurences[0:5]
Out[60]:
[['Action', 10],
 [' Supernatural', 8],
 [' Drama', 5],
 [' Fantasy', 4],
 [' Super Power', 4]]
In [61]:
print('cluster 3\nAVG episode : {0}\nAVG movie rating : {1}\nAVG member : {2}'
      .format(c3_data['episode'].mean(), c3_data['rating'].mean(),c3_data['member'].mean()))
Out[61]:
cluster 3
AVG episode : 23.2
AVG movie rating : 8.066666666666666
AVG member : 687956.2

こちらも平均エピソードはあまり変わらず、ということは、アクションとロマンスがエピソード数に関わってくるのでしょうか?


長くなってしまったので、これらを用いた推薦システムは次回書きたいと思います。

最後まで読んでいただきありがとうございました。よろしければこの記事をシェアしていただけると励みになります。よろしくお願いします。

スポンサーリンク
レクタングル広告(大)
レクタングル広告(大)

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

スポンサーリンク
レクタングル広告(大)