Proyek ini bertujuan untuk membangun sistem rekomendasi film yang mampu memberikan rekomendasi film berdasarkan rating yang telah diberikan pada film tertentu oleh pengguna. Sistem rekomendasi ini menggunakan data yang diambil dari situs Kaggle, yang mencakup informasi tentang rating yang diberikan oleh pengguna Netflix untuk berbagai film. Dengan menggunakan berbagai teknik pemodelan seperti cosine similarity dan UV decomposition, proyek ini akan mengevaluasi kinerja model dalam memprediksi rating film yang belum dilihat oleh pengguna. Hasil dari model ini diharapkan dapat memberikan rekomendasi film yang lebih personal dan relevan bagi pengguna.
Link data: Netflix Movie Rating Dataset
library(tidyverse) # functions for data manipulation
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(recommenderlab) # function for recommendation systems
## Loading required package: Matrix
##
## Attaching package: 'Matrix'
##
## The following objects are masked from 'package:tidyr':
##
## expand, pack, unpack
##
## Loading required package: arules
##
## Attaching package: 'arules'
##
## The following object is masked from 'package:dplyr':
##
## recode
##
## The following objects are masked from 'package:base':
##
## abbreviate, write
##
## Loading required package: proxy
##
## Attaching package: 'proxy'
##
## The following object is masked from 'package:Matrix':
##
## as.matrix
##
## The following objects are masked from 'package:stats':
##
## as.dist, dist
##
## The following object is masked from 'package:base':
##
## as.matrix
##
## Registered S3 methods overwritten by 'registry':
## method from
## print.registry_field proxy
## print.registry_entry proxy
data <- read.csv('/home/faggluyy/Documents/Documents/Data/Netflix_Dataset_Rating.csv')
detail_film <- read.csv('/home/faggluyy/Documents/GitHub/R-Projects/Recommender System/Netflix_Dataset_Movie.csv')
head(data)
## User_ID Rating Movie_ID
## 1 712664 5 3
## 2 1331154 4 3
## 3 2632461 3 3
## 4 44937 5 3
## 5 656399 4 3
## 6 439011 1 3
Karena data terlalu banyak dan tidak bisa digunakan seluruhnya maka dilakukan proses pemangkasan data. Dimana pemangkasan dilakukan dengan cara mengambil data dengan 50 film tertinggi popularitasnya berdasarkan data yang ada.
# Hitung frekuensi setiap movie_id
movie_counts <- data %>%
group_by(Movie_ID) %>%
summarise(count = n()) %>%
arrange(desc(count))
# Dapatkan 50 movie ID teratas
top_50_movies <- movie_counts %>%
top_n(50, wt = count)
# Ambil hanya movie_id dari top 50 movies
top_50_movie_ids <- top_50_movies$Movie_ID
# Filter dataset untuk hanya menyertakan movie_id yang ada di top 50
filtered_data <- data %>%
filter(Movie_ID %in% top_50_movie_ids)
head(filtered_data)
## User_ID Rating Movie_ID
## 1 2473170 5 30
## 2 900816 3 30
## 3 1990901 4 30
## 4 1402412 4 30
## 5 1601783 3 30
## 6 306466 3 30
Data yang sudah dipangkas kemudian diubah menjadi matriks dimana barisnya berisi ID pengguna dan kolomnya berisi ID Film
# Membentuk matrix dari data
ratings_matrix <- filtered_data %>%
pivot_wider(names_from = Movie_ID, values_from = Rating)
ratings_matrix <- as.data.frame(ratings_matrix)
# Ubah User ID menjadi rownames
rownames(ratings_matrix) <- ratings_matrix$User_ID
ratings_matrix <- as.matrix(ratings_matrix[, -1])
# Menampilkan matriks rating
head(ratings_matrix)
## 30 175 191 313 457 571 607 758 798 886 985 1145 1180 1202 1220 1307
## 2473170 5 NA NA NA NA NA NA NA 3 4 NA 3 NA NA 4 3
## 900816 3 5 2 2 5 1 5 NA NA 4 5 2 5 5 4 4
## 1990901 4 2 4 NA 5 5 3 NA 4 5 1 NA 3 3 NA NA
## 1402412 4 NA NA NA 3 5 NA 2 4 4 3 2 NA NA NA 2
## 1601783 3 NA 4 NA 4 2 4 3 4 3 4 3 NA 4 3 3
## 306466 3 4 4 4 4 4 3 4 4 NA 4 NA 3 3 3 2
## 1428 1470 1542 1798 1865 1905 1962 2095 2112 2122 2152 2372 2391 2452
## 2473170 NA NA 5 4 NA 4 4 NA NA NA 4 NA 4 2
## 900816 5 NA 4 4 5 5 4 NA 4 NA 2 3 4 3
## 1990901 NA 5 2 4 5 4 NA 2 NA 5 3 NA NA 4
## 1402412 NA 3 5 4 NA 4 4 2 1 4 NA NA 4 NA
## 1601783 3 3 4 4 3 3 3 NA 3 NA NA 4 NA 4
## 306466 2 4 2 3 5 4 2 4 4 4 3 4 3 4
## 2782 2862 2913 3106 3151 3282 3427 3624 3756 3825 3860 3925 3938 3962
## 2473170 NA NA NA 4 NA 3 NA NA NA 5 3 NA NA NA
## 900816 5 4 NA 4 2 3 3 4 NA 3 5 3 5 5
## 1990901 5 5 4 3 4 5 NA NA 4 NA 3 NA NA 5
## 1402412 NA 1 4 2 2 NA 4 4 4 2 4 NA NA NA
## 1601783 4 4 NA 4 NA NA 3 3 2 2 2 3 4 5
## 306466 4 4 4 3 4 4 1 4 3 2 3 3 4 4
## 4043 4123 4306 4356 4432 4472
## 2473170 NA NA NA 2 3 NA
## 900816 4 4 4 2 4 NA
## 1990901 3 NA 4 3 3 4
## 1402412 NA 4 5 NA 3 5
## 1601783 3 NA 5 3 3 3
## 306466 3 NA 5 4 3 3
Setelah itu matriks dikonvert lagi kedalam bentuk recommenderlab realRatingMatrix sebagai syarat input
Karena keterbatasan memori dilakukan sampling sebanyak 500 amatan dimata amatan dipilih secara acak
set.seed(123) # Set seed for reproducibility
sampled_matrix <- ratings_matrix[sample(1:nrow(ratings_matrix), 500, replace = FALSE), ]
# Check the dimensions of the sampled matrix to ensure it's correct
dim(sampled_matrix)
## [1] 500 50
matriks hasil sampling dikonvert kedalam bentuk recommenderlab realRatingMatrix sebagai syarat input
# convert matrix to a recommenderlab realRatingMatrix
rmat_sampled <- as(sampled_matrix, "realRatingMatrix")
Data dipisah menjadi data latih dan data uji dengan proporsi 8:2
set.seed(6030)
# split the data into the training and the test set:
split_data <- evaluationScheme(
rmat_sampled,
method="split",
train=0.8,
k=1,
given=10,
goodRating=3
)
## Warning in .local(data, ...): Dropping these users from the evaluation since they have fewer rating than specified in given!
## These users are 13, 53, 66, 73, 84, 144, 155, 181, 250, 258, 264, 307, 334, 372, 494
Dalam pemodelan ini tiga pendekatan berbeda dibangun untuk sistem rekomendasi.
Pendekatan pertama menggunakan cosine similarity tanpa normalisasi, di mana kemiripan antara pengguna dihitung berdasarkan sudut antara vektor rating mereka. Pendekatan kedua menggunakan cosine similarity yang dinormalisasi dengan mengurangkan rata-rata rating pengguna, sehingga lebih fokus pada perbedaan preferensi relatif daripada preferensi absolut.
Pendekatan ketiga adalah dekomposisi UV menggunakan Singular Value Decomposition (SVD), yang merupakan metode yang lebih kompleks. SVD mengurangi dimensi data dengan mengidentifikasi pola utama dalam rating pengguna terhadap item, yang memungkinkan sistem untuk menangkap hubungan laten antara pengguna dan item.
set.seed(6030)
# Non-normalized cosine
netflix_reco <- Recommender(getData(split_data, "train"), "UBCF",
param=list(normalize = NULL, method="Cosine"))
# Normalized centered cosine
netflix_reco_centered <- Recommender(getData(split_data, "train"), "UBCF",
param=list(normalize = "center", method="Cosine"))
# UV decomposition
netflix_reco_uv <- Recommender(getData(split_data, "train"), "SVDF",
param=list(k = 4))
Ketiga model diprediksi menggunakan data uji. Dalam evaluasi sistem rekomendasi, penting untuk mengukur seberapa akurat prediksi model dalam memprediksi rating yang sebenarnya diberikan oleh pengguna. Untuk melakukan ini, kita menggunakan beberapa metrik error, yaitu Root Mean Squared Error (RMSE), Mean Squared Error (MSE), dan Mean Absolute Error (MAE).
netflix_predictions <- predict(netflix_reco, getData(split_data, "known"), type="ratings")
netflix_predictions_centered <- predict(netflix_reco_centered, getData(split_data, "known"), type="ratings")
netflix_predictions_uv <- predict(netflix_reco_uv, getData(split_data, "known"), type="ratings")
error_results <- rbind(
cosine = calcPredictionAccuracy(netflix_predictions, getData(split_data, "unknown")),
cosine_normalized = calcPredictionAccuracy(netflix_predictions_centered, getData(split_data, "unknown")),
uv = calcPredictionAccuracy(netflix_predictions_uv, getData(split_data, "unknown"))
)
error_results
## RMSE MSE MAE
## cosine 1.0301142 1.0611353 0.8330421
## cosine_normalized 0.9914494 0.9829720 0.7890552
## uv 0.9457515 0.8944458 0.7412098
Dari error yang didapatkan Model UV decomposition adalah yang paling unggul dalam meminimalkan kesalahan prediksi berdasarkan hasil dari ketiga metrik yang digunakan. Model ini memberikan prediksi yang lebih akurat dan dapat lebih dipercaya untuk digunakan dalam sistem rekomendasi. Sementara itu, penggunaan normalisasi pada model cosine similarity juga memberikan peningkatan kinerja yang signifikan dibandingkan dengan model tanpa normalisasi.
Sistem Rekomendasi akan diaplikasikan menggunakan keseluruhan data yang ada (data yang telah dipangkas berdasarkan 50 film populer)
set.seed(6030)
# Non-normalized cosine
reco <- Recommender(rmat_sampled, "UBCF",
param=list(normalize = NULL, method="Cosine"))
# Normalized centered cosine
reco_centered <- Recommender(rmat_sampled, "UBCF",
param=list(normalize = "center", method="Cosine"))
# UV decomposition
reco_uv <- Recommender(rmat_sampled, "SVDF",
param=list(k = 4))
# Misalkan user id = 584538 yang ingin kita prediksi
user_id <- "584538"
# Prediksi rating untuk user_id ini terhadap semua movie_id menggunakan model reco (non-normalized cosine)
predictions <- predict(reco_uv, rmat_sampled[user_id, ], type = "ratings")
# Konversi hasil prediksi menjadi matrix
predicted_ratings <- as(predictions, "matrix")
Berikut hasil prediksi kotornya
# Menampilkan prediksi rating
print(predicted_ratings)
## 30 175 191 313 457 571 607 758
## 584538 4.769431 3.592382 3.991497 4.606472 NA 4.015071 4.172421 4.360612
## 798 886 985 1145 1180 1202 1220 1307
## 584538 3.736844 4.227287 4.06825 4.559287 4.268238 4.059928 4.231736 4.569893
## 1428 1470 1542 1798 1865 1905 1962 2095 2112
## 584538 NA NA 4.090288 4.116154 NA 4.133637 NA 4.367963 4.344856
## 2122 2152 2372 2391 2452 2782 2862 2913 3106
## 584538 3.673102 4.364819 4.19396 4.63997 NA 3.801927 4.088867 4.008902 NA
## 3151 3282 3427 3624 3756 3825 3860 3925 3938 3962 4043
## 584538 NA 4.244464 NA NA 4.370459 4.471824 NA NA NA NA NA
## 4123 4306 4356 4432 4472
## 584538 4.412021 4.099632 4.553681 NA NA
# konversi menjadi dataframe
predicted_ratings_df <- as.data.frame(predicted_ratings)
# Membalik kolom menjadi baris dengan pivot_longer
long_df <- pivot_longer(predicted_ratings_df,
cols = everything(),
names_to = "Movie_ID",
values_to = "Rating")
# Mengubah kolom Movie_ID menjadi numeric
long_df <- long_df %>%
mutate(Movie_ID = as.numeric(Movie_ID))
# Sortir dari nilai Rating terbesar
df_sorted <- long_df %>%
filter(!is.na(Rating)) %>%
arrange(desc(Rating))
# Menggabungkan dan mengganti Movie_ID dengan Movie_Name
final_df <- df_sorted %>%
left_join(detail_film, by = "Movie_ID") %>%
select(Name, Rating)
# Menampilkan hasil
print(final_df)
## # A tibble: 33 × 2
## Name Rating
## <chr> <dbl>
## 1 Something's Gotta Give 4.77
## 2 Along Came Polly 4.64
## 3 Pay It Forward 4.61
## 4 S.W.A.T. 4.57
## 5 The Wedding Planner 4.56
## 6 Road to Perdition 4.55
## 7 The Sum of All Fears 4.47
## 8 Patch Adams 4.41
## 9 About Schmidt 4.37
## 10 Liar Liar 4.37
## # ℹ 23 more rows
Semakin tinggi nilai Rating maka film semakin cocok untuk direkomendasikan. Pada kasus user_id = 584538 film The Bourne Supremacy merupakan yang paling direkomendasikan