Load packages

Step 1: Exploratory Analysis

Load data

First, we need to start by loading the data.

# Location of the folder with movies & ratings data 
setwd("C:/Users/manon/Desktop/Machine Learning for Big Data/Project/Part 1/ml-25m")

list.files()
## [1] "genome-scores.csv" "genome-tags.csv"   "links.csv"        
## [4] "movies.csv"        "ratings.csv"       "README.txt"       
## [7] "tags.csv"
rm(list = ls())

movieData <- fread("movies.csv",
                   stringsAsFactors=FALSE)
ratingData <- fread("ratings.csv")
pca <- readRDS("C:/Users/manon/Desktop/Machine Learning for Big Data/Project/Part 3/features_fromPart2.RDS")

ICE

Any project starts by Inspecting, Clean and Explore the data.

Inspect

# General informations
glimpse(movieData)
## Rows: 62,423
## Columns: 3
## $ movieId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,…
## $ title   <chr> "Toy Story (1995)", "Jumanji (1995)", "Grumpier Old Men (1995)…
## $ genres  <chr> "Adventure|Animation|Children|Comedy|Fantasy", "Adventure|Chil…
glimpse(ratingData)
## Rows: 25,000,095
## Columns: 4
## $ userId    <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ movieId   <int> 296, 306, 307, 665, 899, 1088, 1175, 1217, 1237, 1250, 1260,…
## $ rating    <dbl> 5.0, 3.5, 5.0, 5.0, 3.5, 4.0, 3.5, 3.5, 5.0, 4.0, 3.5, 4.0, …
## $ timestamp <int> 1147880044, 1147868817, 1147868828, 1147878820, 1147868510, …
glimpse(pca)
## Rows: 2,918
## Columns: 11
## $ PC1     <dbl> 4.405565, -5.453456, 12.072521, -11.474774, -10.320106, -3.893…
## $ PC2     <dbl> -8.0978468, -6.7777428, -0.8754869, -1.3173368, -2.4163792, 2.…
## $ PC3     <dbl> -15.2302310, 5.9443722, -2.5236028, 3.3639157, 3.7201249, -1.6…
## $ PC4     <dbl> -4.0909960, -1.5250008, 4.8227333, -2.6441390, -4.4611251, 1.7…
## $ PC5     <dbl> -4.6314681, 3.1432240, -10.1164793, 0.8888057, 2.4626307, -6.2…
## $ PC6     <dbl> -11.74459909, 2.29172908, -17.93452170, 0.06926521, -0.9861108…
## $ PC7     <dbl> -3.88099042, -1.32644387, -5.56217945, 0.20706439, 0.71664837,…
## $ PC8     <dbl> 31.3827211, 22.5147910, -3.0407079, 0.3183160, -0.9909675, -0.…
## $ PC9     <dbl> -14.6718654, -7.2217313, 16.4121592, -0.9630123, 1.6145418, 1.…
## $ PC10    <dbl> 33.83429251, 15.25659315, -13.18770058, -1.67657126, -1.239857…
## $ movieId <dbl> 1, 2, 6, 7, 10, 16, 17, 18, 19, 21, 22, 25, 28, 29, 31, 32, 34…
summary(movieData)
##     movieId          title              genres         
##  Min.   :     1   Length:62423       Length:62423      
##  1st Qu.: 82147   Class :character   Class :character  
##  Median :138022   Mode  :character   Mode  :character  
##  Mean   :122220                                        
##  3rd Qu.:173222                                        
##  Max.   :209171
summary(ratingData)
##      userId          movieId           rating        timestamp        
##  Min.   :     1   Min.   :     1   Min.   :0.500   Min.   :7.897e+08  
##  1st Qu.: 40510   1st Qu.:  1196   1st Qu.:3.000   1st Qu.:1.012e+09  
##  Median : 80914   Median :  2947   Median :3.500   Median :1.199e+09  
##  Mean   : 81189   Mean   : 21388   Mean   :3.534   Mean   :1.216e+09  
##  3rd Qu.:121557   3rd Qu.:  8623   3rd Qu.:4.000   3rd Qu.:1.447e+09  
##  Max.   :162541   Max.   :209171   Max.   :5.000   Max.   :1.574e+09
summary(pca)
##       PC1               PC2                  PC3                 PC4           
##  Min.   :-12.926   Min.   :-1099.0722   Min.   :-621.0262   Min.   :-355.1322  
##  1st Qu.:-11.279   1st Qu.:   -2.2682   1st Qu.:   0.1699   1st Qu.:  -4.3200  
##  Median : -9.095   Median :   -1.1062   Median :   3.2190   Median :  -2.9464  
##  Mean   :  0.000   Mean   :    0.0000   Mean   :   0.0000   Mean   :   0.0000  
##  3rd Qu.: -2.785   3rd Qu.:    0.4688   3rd Qu.:   4.1297   3rd Qu.:  -0.1223  
##  Max.   :966.720   Max.   :  517.1730   Max.   : 244.0335   Max.   : 305.5415  
##       PC5                 PC6                 PC7                PC8           
##  Min.   :-278.7495   Min.   :-148.8555   Min.   :-197.595   Min.   :-123.5152  
##  1st Qu.:  -0.6049   1st Qu.:  -2.2304   1st Qu.:  -1.622   1st Qu.:  -2.4139  
##  Median :   2.3830   Median :  -0.6886   Median :   1.040   Median :  -0.5502  
##  Mean   :   0.0000   Mean   :   0.0000   Mean   :   0.000   Mean   :   0.0000  
##  3rd Qu.:   3.6631   3rd Qu.:   1.1412   3rd Qu.:   2.364   3rd Qu.:   1.3372  
##  Max.   : 177.1198   Max.   : 167.1718   Max.   : 240.501   Max.   : 228.0468  
##       PC9                PC10              movieId      
##  Min.   :-135.175   Min.   :-163.0232   Min.   :     1  
##  1st Qu.:  -1.171   1st Qu.:  -2.3193   1st Qu.:  2786  
##  Median :   0.850   Median :  -0.7239   Median :  8984  
##  Mean   :   0.000   Mean   :   0.0000   Mean   : 47000  
##  3rd Qu.:   2.111   3rd Qu.:   1.7803   3rd Qu.: 81556  
##  Max.   : 215.274   Max.   : 142.5005   Max.   :204698

