Data Analysis Final Paper

Satya Sankeerth

2025-12-04

Issue Description

Pixar films are known for both artistic quality and strong audience reception. This project uses the TidyTuesday Pixar data to examine how film characteristics (such as runtime and release date) relate to public and critic ratings.

By combining information on film metadata (pixar_films) and review scores (public_response), I explore whether longer films tend to receive higher ratings, how ratings have changed over time, and whether critics and audiences agree in their evaluation of Pixar movies.

Questions

Is there a relationship between film runtime and ratings (Rotten Tomatoes, Metacritic, Critics Choice)?

Have Pixar film ratings changed over the years (are newer films rated higher or lower)?

How strongly do different rating sources (Rotten Tomatoes, Metacritic, Critics Choice) agree with each other?

Which Pixar films are the overall “top-rated” when combining multiple rating sources?

Data Source

The data are from TidyTuesday week of 2025-03-11, which in turn uses the open pixarfilms R data package containing multiple Pixar-related tables.

Key CSVs used:

pixar_films.csv – film metadata (title, release date, runtime, MPAA rating)

public_response.csv – public and critic ratings for each film

#Load Packages

library(tidytuesdayR)
## Warning: package 'tidytuesdayR' was built under R version 4.5.2
library(readr)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(ggplot2)
library(tidyr)

#Load data

tuesdata <- tidytuesdayR::tt_load('2025-03-11')
## ---- Compiling #TidyTuesday Information for 2025-03-11 ----
## --- There are 2 files available ---
## 
## 
## ── Downloading files ───────────────────────────────────────────────────────────
## 
##   1 of 2: "pixar_films.csv"
##   2 of 2: "public_response.csv"
pixar_films     <- tuesdata$pixar_films
public_response <- tuesdata$public_response

Documentation

The Pixar data are described in:

The TidyTuesday readme for 2025-03-11 (Pixar films week). GitHub

The pixarfilms R package documentation on CRAN, which includes a description of each table and a small data dictionary.

Important variables: From pixar_films: number – order of release film – film title release_date – date premiered run_time – runtime in minutes film_rating – MPAA/BBFC rating (e.g., “G”, “PG”) From public_response: film – film title (join key) rotten_tomatoes – Rotten Tomatoes score (0–100) metacritic – Metacritic score (0–100) cinema_score – CinemaScore letter grade (A–F) critics_choice – Critics Choice score (0–100)

Description of the Data

Use the tools in R such as str() and summary() to describe the original dataset you imported.

