-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSBV_Assignment2_PostgreSQLtoR.Rmd
More file actions
371 lines (344 loc) · 11.2 KB
/
Copy pathSBV_Assignment2_PostgreSQLtoR.Rmd
File metadata and controls
371 lines (344 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
---
title: 'Assignment #2 - SQL and R'
author: "Stefano Biguzzi"
date: "9/1/2020"
output:
pdf_document: default
html_document:
highlight: pygments
theme: cerulean
toc: true
toc_float: true
editor_options:
chunk_output_type: console
params:
pwd:
label: "Enter the PostgreSQL password please"
value: ""
input: password
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(RPostgreSQL)
library(RPostgres)
library(DBI)
library(tidyverse)
library(knitr)
library(kableExtra)
```
# ETL Process
## Create Connection to Local PostgreSQL database and getting tables
### Creating connection
```{r create-connection}
con <- dbConnect(
RPostgres::Postgres(),
dbname = "MovieRatings",
host="localhost",
port="5432",
user="postgres",
password=params$pwd)
```
### Setting tables to dataframes
**Loading tblPersons and removing any white spaces**
```{r loading-tblPersons}
#Loading person data
person_df <- dbGetQuery(
con,
'Select * From public."tblPersons"'
)
```
```{r clearing-person-ws}
#Clearing white spaces
for (i in names(person_df)) {
person_df[[i]] <- trimws(
person_df[[i]],
which = c("both", "left", "right"),
whitespace = "[ \t\r\n]"
)
}
```
```{r creating-person-table}
#Creating person table
kable(person_df,format = "markdown",caption = "Person Data")
```
**Loading tblMovies and removing any white spaces**
```{r loading-tblMovies}
#Loading movie data
movie_df <- dbGetQuery(
con,
'Select * From public."tblMovies"'
)
```
```{r clearing-movie-ws}
#Clearing movie data white spaces
for (i in names(movie_df)) {
if (is.character(movie_df[[i]])) {
movie_df[[i]] <- trimws(
movie_df[[i]],
which = c("both", "left", "right"),
whitespace = "[ \t\r\n]"
)
}
}
```
```{r creating-movie-table}
#Creating movie data table
kbl(movie_df, caption = "Movie Data", booktabs = T) %>%
kable_styling(
latex_options = c("scale_down", "hold_position")
)
```
\newpage
**Loading tblRatings and removing any white spaces**
```{r loading-tblRatings}
#Loading ratings data
ratings_df <- dbGetQuery(
con,
'Select * From public."tblRatings"'
)
```
```{r clearing-ratings-ws}
#Removing white spaces in rating data
for (i in names(ratings_df)) {
if (is.character(ratings_df[[i]])) {
ratings_df[[i]] <- trimws(
ratings_df[[i]],
which = c("both", "left", "right"),
whitespace = "[ \t\r\n]"
)
}
}
```
```{r creating-ratings-table}
#Creating ratings data table
kable(
ratings_df,
format = "markdown",
caption = "Movie Ratings Raw"
)
```
\newpage
## Dealing with missing data
In the ratings dataframe for the MovieRating column, I decided to create two dataframes, one for seen movies with ratings and one for non seen movies. This can allow us to run different statistical analyses on the different types of movies, seen and unseen.
### Figure out how many nulls there are in the MovieRatings column
```{r counting-nulls}
#Counting the nulls
kable(
table(is.na(ratings_df$MovieRating)),
format = "markdown"
)
```
### Create dataframe for movie ratings that were seen
```{r subsetting-seen-movies}
#Subsetting data
seen_ratings_df <- subset(
ratings_df,!is.na(ratings_df$MovieRating)
)
```
```{r creating-seen-table}
#Creating table of seen movies
rownames(seen_ratings_df) <- NULL
kable(
seen_ratings_df,
format = "markdown",
caption = "Rated Movies"
)
```
### Create dataframe for movies that were not seen
```{r subsetting-not-seen-movies}
#Subsetting movies with no rating
not_seen_df <-
subset(ratings_df,is.na(ratings_df$MovieRating))
```
```{r drop-movie-rating}
#Dropping movie rating
not_seen_df <-
subset(not_seen_df, select = -MovieRating)
```
```{r create-not seen list}
#Create the list of PersonID and MovieID
rownames(not_seen_df) <- NULL
kable(not_seen_df,format = "markdown",caption = "Not Seen Movie List")
```
# Recommendation Analysis
To create a recommendation of a movie for the people that have not seen all six movies, I want to start by grabbing the *not_seen_df* and merge on the movie title and movie genre. Then I want to subset the *seen_ratings_df* to only the people that have a row in the *not_seen_df*. I will then summarize the *seen_ratings_df* to understand the number of movies and the average rating by genre. Finally I will merge the summary data to the *not_seen_df* and find a movies that match genre and the genre has an average rating above 3.5. I will recommend movies based on those criteria.
## Merge the not seen data with movie data
Take the not seen data and merge it with the movie data to get movie title and movie genre. This will help in understanding the genre of the movies that were not watched
```{r not-seen-genre}
#Merging the movie_df with not_seen_df
not_seen_movie_merge_df <- merge(
not_seen_df,
movie_df[,c("MovieID","MovieTitle","Genre")],
by = "MovieID"
)
```
```{r create-unique-not-seen}
#Creating unique list of movies not seen
not_seen_unique_df <- not_seen_movie_merge_df[
row.names(unique(
not_seen_movie_merge_df[,c(
"MovieTitle", "Genre"
)]
)),
c("MovieTitle","Genre")
]
```
```{r creating-not-seen-merge-table}
#Creating the table for not seen movie merge table
rownames(not_seen_unique_df) <- NULL
kable(
not_seen_unique_df,
format = "markdown",
caption = "List of not seen movies"
)
```
## Subset the seen movie ratings
Subsetting the *seen_ratings_df* to only people that are in the *not_seen_df*
```{r subset-seen-movies}
#Subsetting seen movies to only people who are also in the not seen movie list
person_to_recommend_df <-
subset(
seen_ratings_df,
PersonID %in% not_seen_df$PersonID)
```
```{r create-person-to-recommend-table}
#Creating the table of ratings for people who have not seen all movies
rownames(person_to_recommend_df) <- NULL
kable(
person_to_recommend_df,
format = "markdown",
caption = "Ratings for movies of people who show up in not seen list"
)
```
## Merging movie data to subsetted ratings data frame
Adding column movie title and genre from *movie_df* to the new data frame *person_to_recommend_df*
```{r adding-movie-info}
#Adding movie info to table of ratings for people who have not seen all movies
person_to_recommend_df <- merge(
merge(
person_to_recommend_df,
movie_df[,c("MovieID","MovieTitle","Genre")],
by = "MovieID"
),
person_df[,c("PersonID","FirstName")],
by = "PersonID"
)
```
```{r creating-person-to-recommend-table2}
#Creating rating table for people who watched less than 6 movies
kable(
person_to_recommend_df[c("FirstName","MovieTitle","Genre","MovieRating")],
format = "markdown",
caption = "Person and movie ratings for people who watched < 6 movies"
)
```
## Creating summary statistics table
Summarizing the subsetted dataframe, *person_to_recommend_df*. Finding out the number of movies and the avg rating per person and genre
```{r create-number-of-movies}
#Creating number of movies seen per genre
seen_summary_df <- data.frame(
person_to_recommend_df %>%
group_by(PersonID,FirstName) %>%
count(Genre)
)
```
```{r renaming-n-to-numbermovieseen}
#Renaming the count column
names(seen_summary_df)[names(seen_summary_df) == "n"] <- "NumberMoviesSeen"
```
```{r creating-avg-rating-by-person-genre}
#Creating the average rating by person and genre
seen_rating_average_df <- data.frame(
person_to_recommend_df %>%
group_by(PersonID,Genre) %>%
summarize_at(vars(MovieRating),mean)
)
```
```{r creating-final-seen-summary}
#Creating the final version of seen summary dataframe
seen_summary_df <- merge(
seen_summary_df,
seen_rating_average_df,
by = c("PersonID","Genre")
)
```
```{r renaming-movierating-avg-genre-rating}
#Renaming the movie rating column to average genre rating
names(seen_summary_df)[names(seen_summary_df) == "MovieRating"] <- "AverageGenreRating"
```
```{r creating-seen-summary-table}
#Creating table of seen summary dataframe
kable(
seen_summary_df[,c(
"FirstName",
"Genre",
"NumberMoviesSeen",
"AverageGenreRating"
)],
format = "markdown",
caption = "Summary stats for people with < 6 watched movies"
)
```
## Creating recommended movie list
The final stage is to create a recommended movie list for people who have not seen all the six movies.
### Create recommended list
This list includes movies in genres that were low rated by person. The next step would be to subset this data to include only the movies that are part of a genre above a 3.5 mean rating.
```{r recommend-df}
#Creating the recommend dataframe
recommend_df <- merge(
not_seen_movie_merge_df,
seen_summary_df,
by = c("PersonID","Genre")
)
```
\newpage
```{r creating-recommend-df-table}
#Creating table to show list of movies not seen and average genre rating
kbl(
recommend_df[c("FirstName","MovieTitle","Genre","AverageGenreRating")],
booktabs=T,
caption = "List movies not seen with average genre rating"
) %>% kable_styling(
latex_options=c("scale_down","hold_position")
)
```
### Subset full recommended list to higher rated genres
```{r subset-recommended}
#Subsetting recommend table to only pull movies with genre ratings above 3.5
final_df <- subset(recommend_df,AverageGenreRating >= 3.5)
```
```{r reordering-columns}
#Reordering columns for readability
col_order <- c("FirstName","MovieTitle","Genre","AverageGenreRating")
final_df <- final_df[,col_order]
```
```{r creating-full-list-recommend-movies}
#Creating full list of movies to recommend
rownames(final_df) <- NULL
kable(
final_df,
format = "markdown",
caption = "full list of movies with highest genre rating to recommend"
)
```
# Conclusion
```{r final-table}
#Subsetting final list to only one movie per person
final_table <- final_df %>%
group_by(FirstName) %>%
top_n(1, AverageGenreRating)
```
```{r create-final-table}
#Creating final recommend list with one movie per person
rownames(final_table) <- NULL
kable(
final_table,
format = "markdown",
col.names = c("First Name","Movie","Genre","Average Genre Rating"),
caption = "Final List of Recommended Movies",
align = "lllc"
)
```
Based on *Table 13: Final List of Recommended Movies*, I would have to recommend 1917 to Ashley, Anna, Samantha, and Daniella, while recommending Avengers: Endgame to Dana. This conclusion,however, should be taken with a grain of salt as there is not enough data to make an accurate recommendation.
This accuracy issue could be remedied by collecting more ratings and more information about each movie. First, I would like to collect more movie ratings expanding the genre pool and making the genre rating averages more robust. Secondly, I would love to add additional data for each movie. Some examples include, the gender of the lead actor, release date (to group by month of release), and Oscar nominations and awards won.
Creating a larger dataset with more descriptive information about the movies and expanding the available genres of movies can help create a better movie recommendation list for each survey participant.