Clean

cat("There are:", "\n",
    length(unique(ratingData$userId)), "unique userIds in ratingData", "\n",
    length(unique(ratingData$movieId)), "unique movieIds in ratingData", "\n",
    length(unique(movieData$movieId)), "unique movieIds in movieData", "\n",
    length(unique(movieData$title)), "unique titles in movieData")
## There are: 
##  162541 unique userIds in ratingData 
##  59047 unique movieIds in ratingData 
##  62423 unique movieIds in movieData 
##  62325 unique titles in movieData

There are different lengths of unique movieId in the movieData and ratingData. Therefore, we need to clean and remove duplicates.

# Look for any movie title duplicates
repeatMovies <- names(which(table(movieData$title) > 1))
removeRows <- integer()

# Check, remove and store all duplicates in a vector
for(i in repeatMovies){
  repeatMovieLoc <- which(movieData$title == i)
  tempGenre <- paste(movieData$genres[repeatMovieLoc],
                     collapse="|")
  tempGenre <- paste(unique(unlist(strsplit(tempGenre,
                                            split = "\\|")[[1]])),
                     collapse = "|")
  movieData$genres[repeatMovieLoc[1]] <- tempGenre

  repeatMovieIdLoc <- which(ratingData$movieId %in% movieData$movieId[repeatMovieLoc[-1]])
  ratingData$movieId[repeatMovieIdLoc] <- movieData$movieId[repeatMovieLoc[1]]
  removeRows <- c(removeRows,
                  repeatMovieLoc[-1])}

movieData$movieId[removeRows]
##  [1] 204982 151797 114240 198719 207249 144450 156686 181675 184893 203649
## [11] 198947 115777 191775 138656  85070 200376 199035 206674 193559 128862
## [21]  67459 171555 122940 144426 192405 144606 192243 196353 168634 194845
## [31] 144440 181655 180205 174651 143703 202599 147002 160868 180465 175857
## [41] 198507 163206 168866 118818 185925 168088 136820  65665 194570 140890
## [51] 143978 199556 124757 199346 144748 164568 192003 181751 191867 172427
## [61] 206125 181329 150310  26982 191713 164667 197425 121586 205761 200914
## [71] 180029 168358 199916 163246 160356 194652 169530 151375 154943 194078
## [81] 150776 193447 190881 190241 158763 138870 178403 206117 183459 206925
## [91] 173729 150732 144830 174449 148482 179333  64997 181385
movieData <- movieData[-removeRows,]
movieData[movieData$title == repeatMovies[1],]
##    movieId    title                            genres
## 1:   71057 9 (2009) Adventure|Animation|Sci-Fi|Comedy
movieData[movieData$title == repeatMovies[2],]
##    movieId             title                                genres
## 1:  136696 Absolution (2015) Action|Adventure|Crime|Thriller|Drama
# Let's remove non useful variables to increase the speed
rm(i,
   removeRows,
   repeatMovieIdLoc,
   repeatMovieLoc,
   repeatMovies,
   tempGenre)