str(pixar_films)
## spc_tbl_ [27 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ number      : num [1:27] 1 2 3 4 5 6 7 8 9 10 ...
##  $ film        : chr [1:27] "Toy Story" "A Bug's Life" "Toy Story 2" "Monsters, Inc." ...
##  $ release_date: Date[1:27], format: "1995-11-22" "1998-11-25" ...
##  $ run_time    : num [1:27] 81 95 92 92 100 115 117 111 98 96 ...
##  $ film_rating : chr [1:27] "G" "G" "G" "G" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   number = col_double(),
##   ..   film = col_character(),
##   ..   release_date = col_date(format = ""),
##   ..   run_time = col_double(),
##   ..   film_rating = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>
summary(pixar_films)
##      number         film            release_date           run_time    
##  Min.   : 1.0   Length:27          Min.   :1995-11-22   Min.   : 81.0  
##  1st Qu.: 7.5   Class :character   1st Qu.:2006-12-18   1st Qu.: 95.0  
##  Median :14.0   Mode  :character   Median :2013-06-21   Median :100.0  
##  Mean   :14.0                      Mean   :2012-06-15   Mean   :104.8  
##  3rd Qu.:20.5                      3rd Qu.:2018-12-17   3rd Qu.:106.0  
##  Max.   :27.0                      Max.   :2023-06-16   Max.   :155.0  
##                                                         NA's   :2      
##  film_rating       
##  Length:27         
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 
str(public_response)
## spc_tbl_ [24 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ film           : chr [1:24] "Toy Story" "A Bug's Life" "Toy Story 2" "Monsters, Inc." ...
##  $ rotten_tomatoes: num [1:24] 100 92 100 96 99 97 74 96 95 98 ...
##  $ metacritic     : num [1:24] 95 77 88 79 90 90 73 96 95 88 ...
##  $ cinema_score   : chr [1:24] "A" "A" "A+" "A+" ...
##  $ critics_choice : num [1:24] NA NA 100 92 97 88 89 91 90 95 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   film = col_character(),
##   ..   rotten_tomatoes = col_double(),
##   ..   metacritic = col_double(),
##   ..   cinema_score = col_character(),
##   ..   critics_choice = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
summary(public_response)
##      film           rotten_tomatoes    metacritic    cinema_score      
##  Length:24          Min.   : 40.00   Min.   :57.00   Length:24         
##  Class :character   1st Qu.: 84.00   1st Qu.:71.00   Class :character  
##  Mode  :character   Median : 96.00   Median :81.00   Mode  :character  
##                     Mean   : 89.17   Mean   :79.96                     
##                     3rd Qu.: 97.50   3rd Qu.:90.00                     
##                     Max.   :100.00   Max.   :96.00                     
##                     NA's   :1        NA's   :1                         
##  critics_choice  
##  Min.   : 66.00  
##  1st Qu.: 81.00  
##  Median : 89.00  
##  Mean   : 87.14  
##  3rd Qu.: 93.00  
##  Max.   :100.00  
##  NA's   :3

To analyze ratings together with film metadata, I join the two tables on film:

pixar <- pixar_films |>
  inner_join(public_response, by = "film") |>
  mutate(
    release_year = year(release_date),
    avg_score = (rotten_tomatoes + metacritic + critics_choice) / 3
  )

str(pixar)
## tibble [24 × 11] (S3: tbl_df/tbl/data.frame)
##  $ number         : num [1:24] 1 2 3 4 5 6 7 8 9 10 ...
##  $ film           : chr [1:24] "Toy Story" "A Bug's Life" "Toy Story 2" "Monsters, Inc." ...
##  $ release_date   : Date[1:24], format: "1995-11-22" "1998-11-25" ...
##  $ run_time       : num [1:24] 81 95 92 92 100 115 117 111 98 96 ...
##  $ film_rating    : chr [1:24] "G" "G" "G" "G" ...
##  $ rotten_tomatoes: num [1:24] 100 92 100 96 99 97 74 96 95 98 ...
##  $ metacritic     : num [1:24] 95 77 88 79 90 90 73 96 95 88 ...
##  $ cinema_score   : chr [1:24] "A" "A" "A+" "A+" ...
##  $ critics_choice : num [1:24] NA NA 100 92 97 88 89 91 90 95 ...
##  $ release_year   : num [1:24] 1995 1998 1999 2001 2003 ...
##  $ avg_score      : num [1:24] NA NA 96 89 95.3 ...
summary(pixar$avg_score)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   54.67   76.00   89.00   85.02   93.67   96.00       3

Cleaning and Preparation

Cleaning steps: Inner join pixar_films and public_response on film. Create release year and average rating. Handle missing values in scores or runtime. Convert CinemaScore letters to an ordered factor if needed.

pixar_clean <- pixar_films |>
  inner_join(public_response, by = "film") |>
  mutate(
    release_year = year(release_date),
    avg_score = (rotten_tomatoes + metacritic + critics_choice) / 3,
    cinema_score = factor(
      cinema_score,
      levels = c("F", "D", "C", "B", "A"),
      ordered = TRUE
    )
  ) |>
  filter(
    !is.na(run_time),
    !is.na(rotten_tomatoes),
    !is.na(metacritic),
    !is.na(critics_choice)
  )

