-
Notifications
You must be signed in to change notification settings - Fork 0
/
anime-recommendation-system-content-based-filtering.py
288 lines (191 loc) · 10.5 KB
/
anime-recommendation-system-content-based-filtering.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# -*- coding: utf-8 -*-
"""Proyek Akhir_Irbah Labibah Nur Saidah_Anime_Recommendation.ipynb
Automatically generated by Colaboratory.
Original file is located at
https://colab.research.google.com/drive/1cf6YKMtm7wX6FbdJtI82-ptrTrWsiTR0
# Data Collection
Nama : Irbah Labibah Nur Saidah
Dataset ini diambil dari : https://www.kaggle.com/datasets/CooperUnion/anime-recommendations-database
"""
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
!gdown --id "1-9Y7fxzaY53xjetyh1OEAz5fVAjYJeUB"
!gdown --id "1Gpi4HO8oVKLqabMFfn7e3boOm1agl149"
"""Terdapat 2 file, yaitu anime dan rating anime."""
anime = pd.read_csv("/content/anime.csv")
anime_rating = pd.read_csv("/content/rating.csv")
"""# Data Understanding"""
anime
"""Ini adalah dataset dari setiap judul anime beserta dengan genre, tipe, epidoses, rata-rata rating, dan jumlah anggota komunitas anime tersebut."""
anime.shape
"""Data ini memiliki 12294 sample dan 7 fitur. Beberapa judul anime memiliki symbol."""
anime_rating
"""Ini adalah dataset dari pemberian rating anime dari setiap user."""
anime_rating.shape
"""Data ini memiliki 7813737 sample dan 3 fitur."""
print('Jumlah judul anime: ', len(anime.anime_id.unique()))
print('Jumlah genre anime: ', len(anime.genre.unique()))
print('Jumlah sample rating anime: ', len(anime_rating.user_id.unique()))
anime.info()
anime_rating.info()
anime.isna().sum()
anime = anime.dropna()
"""Menghapus missing value pada dataset anime."""
anime.shape
anime_rating.isna().sum()
"""Tidak ada missing value pada data rating anime"""
dup_anime = anime[anime.duplicated()].shape[0]
print(f"Terdapat {dup_anime} duplikat dari {anime.shape[0]} sample pada anime dataset.")
dup_rating = anime_rating[anime_rating.duplicated()].shape[0]
print(f"Terdapat {dup_rating} duplikat dari {anime_rating.shape[0]} sample pada rating anime dataset.")
# Menghapus duplikat sample dari sample rating anime dataset
anime_rating.drop_duplicates(keep='first',inplace=True)
print(f"Sample rating anime dataset sekarang adalah {anime_rating.shape[0]}.")
# Menghapus symbol pada judul anime
import re
def text_cleaning(text):
text = re.sub(r'"', '', text)
text = re.sub(r'.hack//', '', text)
text = re.sub(r''', '', text)
text = re.sub(r'A's', '', text)
text = re.sub(r'I'', 'I\'', text)
text = re.sub(r'&', 'and', text)
return text
anime['name'] = anime['name'].apply(text_cleaning)
"""## Univariate Exploratory Data Analysis"""
anime.describe().apply(lambda s: s.apply('{0:.2f}'.format))
"""Dataset anime memiliki rating anime terendah 1.67 dan rating tertinggi 10 dengan rata-rata 6.48. Dataset ini juga memiliki jumlah anggota komunitas anime terendah 12 dan terbanyak 1013917 dengan rata-rata 18348. Perbedaan nilai min dan max dari jumlah anggota komunitas anime cukup jauh dan hal ini wajar dikarenakan beberapa anime memang sangat populer dan beberapa tidak."""
anime_rating.describe().apply(lambda s: s.apply('{0:.2f}'.format))
"""Dataset rating anime memiliki rating terendah yang diberikan user pada suatu anime adalah -1 dan rating tertinggi adalah 10. Rating -1 menandakan bahwa user menonton anime, namun tidak memberikan rating."""
anime_rating = anime_rating[~(anime_rating.rating == -1)]
anime_rating.describe().apply(lambda s: s.apply('{0:.2f}'.format))
"""Sample user yang tidak memberikan rating tidak akan digunakan sehingga dihapus."""
anime.groupby('genre')['genre'].agg('count')
# Anime Categories Distribution
from pandas.core.tools.datetimes import overload
ona = anime.loc[anime['type'] == 'ONA'].count()[0]
tv = anime.loc[anime['type'] == 'TV'].count()[0]
movie = anime.loc[anime['type'] == 'Movie'].count()[0]
music = anime.loc[anime['type'] == 'Music'].count()[0]
special = anime.loc[anime['type'] == 'Special'].count()[0]
ova = anime.loc[anime['type'] == 'OVA'].count()[0]
labels = ['ONA', 'TV', 'Movie', 'Music', 'Special', 'OVA']
colors = ['#81F4E1', '#56CBF9', '#F5D491', '#BEB7A4', '#B4E1FF', '#F06C9B']
plt.figure(figsize = (10,7))
plt.title('Anime Categories Distribution')
plt.pie([ona, tv, movie, music, special, ova],
labels = labels,
colors = colors,
autopct = '%.2f %%'
)
plt.show()
# Anime's Average Ratings Distribution
plt.hist(anime.rating, color='#B4E1FF', edgecolor='black')
plt.ylabel('Total')
plt.xlabel('Avg Rating')
plt.title("Anime's Average Ratings Distribution")
plt.show()
# User Anime Ratings Distribution
plt.hist(anime_rating.rating, color='#B4E1FF', edgecolor='black')
plt.ylabel('Total')
plt.xlabel('Rating')
plt.title("User Anime Ratings Distribution")
plt.show()
"""## Multvarite Exploratory Data Analysis"""
# Top 10 Anime Community
anime.sort_values(by='members', ascending=False).head(10)
"""Menampilkan daftar anime dengan jumlah anggota community terbanyak. Misalnya, anime Death Note memiliki jumlah anggota community terbanyak, yaitu sebesar 1013917."""
# Top 10 Anime Community Plot
plt.figure(figsize = (20,15))
top10_anime = anime[['name', 'members']].sort_values(by = 'members',ascending = False).head(10)
colors = ['#87255B', '#56CBF9', '#F5D491', '#BEB7A4', '#B4E1FF', '#F06C9B', '#D3C4D1', '#81F4E1', '#C2AFF0', '#C57B57']
labels = top10_anime[['name']].values.flatten()
values = top10_anime[['members']].values.flatten()
plt.barh(labels, values, color = colors, edgecolor='black')
plt.grid(color='#95a5a6', linestyle='--', linewidth=2, axis='x', alpha=0.7)
plt.xticks(fontsize = 15)
plt.yticks(fontsize = 15)
plt.title("Top 10 Anime Community", fontdict = {'fontsize' : 20})
plt.show()
plt.show()
# Top 10 Anime Based on Avg Ratings
anime.sort_values(by='rating', ascending=False).head(10)
"""Menampilkan anime dengan rata-rata rating tertinggi. Misalnya, anime Taka no Tsume 8: Yoshida-kun no X-Files memiliki rata-rata rating tertinggi, yaitu 10."""
# Top 10 Anime Based on Avg Ratings Plot
plt.figure(figsize = (20,15))
top10_anime = anime[['name', 'rating']].sort_values(by = 'rating',ascending = False).head(10)
colors = ['#87255B', '#56CBF9', '#F5D491', '#BEB7A4', '#B4E1FF', '#F06C9B', '#D3C4D1', '#81F4E1', '#C2AFF0', '#C57B57']
bins = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
labels = top10_anime[['name']].values.flatten()
values = top10_anime[['rating']].values.flatten()
plt.barh(labels, values, color = colors, edgecolor='black')
plt.grid(color='#95a5a6', linestyle='--', linewidth=2, axis='x', alpha=0.7)
plt.xticks(bins, fontsize = 15)
plt.yticks(fontsize = 15)
plt.title("Top 10 Anime Based on Avg Ratings", fontdict = {'fontsize' : 20})
plt.show()
plt.show()
anime_rating.head()
# Count anime rating contribution
anime_rating_contribution = anime_rating.groupby('anime_id').count()
anime_rating_contribution.head(3)
"""Anime dengan ID 1 mendapat rating sebanyak 13449 kali, anime dengan ID 5 mendapat rating sebanyak 5790 kali, dan anime dengan ID 6 mendapat rating sebanyak 9385 kali."""
# Menggabungkan table anime_rating_contribution dengan table anime untuk mendapat nama anime
# Top 10 anime rating contribution
anime2 = anime.drop(['rating'], axis = 'columns')
name_anime_rating_contribution = pd.merge(anime_rating_contribution, anime2, on = 'anime_id', how = 'left')
name_anime_rating_contribution.sort_values(by='rating', ascending=False).head(10)
# Top 10 Anime Rating Contribution Plot
plt.figure(figsize = (20,15))
top10_anime = name_anime_rating_contribution[['name', 'rating']].sort_values(by = 'rating',ascending = False).head(10)
colors = ['#87255B', '#56CBF9', '#F5D491', '#BEB7A4', '#B4E1FF', '#F06C9B', '#D3C4D1', '#81F4E1', '#C2AFF0', '#C57B57']
labels = top10_anime[['name']].values.flatten()
values = top10_anime[['rating']].values.flatten()
plt.barh(labels, values, color = colors, edgecolor='black')
plt.grid(color='#95a5a6', linestyle='--', linewidth=2, axis='x', alpha=0.7)
plt.xticks(fontsize = 15)
plt.yticks(fontsize = 15)
plt.title("Top 10 Anime Rating Contribution", fontdict = {'fontsize' : 20})
plt.show()
plt.show()
"""# Content Based Filtering Model & Result"""
# TF-IDF
from sklearn.feature_extraction.text import TfidfVectorizer
tf = TfidfVectorizer()
tf.fit(anime['genre'])
tf.get_feature_names()
tfidf_matrix = tf.fit_transform(anime['genre'])
tfidf_matrix.shape
tfidf_matrix.todense()
pd.DataFrame(
tfidf_matrix.todense(),
columns=tf.get_feature_names(),
index=anime.name
).sample(22, axis=1).sample(10, axis=0)
"""Dataframe ini menunjukkan matriks tf-idf untuk beberapa anime dan genre. Semakin tinggi nilai matriks menunjukkan hubungan antara anime dengan genre tersebut. Misalkan anime Asa da yo! Kaishain merupakan genre comedy terlihat dari nilai matriks 1.0 yang didapat anime tersebut pada genre comedy"""
from sklearn.metrics.pairwise import cosine_similarity
# Menghitung cosine similarity pada matrix tf-idf
cosine_sim = cosine_similarity(tfidf_matrix)
cosine_sim
# Membuat dataframe dari variabel cosine_sim dengan baris dan kolom berupa nama resto
cosine_sim_df = pd.DataFrame(cosine_sim, index=anime['name'], columns=anime['name'])
print('Shape:', cosine_sim_df.shape)
# Melihat similarity matrix pada setiap resto
cosine_sim_df.sample(5, axis=1).sample(10, axis=0)
"""Dataframe ini menunjukkan nilai kesamaan antar anime. Semakin tinggi nilai cosline similarity, kedua anime akan semakin memiliki kesamaan.Misalnya, anime Anata dake Konbanwa memiliki kesamaan dengan anime Kisaku Spirit terlihat dari nilai cosine similarity 1.0 antar kedua anime tersebut."""
def anime_recommendations(name, similarity_data=cosine_sim_df, items=anime[['name', 'genre']], k=5):
# Mengambil data dengan menggunakan argpartition untuk melakukan partisi secara tidak langsung sepanjang sumbu yang diberikan
# Dataframe diubah menjadi numpy
# Range(start, stop, step)
index = similarity_data.loc[:,name].to_numpy().argpartition(
range(-1, -k, -1))
# Mengambil data dengan similarity terbesar dari index yang ada
closest = similarity_data.columns[index[-1:-(k+2):-1]]
# Drop name agar nama resto yang dicari tidak muncul dalam daftar rekomendasi
closest = closest.drop(name, errors='ignore')
return pd.DataFrame(closest).merge(items).head(k)
"""Fungsi anime_recommendations dibuat untuk menemukan rekomendasi anime menggunakan similarity yang telah didefinisikan sebelumnya. Fungsi ini bekerja dengan cara mengambil anime dengan similarity terbesar dari index yang ada."""
anime[anime.name.eq('Naruto')]
anime_recommendations('Naruto')
"""Sistem telah berhasil merekomendasikan top 5 persen anime yang mirip dengan naruto, yaitu beberapa film dan seri dari naruto itu sendiri. Jadi, jika pengguna menyunkai naruto, maka sistem dapat merekomendasikan seri atau movie naruto lainnya."""