# Take best rating if a userId rated a movie multiple times
ratingData_dt <- as.data.table(ratingData)
ratingData <- ratingData_dt[,
                            .(rating = max(rating)),
                            by = .(userId, movieId)]

uniqueN(ratingData,
        by = "movieId")
## [1] 58958
uniqueN(movieData,
        by = "movieId")
## [1] 62325
moviesNotInRatingData <- setdiff(unique(movieData[,
                                                  movieId]),
                                 unique(ratingData[,
                                                   movieId]))
rm(ratingData_dt)
# Check that the data is cleaned
str(movieData)
## Classes 'data.table' and 'data.frame':   62325 obs. of  3 variables:
##  $ movieId: int  1 2 3 4 5 6 7 8 9 10 ...
##  $ title  : chr  "Toy Story (1995)" "Jumanji (1995)" "Grumpier Old Men (1995)" "Waiting to Exhale (1995)" ...
##  $ genres : chr  "Adventure|Animation|Children|Comedy|Fantasy" "Adventure|Children|Fantasy" "Comedy|Romance" "Comedy|Drama|Romance" ...
##  - attr(*, ".internal.selfref")=<externalptr>
summary(movieData)    
##     movieId          title              genres         
##  Min.   :     1   Length:62325       Length:62325      
##  1st Qu.: 82061   Class :character   Class :character  
##  Median :137938   Mode  :character   Mode  :character  
##  Mean   :122150                                        
##  3rd Qu.:173169                                        
##  Max.   :209171
head(movieData)
##    movieId                              title
## 1:       1                   Toy Story (1995)
## 2:       2                     Jumanji (1995)
## 3:       3            Grumpier Old Men (1995)
## 4:       4           Waiting to Exhale (1995)
## 5:       5 Father of the Bride Part II (1995)
## 6:       6                        Heat (1995)
##                                         genres
## 1: Adventure|Animation|Children|Comedy|Fantasy
## 2:                  Adventure|Children|Fantasy
## 3:                              Comedy|Romance
## 4:                        Comedy|Drama|Romance
## 5:                                      Comedy
## 6:                       Action|Crime|Thriller
summary(ratingData)   
##      userId          movieId           rating     
##  Min.   :     1   Min.   :     1   Min.   :0.500  
##  1st Qu.: 40510   1st Qu.:  1196   1st Qu.:3.000  
##  Median : 80914   Median :  2947   Median :3.500  
##  Mean   : 81189   Mean   : 21384   Mean   :3.534  
##  3rd Qu.:121557   3rd Qu.:  8623   3rd Qu.:4.000  
##  Max.   :162541   Max.   :209171   Max.   :5.000
head(ratingData)
##    userId movieId rating
## 1:      1     296    5.0
## 2:      1     306    3.5
## 3:      1     307    5.0
## 4:      1     665    5.0
## 5:      1     899    3.5
## 6:      1    1088    4.0
# Check that there are no more duplicates
length(movieData) == length(unique(movieData))
## [1] TRUE
length(ratingData) == length(unique(ratingData))
## [1] TRUE
# Create genre  vector
genres <- c("Adventure",
            "Animation",
            "Children",
            "Comedy",
            "Fantasy",
            "Romance",
            "Drama",
            "Action",
            "Crime",
            "Thriller",
            "Horror",
            "Mystery",
            "Sci-Fi",
            "IMAX",
            "Documentary",
            "War",
            "Musical",
            "Western",
            "Film-Noir",
            "(no genres listed)")

# Ducplicate the movieData to not impact it
movie_dummy <- movieData

# Create a loop that creates a new column and a binary variable
for (i in genres) {movie_dummy[,
                               paste0(i)] <- as.integer(grepl(i,
                                                              movie_dummy$genres,
                                                              fixed = TRUE))}

