Sekilas Projek

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

Praproses Analisis

Library yang akan digunakan

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 yang diperlukan

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

Pemangkasan Data

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

Pengonversian ke dalam Matriks

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

Proses Sampling

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")

Pemisahan Data

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

Pemodelan Sistem Rekomendasi

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))

Prediksi dan Evaluasi

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).

  1. RMSE (Root Mean Squared Error): Metrik ini mengukur akar dari rata-rata kesalahan kuadrat antara prediksi dan rating sebenarnya. RMSE memberikan penalti yang lebih besar untuk kesalahan yang lebih besar, sehingga lebih sensitif terhadap outlier. Penggunaan RMSE membantu kita memahami seberapa besar rata-rata deviasi prediksi model dari nilai sebenarnya dalam skala yang sama dengan rating.
  2. MSE (Mean Squared Error): Ini adalah nilai rata-rata dari kesalahan kuadrat antara prediksi dan rating sebenarnya. Meskipun MSE sering digunakan dalam pembelajaran mesin, interpretasinya dalam konteks rating bisa sedikit lebih sulit karena skala error adalah kuadrat dari skala rating.
  3. *MAE (Mean Absolute Error)**: Metrik ini menghitung rata-rata dari kesalahan absolut antara prediksi dan rating sebenarnya. Tidak seperti RMSE, MAE lebih robust terhadap outlier karena tidak mengkuadratkan error. MAE memberikan gambaran langsung mengenai seberapa jauh prediksi rata-rata dari nilai yang sebenarnya.
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.

Pengaplikasian pada seluruh data

Sistem Rekomendasi akan diaplikasikan menggunakan keseluruhan data yang ada (data yang telah dipangkas berdasarkan 50 film populer)

Proses Pemodelan

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))

Prediksi Film Rekomendasi untuk 1 pengguna

# 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")

Hasil Prediksi

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