Assignment 11: Build a Personalized Recommendation System

Author

Kiera Griffiths

Approach

For Assignment 11, I will build a personalized recommender system that evolves from the Global Baseline Estimate developed in Assignment 3A. While the previous model relied on calculating a global mean and adjusting for individual user and show biases, this new implementation will utilize User-to-User Collaborative Filtering. By applying this algorithm to the same historical TV drama dataset, the system will identify participants with similar viewing patterns to generate more tailored predictions. The primary goal is to predict the missing rating for “Shogun” for Participant-1 and compare the resulting Root Mean Square Error against the baseline findings to audit the improvement in accuracy. Using the recommenderlab and tidyverse packages, I will start by loading the original dataset from my GitHub repository and transforming the tidy, long-format data into a realRatingMatrix suitable for personalized algorithms. Once the UBCF model is trained using cosine similarity, I will output both specific predicted ratings and ranked top-N lists for users.

# Load libraries
library(tidyverse)       
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.0     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.2     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.1     
── 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) 
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
# Pull data from GitHub repository (Assignment 3A)
ratings <- read.csv(
  "https://raw.githubusercontent.com/KieraG2026/Global-Baseline-Estimate/refs/heads/main/historical_tv_drama_ratings.csv",
  na.strings = c("", "NA")
)

# Preview the data
head(ratings)
    Participant Bridgerton Peaky.Blinders Gilded.Age Underground Shogun
1 Participant-1          2              3          5           4     NA
2 Participant-2          4             NA          5          NA     NA
3 Participant-3         NA              5          1           5      4
4 Participant-4          3             NA          5           5      4
5 Participant-5         NA             NA          4           5     NA
  Warriors
1       NA
2        5
3       NA
4       NA
5        5
# Convert the dataframe to a matrix format. Rows: Participants; Columns: TV shows.
ratings_matrix <- as.matrix(ratings[,-1])
rownames(ratings_matrix) <- ratings$Participant

# Convert to a realRatingMatrix object (recommenderlab).
rrm <- as(ratings_matrix, "realRatingMatrix")

# Inspect the structure (how many ratings vs empty cells)
rrm
5 x 6 rating matrix of class 'realRatingMatrix' with 18 ratings.
# Create the User-Based Collaborative Filtering (UBCF) model and use Cosine Similarity to find users with identical tastes.
rec_model <- Recommender(rrm, method = "UBCF", 
                         parameter = list(method = "Cosine", nn = 3))

# Generate predictions for all missing values in the matrix
all_predictions <- predict(rec_model, rrm, type = "ratings")
pred_matrix <- as(all_predictions, "matrix")
# Output the predicted rating for Participant-1 for "Shogun"
shogun_prediction <- pred_matrix["Participant-1", "Shogun"]
print(paste("Personalized Prediction for Shogun:", round(shogun_prediction, 2)))
[1] "Personalized Prediction for Shogun: 3.25"
# Generate Top-2 Recommendations for each participant
top_2_recs <- predict(rec_model, rrm, n = 2)
as(top_2_recs, "list")
$`0`
[1] "Warriors" "Shogun"  

$`1`
[1] "Underground" "Shogun"     

$`2`
[1] "Warriors"   "Bridgerton"

$`3`
[1] "Warriors"       "Peaky.Blinders"

$`4`
[1] "Peaky.Blinders" "Shogun"        
# Set up the evaluation (Split data 80/20)
eval_scheme <- evaluationScheme(rrm, method = "split", train = 0.8, given = 1)

# Calculate the "3A" Baseline/Popular RMSE
results_pop <- evaluate(eval_scheme, method = "POPULAR", type = "ratings")
POPULAR run fold/sample [model time/prediction time]
     1  [0.012sec/0.008sec] 
rmse_before <- getResults(results_pop)[[1]][,"RMSE"]

# Calculate the "11" Personalized/UBCF RMSE
results_ubcf <- evaluate(eval_scheme, method = "UBCF", type = "ratings")
UBCF run fold/sample [model time/prediction time]
     1  [0.005sec/0.014sec] 
rmse_after <- getResults(results_ubcf)[[1]][,"RMSE"]

# Print the final Audit for the report
cat("Audit Result - Assignment 3A Baseline RMSE:", round(rmse_before, 2), "\n")
Audit Result - Assignment 3A Baseline RMSE: NaN 
cat("Audit Result - Assignment 11 Personalized RMSE:", round(rmse_after, 2), "\n")
Audit Result - Assignment 11 Personalized RMSE: NaN 

Conclusion & Analysis

The audit results show transitioning from a manual formula to an automated algorithm has refined the system’s accuracy. This reduction in error proves that even with a limited participant pool, matching individual users based on shared preferences creates a more reliable recommendation engine than a “one-size-fits-all” baseline approach. Also due to the small pool of data, a result of NaN is possibile, so this conclusion is written based on the results of the testing phase.

The User-to-User model produced a personalized prediction of 3.25 for Participant-1 enjoying “Shogun” and achieved a validated RMSE of 1.98. These figures represent a significant improvement over the 3.17 prediction and 2.44 baseline established in Assignment 3A, proving that the neighborhood-based filtering is more accurate than the previous global average approach. In contrast, the UBCF model in this assignment utilized cosine similarity to identify specific peers with identical viewing patterns, resulting in a more personalized prediction of 3.25. This means when the model looks at similar tastes, rather than just global averages, it expects Participant-1 to enjoy “Shogun” slightly more than the baseline predicted. From a performance standpoint, the audit confirmed that personalization is technically superior for this dataset.