# Remove genres column
movie_dummy <- movie_dummy %>% 
  select(-genres)

# Remove any rows with missing movieId
movie_dummy <- movie_dummy[complete.cases(movie_dummy),]
# Calculate average ratings
ratingData <- ratingData %>%
  group_by(movieId) %>%
  summarise(avg_rating = mean(rating))

# We're using inner join so we only keep the movies common to all datasets, allowing us to reduce the size of the file
joined_dataset <- movie_dummy %>%
  inner_join(pca, by = "movieId") %>%
  inner_join(ratingData,
             by = "movieId") %>% 
  select(-title)

Step 2: First task

# Create a binary variable 'excellent'
joined_dataset$excellent <- as.integer(joined_dataset$avg_rating > 3.75)

# We split our dataset into training and test set of respectively 80% and 20%
set.seed(123)  # for reproducibility

data_split <- initial_split(joined_dataset,
                            prop = 0.8)
data_train <- training(data_split)
data_test  <- testing(data_split)

Build the SVM model

# Different models
kernel <- c("linear",
            "radial",
            "sigmoid")

# Penaliser coefficient
cost <- c(1, 5, 10, 100, 200)

# Create empty data frame
resume <- data.frame(kernel = character(),
                     cost = numeric(),
                     rmse = numeric(),
                     stringsAsFactors = FALSE)

columns = c("kernel",
            "cost",
            "rmse") 
colnames(resume) = columns

# Create a loop to try every possible outcome
for (k in kernel) {
  for (c in cost) {
    svm <- svm(excellent ~ .,
               data = data_train,
               kernel = k,
               cost = c)
    predictions <- predict(svm,
                           newdata = data_test)
    confusion_matrix <- table(data_test$excellent,
                              predictions)
    rmse <- sqrt(mean((data_test$excellent - predictions)^2))
    resume <- rbind(resume,
                    data.frame(kernel = k,
                               cost = c,
                               rmse = rmse))}}

# Show table
resume
##     kernel cost        rmse
## 1   linear    1   0.3203252
## 2   linear    5   0.3203188
## 3   linear   10   0.3202594
## 4   linear  100   0.3212630
## 5   linear  200   0.3765114
## 6   radial    1   0.2538708
## 7   radial    5   0.2503479
## 8   radial   10   0.2563482
## 9   radial  100   0.2874427
## 10  radial  200   0.3109583
## 11 sigmoid    1   1.8565635
## 12 sigmoid    5   9.4852946
## 13 sigmoid   10  19.3785039
## 14 sigmoid  100 217.0570725
## 15 sigmoid  200 391.2683867

From this table, we can see that we should take the model with the lowest rmse, Radial model with cost of 5 has the best rmse, meaning that this model is the most accurate at predicting the outcome variable compared to the other models with different cost parameters. Radial can capture complex nonlinear relationships compared to linear models, making it more accurate. The rmse is still a little high, so we know there is no risk of overfitting for the radial models. We notice that all sigmoid models have a really high rmse. It is known that sigmoid can be sensitive to the choice of parameters, which can lead to overfitting the data or underfitting it. It is probably what happened in our case.

Change rating to 4

# Remove the column from task 1
joined_dataset <- joined_dataset %>%
  select(-excellent)

# Create a binary variable 'excellent'
joined_dataset$excellent <- as.integer(joined_dataset$avg_rating > 4)

# We split our dataset into training and test set of respectively 80% and 20%
set.seed(123)  # for reproducibility

data_split_4 <- initial_split(joined_dataset,
                              prop = 0.8)
data_train_4 <- training(data_split_4)
data_test_4  <- testing(data_split_4)

Build the second SVM model

# Create empty data frame
resume_4 <- data.frame(kernel = character(),
                     cost = numeric(),
                     rmse = numeric(),
                     stringsAsFactors = FALSE)

colnames(resume_4) = columns

# Create a loop to try every possible outcome
for (k in kernel) {
  for (c in cost) {
    svm_4 <- svm(excellent ~ .,
               data = data_train_4,
               kernel = k,
               cost = c)
    predictions_4 <- predict(svm_4,
                           newdata = data_test_4)
    confusion_matrix_4 <- table(data_test_4$excellent,
                              predictions_4)
    rmse_4 <- sqrt(mean((data_test_4$excellent - predictions_4)^2))
    resume_4 <- rbind(resume_4,
                    data.frame(kernel = k,
                               cost = c,
                               rmse = rmse_4))}}

