DATA 607 - Assignment 2- Week 2A SQL and R: Code Base submission

Bikash Bhowmik —- 12 Sep 2026

Objective

The goal of this assignment is to collect a small dataset of movie ratings from different users, store the data in a PostgreSQL database, and analyze it using R. This project demonstrates the complete process of collecting data, storing it in a relational database, querying the data, and bringing it into R for analysis. It also shows how missing movie ratings can be handled appropriately.

Data Collection Plan

  • Items (Movies): I will select six recent popular movies (or similar media items) to increase the chance that participants have seen at least some of them.
  • Users (Participants): I will survey at least five people.
  • Rating Scale: Participants will rate any movie they have seen on a 1–5 integer scale.
  • Missing Ratings: If a participant has not seen a movie, the rating will be recorded as missing (NULL) instead of forcing a guess.

Responses will be collected in a simple format (e.g., a small table) and then inserted into PostgreSQL.

Tools / Environment

  • Database: PostgreSQL (local installation)
  • GUI: pgAdmin 4 (for running SQL scripts and verifying tables)
  • R Packages (planned): DBI, RPostgres, tidyverse

Security Note: Database passwords will not be included in the code. Connection credentials will be stored using environment variables or masked placeholders.

Database Design (Normalized Schema)

To keep the solution professional and interview-ready, I will use a normalized relational schema to represent the many-to-many relationship between users and movies.

Planned tables:

  • users(user_id, name)
  • movies(movie_id, title, release_year)
  • ratings(rating_id, user_id, movie_id, rating) `

Keys and Constraints (Planned)

  • users.user_id and movies.movie_id will be primary keys
  • ratings.user_id and ratings.movie_id will be foreign keys
  • Ratings will be constrained to the range 1–5
  • A unique constraint on (user_id, movie_id) will prevent duplicate ratings

This structure mirrors how real recommendation datasets are stored: users and movies are separate entities, and ratings are stored in a junction table.

Missing Data Strategy

Missing ratings are expected because participants may not have seen every movie.

I will handle missing data in two ways:

  1. In SQL: Missing ratings will be represented as NULL (or omitted rows if a participant did not rate an item).
  2. In R: When analyzing results, missing ratings will appear as NA. Summary statistics (means, medians) will be computed with na.rm = TRUE to avoid bias from missingness.

To document missingness, I will report:

  • ratings count per movie (coverage)
  • ratings count per user
  • how many missing ratings exist overall

Analysis Plan in R

After the database is populated, I will connect PostgreSQL to R using DBI and RPostgres. I will then use R to:

-Combine the users, movies, and ratings tables into one tidy dataset.
-Calculate basic summaries, including:
-The average rating for each movie along with the number of ratings.
-The average rating given by each user.
-The overall distribution of ratings from 1 to 5.
-Optionally, convert the data into a user–item matrix in wide format to show how the dataset can be structured for collaborative filtering and recommendation systems. An advanced recommendation model is not required for this assignment.

Reproducibility Plan

Even if I use pgAdmin to run queries, I will include the full SQL scripts required to:

  • create the tables (CREATE TABLE)
  • populate them (INSERT INTO)
  • query them (SELECT ... JOIN ...)

All code (SQL + R + Quarto) will be stored in a GitHub repository for submission, with sensitive credentials removed or masked.

Loading Libraries

library(DBI)
library(RPostgres)
library(dplyr)
library(tidyr)

Connect to PosgreSQL

con <- dbConnect(
  RPostgres::Postgres(),
  host = "localhost",
  port = 5432,
  dbname = "postgres",
  user = "postgres",
  password = "admin"
)

dbListTables(con) 
[1] "movies"  "ratings" "users"  
Load table in R
users_df  <- dbGetQuery(con, "SELECT * FROM users;")
movies_df <- dbGetQuery(con, "SELECT * FROM movies;")
ratings_df <- dbGetQuery(con, "SELECT * FROM ratings;")

Join tables in R

ratings_joined <- ratings_df %>%
  left_join(users_df, by = "user_id") %>%
  left_join(movies_df, by = "movie_id") %>%
  select(name, title, release_year, rating) %>%
  arrange(name, title)

ratings_joined
      name                   title release_year rating
1    Anush Avatar The Way of Water         2022      4
2    Anush           Dune Part Two         2024      4
3    Anush           Lilo & Stitch         2025      4
4    Anush              The Batman         2022      3
5     Arpi                  Barbie         2023      4
6     Arpi           Lilo & Stitch         2025      5
7     Arpi           The Holdovers         2023      5
8      Das Avatar The Way of Water         2022      5
9      Das                  Barbie         2023      3
10     Das           The Holdovers         2023      4
11   Mihir Avatar The Way of Water         2022      5
12   Mihir           Dune Part Two         2024      3
13   Mihir              The Batman         2022      4
14   Mihir           The Holdovers         2023      5
15 Prakash           Dune Part Two         2024      5
16 Prakash           Lilo & Stitch         2025      4
17 Prakash           The Holdovers         2023      5

Demonstrate missing ratings handling (Part 1)

ratings_complete <- users_df %>%
  crossing(movies_df) %>%
  left_join(ratings_df, by = c("user_id", "movie_id"))

ratings_complete
# A tibble: 30 × 7
   user_id name    movie_id title                  release_year rating_id rating
     <int> <chr>      <int> <chr>                         <int>     <int>  <int>
 1       1 Prakash        1 Dune Part Two                  2024         1      5
 2       1 Prakash        2 The Holdovers                  2023         2      5
 3       1 Prakash        3 Barbie                         2023        NA     NA
 4       1 Prakash        4 The Batman                     2022        NA     NA
 5       1 Prakash        5 Lilo & Stitch                  2025         3      4
 6       1 Prakash        6 Avatar The Way of Wat…         2022        NA     NA
 7       2 Das            1 Dune Part Two                  2024        NA     NA
 8       2 Das            2 The Holdovers                  2023         4      4
 9       2 Das            3 Barbie                         2023         5      3
10       2 Das            4 The Batman                     2022        NA     NA
# ℹ 20 more rows

Demonstrate missing ratings handling (Part 2)

ratings_complete %>%
  summarise(
    total_possible = n(),
    missing_ratings = sum(is.na(rating)),
    observed_ratings = sum(!is.na(rating))
  )
# A tibble: 1 × 3
  total_possible missing_ratings observed_ratings
           <int>           <int>            <int>
1             30              13               17

Simple summaries

ratings_complete %>%
  group_by(title) %>%
  summarise(avg_rating = mean(rating, na.rm = TRUE),
            n_ratings = sum(!is.na(rating)))
# A tibble: 6 × 3
  title                   avg_rating n_ratings
  <chr>                        <dbl>     <int>
1 Avatar The Way of Water       4.67         3
2 Barbie                        3.5          2
3 Dune Part Two                 4            3
4 Lilo & Stitch                 4.33         3
5 The Batman                    3.5          2
6 The Holdovers                 4.75         4

Disconnecting Database

dbDisconnect(con)

Conclusion

This assignment demonstrates how relational databases and R can be used together in a data science workflow. PostgreSQL provides structured data storage and helps maintain data integrity through constraints, while R is used for data manipulation, handling missing values, and performing analysis. The final dataset is organized in a user–item ratings format, which is commonly used in recommendation systems.