summary(select(pixar_clean, run_time, rotten_tomatoes, metacritic, critics_choice, avg_score))
##     run_time     rotten_tomatoes    metacritic    critics_choice  
##  Min.   : 92.0   Min.   : 40.00   Min.   :57.00   Min.   : 66.00  
##  1st Qu.: 96.0   1st Qu.: 80.00   1st Qu.:69.00   1st Qu.: 81.00  
##  Median :100.0   Median : 96.00   Median :81.00   Median : 89.00  
##  Mean   :101.9   Mean   : 88.52   Mean   :79.38   Mean   : 87.14  
##  3rd Qu.:105.0   3rd Qu.: 97.00   3rd Qu.:90.00   3rd Qu.: 93.00  
##  Max.   :118.0   Max.   :100.00   Max.   :96.00   Max.   :100.00  
##    avg_score    
##  Min.   :54.67  
##  1st Qu.:76.00  
##  Median :89.00  
##  Mean   :85.02  
##  3rd Qu.:93.67  
##  Max.   :96.00

Final Results

Q1 – Runtime vs. ratings

ggplot(pixar_clean,
       aes(x = run_time,
           y = avg_score)) +
  geom_point() +
  geom_smooth(method = "lm", se = FALSE) +
  labs(
    title = "Runtime vs. Average Rating for Pixar Films",
    x = "Runtime (minutes)",
    y = "Average rating (Rotten Tomatoes, Metacritic, Critics Choice)"
  )
## `geom_smooth()` using formula = 'y ~ x'

runtime_model <- lm(avg_score ~ run_time, data = pixar_clean)
summary(runtime_model)
## 
## Call:
## lm(formula = avg_score ~ run_time, data = pixar_clean)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -29.737  -8.995   4.449   8.593  10.820 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)   
## (Intercept) 100.0682    33.6963   2.970  0.00787 **
## run_time     -0.1478     0.3299  -0.448  0.65922   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 11.71 on 19 degrees of freedom
## Multiple R-squared:  0.01045,    Adjusted R-squared:  -0.04163 
## F-statistic: 0.2007 on 1 and 19 DF,  p-value: 0.6592

##Q2 – Ratings over time

ggplot(pixar_clean,
       aes(x = release_year,
           y = avg_score)) +
  geom_point() +
  geom_smooth(method = "loess", se = FALSE) +
  labs(
    title = "Average Pixar Film Rating Over Time",
    x = "Release year",
    y = "Average rating"
  )
## `geom_smooth()` using formula = 'y ~ x'

##Q3 – Agreement between rating sources

ratings_only <- pixar_clean |>
  select(rotten_tomatoes, metacritic, critics_choice)

cor(ratings_only, use = "complete.obs")
##                 rotten_tomatoes metacritic critics_choice
## rotten_tomatoes       1.0000000  0.8018090      0.8521907
## metacritic            0.8018090  1.0000000      0.8645541
## critics_choice        0.8521907  0.8645541      1.0000000
# Simple pairs plot (base R) or GGally if allowed
pairs(ratings_only)

##Q4 – Top-rated films

top_pixar <- pixar_clean |>
  arrange(desc(avg_score)) |>
  select(film, release_year, run_time,
         rotten_tomatoes, metacritic, critics_choice, avg_score) |>
  slice_head(n = 5)

top_pixar
## # A tibble: 5 × 7
##   film         release_year run_time rotten_tomatoes metacritic critics_choice
##   <chr>               <dbl>    <dbl>           <dbl>      <dbl>          <dbl>
## 1 Toy Story 2          1999       92             100         88            100
## 2 Toy Story 3          2010      103              98         92             97
## 3 Finding Nemo         2003      100              99         90             97
## 4 Inside Out           2015       95              98         94             93
## 5 Ratatouille          2007      111              96         96             91
## # ℹ 1 more variable: avg_score <dbl>
ggplot(top_pixar,
       aes(x = reorder(film, avg_score),
           y = avg_score)) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Top 5 Pixar Films by Combined Rating",
    x = "Film",
    y = "Average rating"
  )