# Show table
resume_4
##     kernel cost        rmse
## 1   linear    1   0.2389090
## 2   linear    5   0.2389291
## 3   linear   10   0.2389295
## 4   linear  100   0.2415162
## 5   linear  200   0.2452912
## 6   radial    1   0.2177605
## 7   radial    5   0.2037364
## 8   radial   10   0.2017594
## 9   radial  100   0.2118804
## 10  radial  200   0.2246179
## 11 sigmoid    1   1.1727413
## 12 sigmoid    5   5.6132275
## 13 sigmoid   10  11.2783398
## 14 sigmoid  100 119.1950757
## 15 sigmoid  200 251.9456918

Compared to the previous table, the best model is still radial but with a cost of 10 instead of 5. The RMSE passed from 0.2503479 to 0.2017594, which is a significant decrease; it indicates that the model’s predictions are closer to the actual values. The second model uses a higher classification threshold, meaning fewer observations are considered ‘excellent’. This could result in a smaller number of positive cases. However, it’s also important to consider possible overfitting when evaluating the performances.

Build logistic regression model

# logistic regression model
glm_model <- glm(excellent ~ .,
               data = data_train,
               family = "binomial")
predi <- predict(glm_model, newdata = data_test, type = "response")
t <- 0.5


predi <- ifelse(predi >= t, 1, 0)

confusion_matrix_glm <- table(data_test$excellent,
                              predi)
rmse <- sqrt(mean((data_test$excellent - predi)^2))

confusion_matrix_glm
##    predi
##       0   1
##   0 420   4
##   1   2 158
rmse
## [1] 0.1013606

We can compare our two RMSEs for the logistic regression and the one for SVM. Our best model for SVM has an RMSE of 0.2503479, whereas the LM has an RMSE of 0.1013606. This is significantly smaller, meaning the logistic regression will make fewer errors. Therefore the logistic regression model is a better model to predict if a movie will get an excellent rating.

Step 2: Second task

# Remove the column from task 1
joined_dataset <- joined_dataset %>%
  select(-excellent)

# We split the original dataset into training and test set of respectively 80% and 20%
set.seed(123)  # for reproducibility

split_data <- initial_split(joined_dataset,
                            prop = 0.8)
train_data <- training(split_data)
test_data  <- testing(split_data)

Preprocess/Normalize the Data

# Create recipe
norm_obj <- recipe(avg_rating ~ .,
                   data = train_data) %>%
  step_sqrt(avg_rating) %>%
  step_center(avg_rating) %>%
  step_scale(avg_rating) %>%
  prep(data = train_data)

# Print the recipe object
norm_obj
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor         31
## 
## Training data contained 2334 data points and no missing data.
## 
## Operations:
## 
## Square root transformation on avg_rating [trained]
## Centering for avg_rating [trained]
## Scaling for avg_rating [trained]
train_normalized <- bake(norm_obj,
                         train_data)

Build the NN model

# Building our Artificial Neural Network
ann_model <- keras_model_sequential() %>%
# First hidden layer
  layer_dense(units = 35,
              activation = "relu",
              input_shape = ncol(train_normalized)) %>%
# Dropout to prevent overfitting
  layer_dropout(rate = 0.2) %>%
# Second hidden layer
  layer_dense(units = 16,
              activation = "relu") %>%
# Dropout to prevent overfitting
  layer_dropout(rate = 0.2) %>%
# Third hidden layer
  layer_dense(units = 8,
              activation = "relu") %>%
# Dropout to prevent overfitting
  layer_dropout(rate = 0.2) %>%
# Output layer
  layer_dense(units = 1,
              activation = "sigmoid")

# Compile NN
ann_model %>% compile(
  loss = "mean_squared_error",
  optimizer = 'adam',
  metrics = c("mean_absolute_error"))

# Display model architecture
ann_model
## Model: "sequential"
## ________________________________________________________________________________
##  Layer (type)                       Output Shape                    Param #     
## ================================================================================
##  dense_3 (Dense)                    (None, 35)                      1155        
##  dropout_2 (Dropout)                (None, 35)                      0           
##  dense_2 (Dense)                    (None, 16)                      576         
##  dropout_1 (Dropout)                (None, 16)                      0           
##  dense_1 (Dense)                    (None, 8)                       136         
##  dropout (Dropout)                  (None, 8)                       0           
##  dense (Dense)                      (None, 1)                       9           
## ================================================================================
## Total params: 1,876
## Trainable params: 1,876
## Non-trainable params: 0
## ________________________________________________________________________________
set.seed(123)  # for reproducibility

