library(tidyverse)Week 3A Assignment - Global Baseline Estimate
Global Baseline Estimate
Approach
Recap
This assignment is a follow up on the previous assignment Week 2A’s assignment, Movie Ratings where the goal of that assignment was to conduct a survey using a Google Form about recent popular movies and the users ratings of them.
Goal
To expand on the use of that survey’s data, we will now be creating a recommender system using the Global Baseline Algorithm to estimate what a user would rate a movie they had not seen using a formula that consists of information of:
Global Mean \(\mu\)
User Bias \(b_u\)
Item Bias \(b_i\)
\[\begin{equation*} \hat{r}_{ui} = \mu + b_u + b_i \end{equation*}\]
About the Algorithm
The algorithm as mentioned is an estimate to predict what a user would rate something that they had not seen before.
We have the formula above, but what does it mean specifically rather than taking averages in a spreadsheet and computing addition and subtraction between the variables?
Definitions
Predicted Rating \(\hat{r}_{ui}\): Our predicted rating after computing the arithmetic among the variables. This is not an observed rating.
Global Mean \(\mu\): The mean of all the ratings for all movies in 1 variable.
User Bias \(b_u\): This is a way to see if the user rated a something higher or lower than the global mean with its users
Item Bias \(b_i\): Similar to user bias, this is a way to see if someone rated the movies higher than the global mean.
In different contexts, the formula still applies but in this context it is on movie ratings as a great introduction to recommendation systems as in the future the complexity will increase.
The Plan
Given that this is a step up to the previous assignment, we need to have the cleaned version of the dataset as it contains appropriate column names and column typings.
Following that we need to rearrange our table from a wide table to a long table to be able to compute calculations easier.
Once we have our long table, we can then start computing the averages by hand and then implementing it by code.
After doing one person, we can apply the same format for people that had not seen a certain movie.
Codebase
First we need to import the data values once again into a tribble
Import Library
Creating Tribble
ratings_wide <- tribble(
~user, ~spider_man_bnd, ~the_odyssey, ~coyote_vs_acme, ~the_whisper_man, ~toy_story_5, ~project_hail_mary,
"1", 4, NA, NA, NA, 4, 5,
"2", 4, 3, NA, NA, NA, 5,
"3", 4, 3, NA, NA, 2, 5,
"4", 4, 5, NA, NA, 4, 5,
"5", NA, NA, NA, NA, NA, NA,
"6", 5, NA, 5, NA, 1, 5,
"7", 5, 4, 5, NA, 4, 5,
"8", NA, NA, NA, NA, NA, NA,
"9", NA, NA, NA, NA, NA, NA,
"10", 3, 5, NA, NA, NA, 5,
"11", 4, NA, NA, NA, NA, 4,
"12", NA, NA, NA, NA, NA, 1,
"13", 5, 4, 4, 4, 5, 5,
"14", 5, 5, 4, NA, 3, 3,
"15", NA, NA, NA, NA, NA, 5,
"16", 5, 1, 3, 5, 5, 2,
"17", 3, 1, 2, 4, NA, 5,
"18", 5, 5, NA, NA, NA, 5,
"19", 5, NA, NA, NA, 5, 5,
"20", NA, NA, NA, NA, 4, 5,
"21", NA, NA, NA, NA, NA, 2
)
ratings_wide# A tibble: 21 × 7
user spider_man_bnd the_odyssey coyote_vs_acme the_whisper_man toy_story_5
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1 4 NA NA NA 4
2 2 4 3 NA NA NA
3 3 4 3 NA NA 2
4 4 4 5 NA NA 4
5 5 NA NA NA NA NA
6 6 5 NA 5 NA 1
7 7 5 4 5 NA 4
8 8 NA NA NA NA NA
9 9 NA NA NA NA NA
10 10 3 5 NA NA NA
# ℹ 11 more rows
# ℹ 1 more variable: project_hail_mary <dbl>
Now we have the data in tabular format, for this assignment and the algorithm in general, we need it in a long format rather than a wide so it is easier to calculate averages.
Wide to Long Format
ratings_long <- ratings_wide |>
pivot_longer(-user, names_to = "movies", values_to = "rating") |>
drop_na(rating)
ratings_long# A tibble: 61 × 3
user movies rating
<chr> <chr> <dbl>
1 1 spider_man_bnd 4
2 1 toy_story_5 4
3 1 project_hail_mary 5
4 2 spider_man_bnd 4
5 2 the_odyssey 3
6 2 project_hail_mary 5
7 3 spider_man_bnd 4
8 3 the_odyssey 3
9 3 toy_story_5 2
10 3 project_hail_mary 5
# ℹ 51 more rows
We have successfully converted the table to a long format. Which means every row is now stacked upon each other
Global Average Rating
global_mean <- mean(ratings_long$rating)
global_mean[1] 4.04918
We now have one of the variables for our formula:
- \(\mu\): 4.04918
User Bias
The next step is to retrieve our \(b_u\) variable, the user bias, which indicates if the user average rating is higher or lower than the global mean
user_bias <- ratings_long |>
group_by(user) |>
summarize(b_u = mean(rating) - global_mean)
user_bias# A tibble: 18 × 2
user b_u
<chr> <dbl>
1 1 0.284
2 10 0.284
3 11 -0.0492
4 12 -3.05
5 13 0.451
6 14 -0.0492
7 15 0.951
8 16 -0.549
9 17 -1.05
10 18 0.951
11 19 0.951
12 2 -0.0492
13 20 0.451
14 21 -2.05
15 3 -0.549
16 4 0.451
17 6 -0.0492
18 7 0.551
We now have our user bias variable for each user, and at first glance, there are those slightly above or below, or way below the global mean average.
Item Bias
item_bias <- ratings_long |>
group_by(movies) |>
summarize(b_i = mean(rating) - global_mean)
item_bias# A tibble: 6 × 2
movies b_i
<chr> <dbl>
1 coyote_vs_acme -0.216
2 project_hail_mary 0.229
3 spider_man_bnd 0.308
4 the_odyssey -0.449
5 the_whisper_man 0.284
6 toy_story_5 -0.349
We have successfully created our last variable for our formula.
With all this information we can now proceed to predict what certain users would rate a movie that they had not seen.
Some Examples Of Predictions
Given that there are 21 rows and some users have not seen all of the movies or most of them, I will be doing 3 examples to show that the formula and arithmetic works.
First Example - User 4
For the first example, we will predict movies user number 4 had not seen and predict what they would rate it had they seen it.
four_seen <- ratings_long |>
filter(user == "4") |>
distinct(movies)
four_seen# A tibble: 4 × 1
movies
<chr>
1 spider_man_bnd
2 the_odyssey
3 toy_story_5
4 project_hail_mary
Let us do the predictions arithmetically before writing in code:
| Quantity | Rounded Value | How? |
|---|---|---|
| Global Mean, \(\mu\) | 4.05 | Global mean given from code |
| User 4 Bias, \(b_u\) | 0.45 | Refer to User Bias Section |
Coyote vs ACME Bias \(b_i\) |
-0.22 | Refer to Item Bias Section |
Prediction: 4.05 + 0.45 + (-0.22) = 4.28
| Quantity | Rounded Value | How? |
|---|---|---|
| Global Mean, \(\mu\) | 4.05 | Global mean given from code |
| User 4 Bias, \(b_u\) | 0.45 | Refer to User Bias Section |
The Whisper Man Bias \(b_i\) |
0.28 | Refer to Item Bias Section |
Prediction: 4.05 + 0.45 + 0.28 = 4.78
four_predictions <- ratings_long |>
distinct(movies) |>
anti_join(four_seen, by = "movies") |>
mutate(user = "4") |>
left_join(user_bias, by = "user") |>
left_join(item_bias, by = "movies") |>
mutate(predicted_rating = global_mean + b_u + b_i) |>
arrange(desc(predicted_rating))
four_predictions# A tibble: 2 × 5
movies user b_u b_i predicted_rating
<chr> <chr> <dbl> <dbl> <dbl>
1 the_whisper_man 4 0.451 0.284 4.78
2 coyote_vs_acme 4 0.451 -0.216 4.28
After computing the arithmetic by carefully referring to each variable respective tibbles, we were able to code the prediction tibble and get a return of the predicted rating in a non-rounded format, but it does align with our results.
A simple formula for the R Code above:
- Retrieve Distinct Movies
- That they have not seen (NA values)
- Mutate so that we create a column with only showing “4”
- Left Join to bring in user bias
- Left Join to bring item bias
- Mutate to create a new column for predicted rating
- Arrange in descending order
Example 2: User 7
This time we will predict this user who has seen one of the movies out of the 6.
seven_seen <- ratings_long |>
filter(user == "7") |>
distinct(movies)
seven_seen# A tibble: 5 × 1
movies
<chr>
1 spider_man_bnd
2 the_odyssey
3 coyote_vs_acme
4 toy_story_5
5 project_hail_mary
Let us do the predictions arithmetically before writing in code:
| Quantity | Rounded Value | How? |
|---|---|---|
| Global Mean, \(\mu\) | 4.05 | Global mean given from code |
| User 4 Bias, \(b_u\) | 0.95 | Refer to User Bias Section |
The Whisper Man Bias \(b_i\) |
0.28 | Refer to Item Bias Section |
Prediction: 4.05 + 0.55 + 0.28 = 4.88
seven_predictions <- ratings_long |>
distinct(movies) |>
anti_join(seven_seen, by = "movies") |>
mutate(user = "7") |>
left_join(user_bias, by = "user") |>
left_join(item_bias, by = "movies") |>
mutate(predicted_rating = global_mean + b_u + b_i) |>
arrange(desc(predicted_rating))
seven_predictions# A tibble: 1 × 5
movies user b_u b_i predicted_rating
<chr> <chr> <dbl> <dbl> <dbl>
1 the_whisper_man 7 0.551 0.284 4.88
Once again, this formulaic and arithmetic calculation was correct yet again.
Final Example - User 10
Lets try with 3 missing ratings:
ten_seen <- ratings_long |>
filter(user == "10") |>
distinct(movies)
ten_seen# A tibble: 3 × 1
movies
<chr>
1 spider_man_bnd
2 the_odyssey
3 project_hail_mary
| Quantity | Rounded Value | How? |
|---|---|---|
| Global Mean, \(\mu\) | 4.05 | Global mean given from code |
| User 10 Bias, \(b_u\) | 0.28 | Refer to User Bias Section |
Coyote vs ACME Bias \(b_i\) |
-0.22 | Refer to Item Bias Section |
Prediction: 4.05 + 0.28+ (-0.22) = 4.11
| Quantity | Rounded Value | How? |
|---|---|---|
| Global Mean, \(\mu\) | 4.05 | Global mean given from code |
| User 10 Bias, \(b_u\) | 0.28 | Refer to User Bias Section |
The Whisper Man Bias \(b_i\) |
0.28 | Refer to Item Bias Section |
Prediction: 4.05 + 0.28 + 0.28 = 4.61
| Quantity | Rounded Value | How? |
|---|---|---|
| Global Mean, \(\mu\) | 4.05 | Global mean given from code |
| User 10 Bias, \(b_u\) | 0.28 | Refer to User Bias Section |
Toy Story 5 Bias \(b_i\) |
-0.35 | Refer to Item Bias Section |
Prediction: 4.05 + 0.28+ (-0.35) = 3.98