This recommender system is built to recommend movies to users based on user ratings. The dataset was collected from grouplens.org.

library(tidyverse)
library(Matrix)

Read Data into R

ratings <- read.csv("https://raw.githubusercontent.com/javernw/DATA612-Recommender-Systems/master/ratings.csv", header = T, stringsAsFactors = F)
#copy ratings
tbl_ratings <- ratings %>% select(-timestamp)
tbl_ratings$userId <- as.factor(tbl_ratings$userId)
tbl_ratings$movieId <- as.factor(tbl_ratings$movieId)

Create User-Item Matrix

UI_matrix <- sparseMatrix(
  i = as.integer(tbl_ratings$userId), 
  j = as.integer(tbl_ratings$movieId), 
  x = tbl_ratings$rating, 
  dimnames = list(levels(tbl_ratings$userId), levels(tbl_ratings$movieId))
 )
UI_matrix
610 x 9724 sparse Matrix of class "dgCMatrix"
   [[ suppressing 32 column names <U+393C><U+3E31>1<U+393C><U+3E32>, <U+393C><U+3E31>2<U+393C><U+3E32>, <U+393C><U+3E31>3<U+393C><U+3E32> ... ]]
   [[ suppressing 32 column names <U+393C><U+3E31>1<U+393C><U+3E32>, <U+393C><U+3E31>2<U+393C><U+3E32>, <U+393C><U+3E31>3<U+393C><U+3E32> ... ]]
                                                                             
1  4.0 . 4 . . 4 . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
2  .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
3  .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 0.5 . ......
4  .   . . . . . . . . . . . . . . . . . . . 3 . . . . . . . . . .   2 ......
5  4.0 . . . . . . . . . . . . . . . . . . . 4 . . . . . . . . . .   . ......
6  .   4 5 3 5 4 4 3 . 3 4 . 3 . 4 4 4 . 2 . 2 5 . 4 3 4 3 . . . 3.0 4 ......
7  4.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
8  .   4 . . . . . . . 2 4 . . . . . . . . . 4 . . . . . . . . . .   3 ......
9  .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
10 .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
11 .   . . . . 5 . . . 3 . . . . . . . . . . . . . . . . . . . . .   . ......
12 .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
13 .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
14 .   . . 3 . . 3 . . . . . . . . . . . 1 . . . . . 4 . . . . . .   4 ......
15 2.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......
16 .   . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   . ......

 ..............................
 ........suppressing 9692 columns and 579 rows in show(); maybe adjust 'options(max.print= *, width = *)'
 ..............................
   [[ suppressing 32 column names <U+393C><U+3E31>1<U+393C><U+3E32>, <U+393C><U+3E31>2<U+393C><U+3E32>, <U+393C><U+3E31>3<U+393C><U+3E32> ... ]]
                                                                                 
596 4.0 .   .   .   .   .   .   . .   .   .   .   . . .   .   .   . . .   .   . .
597 4.0 .   .   .   .   3.0 1.0 . .   3.0 3.0 .   . . .   .   3.0 . . .   5.0 . .
598 .   .   .   .   .   .   .   . .   .   .   .   . . .   .   .   . . .   .   . .
599 3.0 2.5 1.5 .   .   4.5 2.5 . 1.5 3.5 2.5 1.5 . . 1.5 3.0 3.5 3 3 1.5 4.0 . 1
600 2.5 4.0 .   1.5 2.5 .   3.5 . .   .   .   .   . . .   .   3.5 . 3 .   .   . .
601 4.0 .   .   .   .   .   .   . .   .   .   .   . . .   .   .   . . .   .   . .
602 .   4.0 .   .   .   3.0 .   . .   3.0 3.0 .   . 5 .   3.0 .   . 2 .   4.0 3 .
603 4.0 .   .   .   .   4.0 .   . .   .   .   .   . . .   4.0 3.0 . . .   5.0 . .
604 3.0 5.0 .   .   3.0 3.0 .   . .   .   .   .   . 4 .   .   4.0 . 1 .   .   3 4
605 4.0 3.5 .   .   .   .   .   . .   .   .   .   . . .   .   .   . . .   .   . .
606 2.5 .   .   .   .   .   2.5 . .   .   2.5 .   . . 3.5 .   4.0 4 2 .   .   . .
607 4.0 .   .   .   .   .   .   . .   .   3.0 .   . . .   .   .   . . .   .   . .
608 2.5 2.0 2.0 .   .   .   .   . .   4.0 .   .   . . .   4.5 .   . 2 .   3.5 . .
609 3.0 .   .   .   .   .   .   . .   4.0 .   .   . . .   .   .   . . .   .   . .
610 5.0 .   .   .   .   5.0 .   . .   .   .   .   . . .   4.5 .   . . .   .   . .
                                      