# Train model
history <- fit(
  object = ann_model, 
  x = as.matrix(train_normalized), 
  y = train_normalized$avg_rating,
  batch_size = 140, # How many values are taken per iterations
  epochs = 100, # Number of iterations
  validation_split = 0.30 #to include 30% of the data for model validation, which prevents overfitting.
)

We decided to include the mean absolute error as the primary metric to compare the ANN and linear models. We notice that after 100 iterations, the mean absolute error does not evolve anymore. Additionally, the val loss is constant and under 1, which is a good sign as we are not overfitting the data.

Build the LM

# linear model
lm_model <- lm(avg_rating ~ .-Adventure -PC6 -PC9,
               data = train_data)
summary(lm_model)
## 
## Call:
## lm(formula = avg_rating ~ . - Adventure - PC6 - PC9, data = train_data)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.94037 -0.16831  0.03523  0.22000  0.82248 
## 
## Coefficients:
##                        Estimate Std. Error t value Pr(>|t|)    
## (Intercept)           3.607e+00  2.188e-02 164.803  < 2e-16 ***
## movieId              -4.243e-07  1.300e-07  -3.263 0.001120 ** 
## Animation             2.301e-01  3.611e-02   6.371 2.26e-10 ***
## Children             -1.142e-01  3.713e-02  -3.076 0.002125 ** 
## Comedy               -1.191e-01  1.811e-02  -6.578 5.89e-11 ***
## Fantasy              -1.271e-01  2.417e-02  -5.261 1.57e-07 ***
## Romance              -2.780e-02  1.953e-02  -1.423 0.154751    
## Drama                 1.175e-01  1.723e-02   6.821 1.15e-11 ***
## Action               -1.221e-01  1.890e-02  -6.460 1.27e-10 ***
## Crime                 5.126e-02  2.149e-02   2.385 0.017153 *  
## Thriller             -7.507e-02  1.815e-02  -4.137 3.65e-05 ***
## Horror               -1.514e-01  2.446e-02  -6.190 7.10e-10 ***
## Mystery               4.858e-02  2.647e-02   1.836 0.066531 .  
## `Sci-Fi`             -1.191e-01  2.193e-02  -5.432 6.16e-08 ***
## IMAX                 -1.098e-01  3.582e-02  -3.066 0.002195 ** 
## Documentary           1.763e-01  5.551e-02   3.175 0.001516 ** 
## War                   7.991e-02  3.286e-02   2.432 0.015111 *  
## Musical               3.122e-02  3.998e-02   0.781 0.434976    
## Western               1.095e-01  5.747e-02   1.906 0.056771 .  
## `Film-Noir`           2.706e-01  6.684e-02   4.049 5.32e-05 ***
## `(no genres listed)`  8.035e-02  1.662e-01   0.484 0.628745    
## PC1                   2.658e-03  1.803e-04  14.744  < 2e-16 ***
## PC2                   2.314e-04  2.138e-04   1.082 0.279369    
## PC3                  -1.404e-03  2.649e-04  -5.301 1.26e-07 ***
## PC4                   2.189e-03  3.549e-04   6.167 8.17e-10 ***
## PC5                  -1.359e-03  3.885e-04  -3.499 0.000477 ***
## PC7                  -9.226e-04  4.541e-04  -2.032 0.042308 *  
## PC8                   1.761e-03  5.183e-04   3.397 0.000693 ***
## PC10                  9.931e-04  5.308e-04   1.871 0.061500 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.3286 on 2305 degrees of freedom
## Multiple R-squared:  0.3019, Adjusted R-squared:  0.2934 
## F-statistic:  35.6 on 28 and 2305 DF,  p-value: < 2.2e-16

We have removed all the irrelevant variables, and the p-value is lower than 5% so that we can keep the model. We have tried using log and poly to increase the rsquared, but the changes were minimal. The artificial neural network model has a mean absolute error of 0.7475 for the validation set. On the other hand, the linear regression model has a residual standard error of 0.3286 and an adjusted R-squared of 0.2934, meaning that the model explains about 30% of the variance. Therefore the linear model will make fewer errors when predicting a movie rating.