Using the survey data collected in Week 2, I have implemented a global baseline estimate (GBE) recommender system as below: \[\hat{r}_{ui} = \mu + b_u + b_i\] where
\(\hat{r}_{ui}\) = predicted rating for movie \(i\) and user \(u\)
\(\mu\) = mean rating for movie \(i\) across all users
\(b_u\) = user bias (\(\mu_u - \mu\) how this user rated movies compared to the average)
\(b_i\) = movie bias (\(\mu_i - \mu\) how this movie is rated compared to the average)
Data Import
Data is imported from .csv from the SQL database. See Assignment 2A for more details.
I create another dataframe that includes NaN ratings for unseen movies.
full_ratings_df <- rating_raw %>%complete(user_name, movie_title, fill =list(rating =NaN))
Data Presentation
Use great table and conditional formatting to display ratings before recommender.
# Reshape data into 5 rows (users) and 6 movie columnsratings_matrix <- full_ratings_df |>pivot_wider(names_from = movie_title,values_from = rating )# 2. Initialize base tabletbl <- ratings_matrix |>gt(rowname_col ="user_name") |>sub_missing(missing_text ="NaN") ## 3. Apply background fill to missing cells per columnmovie_cols <-setdiff(names(ratings_matrix), "user_name")for (col in movie_cols) {# Get row indices where the current column has NA/NaN na_rows <-which(is.na(ratings_matrix[[col]]))if (length(na_rows) >0) { tbl <- tbl |>tab_style(style =cell_fill(color ="#f8d7da"), # Soft red filllocations =cells_body(columns =all_of(col),rows = na_rows ) ) }}tbl
Goat
Project Hail Mary
Spiderman Brand New Day
Superman
The Odyssey
Toy Story 5
Cody Slater
3.5
3.7
NaN
4.2
4.5
3.9
Heidi Smith
4.5
NaN
NaN
NaN
NaN
4.3
Jacob OBrien
3.3
4.3
4.6
4.5
5.0
4.0
Jocelyn Slater
4.0
4.8
NaN
4.7
NaN
4.2
Michael Pento
4.0
4.8
NaN
NaN
NaN
4.3
Global Baseline Estimate
To calcuate the GBE, we must first calculate the metrics of each user avg rating, global average movie rating, and each movie average
# Convert to matrix mat <-as.matrix(ratings_matrix[, -1])rownames(mat) <- ratings_matrix$user_name# Compute necessary GBE metricsmu <-mean(mat, na.rm =TRUE) # Overall mean movie ratingmovie_bias <-colMeans(mat, na.rm =TRUE) - mu # Movie relative rating (b_i)user_bias <-rowMeans(mat, na.rm =TRUE) - mu # User relative rating (b_u)# Create imputed matriximputed_mat <- mat# If the cell is NaN (user has not seen the movie), perform GBE calculation and fill in the valuefor (user inrownames(mat)) {for (movie incolnames(mat)) {if (is.na(mat[user, movie])) { est <- mu + movie_bias[movie] + user_bias[user] imputed_mat[user, movie] <-round(est, 2) } }}# Convert back to data frame, add back in user namesimputed_ratings_df <-as.data.frame(imputed_mat) |> tibble::rownames_to_column(var ="user_name")
# Display table with imputed values# 1. Start with the imputed data frametbl_imputed <- imputed_ratings_df |>gt(rowname_col ="user_name")# 2. Extract column names (excluding user_name)movie_cols <-setdiff(names(imputed_ratings_df), "user_name")# 3. Loop over each column and apply background fill ONLY to originally missing rowsfor (col in movie_cols) {# Identify row indices that were NA/NaN in the original matrix na_rows <-which(is.na(ratings_matrix[[col]]))if (length(na_rows) >0) { tbl_imputed <- tbl_imputed |>tab_style(style =cell_fill(color ="#e0f2fe"), locations =cells_body(columns =all_of(col),rows = na_rows ) ) }}tbl_imputed
Goat
Project Hail Mary
Spiderman Brand New Day
Superman
The Odyssey
Toy Story 5
Cody Slater
3.5
3.70
4.30
4.20
4.50
3.9
Heidi Smith
4.5
4.55
4.74
4.61
4.90
4.3
Jacob OBrien
3.3
4.30
4.60
4.50
5.00
4.0
Jocelyn Slater
4.0
4.80
4.77
4.70
4.92
4.2
Michael Pento
4.0
4.80
4.71
4.58
4.86
4.3
Summary and Next Steps
In order to make a recommendation, I need to decide which estimated rating will be the cutoff. I have decided 4.5 to be that cutoff. For any movie that has a global baseline estimate of below 4.5, I will not recommend, but at or above 4.5 I will recommend for that user. Therefore, I would recommend all of the listed movies to every user in my system, with the exception that Cody Slater would not be recommended Spiderman Brand New Day.
Next steps could include surveying many more people and deploying this algorithm on a much larger database. I could also build more of an interface that displayed recommendations automatically after a user filled out the survery, along the lines of “if you liked this, you might also like this”. I would also be interested in playing around with the ratings cutoff for a recommendation to see if 4.5 is the best value or if users would be happier with a lower or higher threshold.
LLM Model Citations
Google DeepMind. (2026). Gemini 3.6 Flash [Large language model]. https://gemini.google.com. Accessed Sept 16, 2026. Transcript available in Github as 3AGeminiTranscript.pdf