596 .   . .   . .   .   . . 3.5 ......
597 .   . .   . .   .   . . .   ......
598 .   . .   . .   .   . . .   ......
599 2.5 . 2.5 . .   3.5 . 2 3.0 ......
600 2.0 . .   . .   4.5 . . 4.5 ......
601 .   . .   . .   .   . . .   ......
602 .   4 .   . .   2.0 . . 3.0 ......
603 .   4 .   . 5.0 2.0 4 . 3.0 ......
604 .   3 .   . .   .   . . 4.0 ......
605 .   . .   . 4.0 .   . . .   ......
606 .   . .   . 3.5 4.5 . . 4.0 ......
607 .   3 .   . .   .   . . .   ......
608 2.0 . .   . .   .   . 3 3.5 ......
609 .   . .   . .   .   . . .   ......
610 .   . .   . .   .   . . 4.5 ......

Training and Testing Datasets (80:20)

# break up into training and test sets
train <- sample(x = c(T, F), size = nrow(UI_matrix), replace = T, prob = c(0.8, 0.2)) 
training_data <- UI_matrix[train, ] 
testing_data <- UI_matrix[-train, ]

Raw Average

avg_train <- mean(training_data@x)
avg_train
[1] 3.49839

RMSE

RMSE <- function(m, o){
  sqrt(mean((m - o)^2))
}
train_rmse <- RMSE(avg_train, training_data)
train_rmse
[1] 3.470717
test_rmse <- RMSE(avg_train, testing_data)
test_rmse
[1] 3.471206

Bias for each user and item

user_bias <- rowMeans(training_data) - avg_train
item_bias <- colMeans(training_data) - avg_train

Baseline Predictors

baseline_predictors <- user_bias + item_bias + avg_train
#ratings cannot be lower than 1 or higher than 5
baseline_predictors[baseline_predictors < 1] <- 1
baseline_predictors[baseline_predictors > 5] <- 5

RMSE for Baseline Predictors (train and test)

Train

base_train_rmse <- RMSE(avg_train, baseline_predictors)

Test

avg_test <- mean(testing_data@x)
test_user_bias <- rowMeans(testing_data) - avg_test
test_item_bias <- colMeans(testing_data) - avg_test
test_baseline_predictors <- avg_test + test_item_bias + test_user_bias
longer object length is not a multiple of shorter object length
#ratings cannot be lower than 1 or higher than 5
test_baseline_predictors[test_baseline_predictors < 1] <- 1
test_baseline_predictors[test_baseline_predictors > 5] <- 5
base_test_rmse <- RMSE(avg_test, test_baseline_predictors)

Summary

test_ <- (1 - (base_test_rmse / test_rmse)) * 100
test_
[1] 27.99152
train_ <- (1 - (base_train_rmse / train_rmse)) * 100
train_
[1] 28.01515

Based on the calculations above, the recommender system improved by 28% with the test data and 28% with the training data when making predictions.

Sources

https://stackoverflow.com/questions/26237688/rmse-root-mean-square-deviation-calculation-in-r

https://stackoverflow.com/questions/51467276/how-to-find-the-column-means-for-a-sparse-matrix-excluding-0-values

LS0tDQp0aXRsZTogJ0RBVEEgNjEyOiBQcm9qZWN0IDF8IEdsb2JhbCBCYXNlbGluZSBQcmVkaWN0b3JzIGFuZCBSTVNFICcNCmF1dGhvcjogIkphdmVybiBXaWxzb24iDQpkYXRlOiAiSnVuZSAzLCAyMDE5Ig0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICANCg0KLS0tDQoNCioqVGhpcyByZWNvbW1lbmRlciBzeXN0ZW0gaXMgYnVpbHQgdG8gcmVjb21tZW5kIG1vdmllcyB0byB1c2VycyBiYXNlZCBvbiB1c2VyIHJhdGluZ3MuIFRoZSBkYXRhc2V0IHdhcyBjb2xsZWN0ZWQgZnJvbSBbZ3JvdXBsZW5zLm9yZ10oaHR0cHM6Ly9ncm91cGxlbnMub3JnL2RhdGFzZXRzL21vdmllbGVucy8xMDBrLykuKioNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShNYXRyaXgpDQpgYGANCg0KIyMjIFJlYWQgRGF0YSBpbnRvIFINCmBgYHtyfQ0KDQpyYXRpbmdzIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vamF2ZXJudy9EQVRBNjEyLVJlY29tbWVuZGVyLVN5c3RlbXMvbWFzdGVyL3JhdGluZ3MuY3N2IiwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQoNCiNjb3B5IHJhdGluZ3MNCnRibF9yYXRpbmdzIDwtIHJhdGluZ3MgJT4lIHNlbGVjdCgtdGltZXN0YW1wKQ0KdGJsX3JhdGluZ3MkdXNlcklkIDwtIGFzLmZhY3Rvcih0YmxfcmF0aW5ncyR1c2VySWQpDQp0YmxfcmF0aW5ncyRtb3ZpZUlkIDwtIGFzLmZhY3Rvcih0YmxfcmF0aW5ncyRtb3ZpZUlkKQ0KDQoNCg0KYGBgDQoNCg0KIyMjIENyZWF0ZSBVc2VyLUl0ZW0gTWF0cml4DQpgYGB7cn0NCg0KVUlfbWF0cml4IDwtIHNwYXJzZU1hdHJpeCgNCiAgaSA9IGFzLmludGVnZXIodGJsX3JhdGluZ3MkdXNlcklkKSwgDQogIGogPSBhcy5pbnRlZ2VyKHRibF9yYXRpbmdzJG1vdmllSWQpLCANCiAgeCA9IHRibF9yYXRpbmdzJHJhdGluZywgDQogIGRpbW5hbWVzID0gbGlzdChsZXZlbHModGJsX3JhdGluZ3MkdXNlcklkKSwgbGV2ZWxzKHRibF9yYXRpbmdzJG1vdmllSWQpKQ0KICkNCg0KVUlfbWF0cml4DQpgYGANCg0KIyMjIFRyYWluaW5nIGFuZCBUZXN0aW5nIERhdGFzZXRzICg4MDoyMCkNCmBgYHtyfQ0KIyBicmVhayB1cCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMNCnRyYWluIDwtIHNhbXBsZSh4ID0gYyhULCBGKSwgc2l6ZSA9IG5yb3coVUlfbWF0cml4KSwgcmVwbGFjZSA9IFQsIHByb2IgPSBjKDAuOCwgMC4yKSkgDQp0cmFpbmluZ19kYXRhIDwtIFVJX21hdHJpeFt0cmFpbiwgXSANCg0KdGVzdGluZ19kYXRhIDwtIFVJX21hdHJpeFstdHJhaW4sIF0NCg0KYGBgDQoNCg0KIyMjIFJhdyBBdmVyYWdlDQpgYGB7cn0NCmF2Z190cmFpbiA8LSBtZWFuKHRyYWluaW5nX2RhdGFAeCkNCmF2Z190cmFpbg0KYGBgDQoNCiMjIyBbUk1TRV0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjYyMzc2ODgvcm1zZS1yb290LW1lYW4tc3F1YXJlLWRldmlhdGlvbi1jYWxjdWxhdGlvbi1pbi1yKQ0KYGBge3J9DQoNClJNU0UgPC0gZnVuY3Rpb24obSwgbyl7DQogIHNxcnQobWVhbigobSAtIG8pXjIpKQ0KfQ0KDQp0cmFpbl9ybXNlIDwtIFJNU0UoYXZnX3RyYWluLCB0cmFpbmluZ19kYXRhKQ0KdHJhaW5fcm1zZQ0KDQp0ZXN0X3Jtc2UgPC0gUk1TRShhdmdfdHJhaW4sIHRlc3RpbmdfZGF0YSkNCnRlc3Rfcm1zZQ0KDQpgYGANCg0KIyMjIEJpYXMgZm9yIGVhY2ggdXNlciBhbmQgaXRlbQ0KYGBge3J9DQp1c2VyX2JpYXMgPC0gcm93TWVhbnModHJhaW5pbmdfZGF0YSkgLSBhdmdfdHJhaW4NCml0ZW1fYmlhcyA8LSBjb2xNZWFucyh0cmFpbmluZ19kYXRhKSAtIGF2Z190cmFpbg0KYGBgDQoNCg0KDQojIyMgQmFzZWxpbmUgUHJlZGljdG9ycw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmJhc2VsaW5lX3ByZWRpY3RvcnMgPC0gdXNlcl9iaWFzICsgaXRlbV9iaWFzICsgYXZnX3RyYWluDQoNCiNyYXRpbmdzIGNhbm5vdCBiZSBsb3dlciB0aGFuIDEgb3IgaGlnaGVyIHRoYW4gNQ0KYmFzZWxpbmVfcHJlZGljdG9yc1tiYXNlbGluZV9wcmVkaWN0b3JzIDwgMV0gPC0gMQ0KYmFzZWxpbmVfcHJlZGljdG9yc1tiYXNlbGluZV9wcmVkaWN0b3JzID4gNV0gPC0gNQ0KDQpgYGANCg0KDQojIyMgUk1TRSBmb3IgQmFzZWxpbmUgUHJlZGljdG9ycyAodHJhaW4gYW5kIHRlc3QpDQoNClRyYWluDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYmFzZV90cmFpbl9ybXNlIDwtIFJNU0UoYXZnX3RyYWluLCBiYXNlbGluZV9wcmVkaWN0b3JzKQ0KYGBgDQoNClRlc3QNCmBgYHtyfQ0KYXZnX3Rlc3QgPC0gbWVhbih0ZXN0aW5nX2RhdGFAeCkNCg0KdGVzdF91c2VyX2JpYXMgPC0gcm93TWVhbnModGVzdGluZ19kYXRhKSAtIGF2Z190ZXN0DQp0ZXN0X2l0ZW1fYmlhcyA8LSBjb2xNZWFucyh0ZXN0aW5nX2RhdGEpIC0gYXZnX3Rlc3QNCg0KdGVzdF9iYXNlbGluZV9wcmVkaWN0b3JzIDwtIGF2Z190ZXN0ICsgdGVzdF9pdGVtX2JpYXMgKyB0ZXN0X3VzZXJfYmlhcw0KDQojcmF0aW5ncyBjYW5ub3QgYmUgbG93ZXIgdGhhbiAxIG9yIGhpZ2hlciB0aGFuIDUNCnRlc3RfYmFzZWxpbmVfcHJlZGljdG9yc1t0ZXN0X2Jhc2VsaW5lX3ByZWRpY3RvcnMgPCAxXSA8LSAxDQp0ZXN0X2Jhc2VsaW5lX3ByZWRpY3RvcnNbdGVzdF9iYXNlbGluZV9wcmVkaWN0b3JzID4gNV0gPC0gNQ0KDQpiYXNlX3Rlc3Rfcm1zZSA8LSBSTVNFKGF2Z190ZXN0LCB0ZXN0X2Jhc2VsaW5lX3ByZWRpY3RvcnMpDQoNCmBgYA0KDQoNCiMjIyBTdW1tYXJ5DQoNCmBgYHtyfQ0KdGVzdF8gPC0gKDEgLSAoYmFzZV90ZXN0X3Jtc2UgLyB0ZXN0X3Jtc2UpKSAqIDEwMA0KDQp0ZXN0Xw0KDQp0cmFpbl8gPC0gKDEgLSAoYmFzZV90cmFpbl9ybXNlIC8gdHJhaW5fcm1zZSkpICogMTAwDQp0cmFpbl8NCmBgYA0KDQpCYXNlZCBvbiB0aGUgY2FsY3VsYXRpb25zIGFib3ZlLCB0aGUgcmVjb21tZW5kZXIgc3lzdGVtIGltcHJvdmVkIGJ5IGAyOCVgIHdpdGggdGhlIHRlc3QgZGF0YSBhbmQgYDI4JWAgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YSB3aGVuIG1ha2luZyBwcmVkaWN0aW9ucy4NCg0KDQojIyMgU291cmNlcw0KDQpbaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjYyMzc2ODgvcm1zZS1yb290LW1lYW4tc3F1YXJlLWRldmlhdGlvbi1jYWxjdWxhdGlvbi1pbi1yXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNjIzNzY4OC9ybXNlLXJvb3QtbWVhbi1zcXVhcmUtZGV2aWF0aW9uLWNhbGN1bGF0aW9uLWluLXIpDQoNCltodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy81MTQ2NzI3Ni9ob3ctdG8tZmluZC10aGUtY29sdW1uLW1lYW5zLWZvci1hLXNwYXJzZS1tYXRyaXgtZXhjbHVkaW5nLTAtdmFsdWVzXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy81MTQ2NzI3Ni9ob3ctdG8tZmluZC10aGUtY29sdW1uLW1lYW5zLWZvci1hLXNwYXJzZS1tYXRyaXgtZXhjbHVkaW5nLTAtdmFsdWVzKQ0KDQoNCg==