Loading libraries

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(tidyr)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats   1.0.1     ✔ readr     2.2.0
## ✔ ggplot2   4.0.2     ✔ stringr   1.6.0
## ✔ lubridate 1.9.5     ✔ tibble    3.3.1
## ✔ purrr     1.2.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(ggplot2)
data <- read_csv("netflix_titles.csv")
## Rows: 8807 Columns: 12
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (11): show_id, type, title, director, cast, country, date_added, rating,...
## dbl  (1): release_year
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
data
## # A tibble: 8,807 × 12
##    show_id type    title   director cast  country date_added release_year rating
##    <chr>   <chr>   <chr>   <chr>    <chr> <chr>   <chr>             <dbl> <chr> 
##  1 s1      Movie   Dick J… Kirsten… <NA>  United… September…         2020 PG-13 
##  2 s2      TV Show Blood … <NA>     Ama … South … September…         2021 TV-MA 
##  3 s3      TV Show Gangla… Julien … Sami… <NA>    September…         2021 TV-MA 
##  4 s4      TV Show Jailbi… <NA>     <NA>  <NA>    September…         2021 TV-MA 
##  5 s5      TV Show Kota F… <NA>     Mayu… India   September…         2021 TV-MA 
##  6 s6      TV Show Midnig… Mike Fl… Kate… <NA>    September…         2021 TV-MA 
##  7 s7      Movie   My Lit… Robert … Vane… <NA>    September…         2021 PG    
##  8 s8      Movie   Sankofa Haile G… Kofi… United… September…         1993 TV-MA 
##  9 s9      TV Show The Gr… Andy De… Mel … United… September…         2021 TV-14 
## 10 s10     Movie   The St… Theodor… Meli… United… September…         2021 PG-13 
## # ℹ 8,797 more rows
## # ℹ 3 more variables: duration <chr>, listed_in <chr>, description <chr>

Understanding the data

head(data)
## # A tibble: 6 × 12
##   show_id type    title    director cast  country date_added release_year rating
##   <chr>   <chr>   <chr>    <chr>    <chr> <chr>   <chr>             <dbl> <chr> 
## 1 s1      Movie   Dick Jo… Kirsten… <NA>  United… September…         2020 PG-13 
## 2 s2      TV Show Blood &… <NA>     Ama … South … September…         2021 TV-MA 
## 3 s3      TV Show Ganglan… Julien … Sami… <NA>    September…         2021 TV-MA 
## 4 s4      TV Show Jailbir… <NA>     <NA>  <NA>    September…         2021 TV-MA 
## 5 s5      TV Show Kota Fa… <NA>     Mayu… India   September…         2021 TV-MA 
## 6 s6      TV Show Midnigh… Mike Fl… Kate… <NA>    September…         2021 TV-MA 
## # ℹ 3 more variables: duration <chr>, listed_in <chr>, description <chr>
str(data)
## spc_tbl_ [8,807 × 12] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ show_id     : chr [1:8807] "s1" "s2" "s3" "s4" ...
##  $ type        : chr [1:8807] "Movie" "TV Show" "TV Show" "TV Show" ...
##  $ title       : chr [1:8807] "Dick Johnson Is Dead" "Blood & Water" "Ganglands" "Jailbirds New Orleans" ...
##  $ director    : chr [1:8807] "Kirsten Johnson" NA "Julien Leclercq" NA ...
##  $ cast        : chr [1:8807] NA "Ama Qamata, Khosi Ngema, Gail Mabalane, Thabang Molaba, Dillon Windvogel, Natasha Thahane, Arno Greeff, Xolile "| __truncated__ "Sami Bouajila, Tracy Gotoas, Samuel Jouy, Nabiha Akkari, Sofia Lesaffre, Salim Kechiouche, Noureddine Farihi, G"| __truncated__ NA ...
##  $ country     : chr [1:8807] "United States" "South Africa" NA NA ...
##  $ date_added  : chr [1:8807] "September 25, 2021" "September 24, 2021" "September 24, 2021" "September 24, 2021" ...
##  $ release_year: num [1:8807] 2020 2021 2021 2021 2021 ...
##  $ rating      : chr [1:8807] "PG-13" "TV-MA" "TV-MA" "TV-MA" ...
##  $ duration    : chr [1:8807] "90 min" "2 Seasons" "1 Season" "1 Season" ...
##  $ listed_in   : chr [1:8807] "Documentaries" "International TV Shows, TV Dramas, TV Mysteries" "Crime TV Shows, International TV Shows, TV Action & Adventure" "Docuseries, Reality TV" ...
##  $ description : chr [1:8807] "As her father nears the end of his life, filmmaker Kirsten Johnson stages his death in inventive and comical wa"| __truncated__ "After crossing paths at a party, a Cape Town teen sets out to prove whether a private-school swimming star is h"| __truncated__ "To protect his family from a powerful drug lord, skilled thief Mehdi and his expert team of robbers are pulled "| __truncated__ "Feuds, flirtations and toilet talk go down among the incarcerated women at the Orleans Justice Center in New Or"| __truncated__ ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   show_id = col_character(),
##   ..   type = col_character(),
##   ..   title = col_character(),
##   ..   director = col_character(),
##   ..   cast = col_character(),
##   ..   country = col_character(),
##   ..   date_added = col_character(),
##   ..   release_year = col_double(),
##   ..   rating = col_character(),
##   ..   duration = col_character(),
##   ..   listed_in = col_character(),
##   ..   description = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>

Missing Values

colSums(is.na(data))
##      show_id         type        title     director         cast      country 
##            0            0            0         2634          825          831 
##   date_added release_year       rating     duration    listed_in  description 
##           10            0            4            3            0            0

Removing Duplicates

data <- data[!duplicated(data), ]

Data Type Conversion(to have categorical data)

data$type <- as.factor(data$type)
data$rating <- as.factor(data$rating)

New variables created for deeper analysis

data <- data %>%
  mutate(
    date_added = as.Date(date_added, format="%B %d, %Y"),
    year_added = as.numeric(format(date_added, "%Y")),
    duration_num = as.numeric(gsub("[^0-9]", "", duration))
  )

What is the average release year for Movies vs TV Shows?

data %>%
  group_by(type) %>%
  summarise(avg_year = mean(release_year, na.rm = TRUE))
## # A tibble: 2 × 2
##   type    avg_year
##   <fct>      <dbl>
## 1 Movie      2013.
## 2 TV Show    2017.

Which countries produce the most content?

data$country[data$country == ""] <- "Unknown"
data %>%
  count(country, sort = TRUE) %>%
  head(5)
## # A tibble: 5 × 2
##   country            n
##   <chr>          <int>
## 1 United States   2818
## 2 India            972
## 3 <NA>             831
## 4 United Kingdom   419
## 5 Japan            245

Which year had the highest content addition?

data %>%
  count(year_added, sort = TRUE) %>%
  head(1)
## # A tibble: 1 × 2
##   year_added     n
##        <dbl> <int>
## 1       2019  2016

Which country produces more Movies vs TV Shows?

data %>%
  count(country, type) %>%
  arrange(desc(n))
## # A tibble: 849 × 3
##    country        type        n
##    <chr>          <fct>   <int>
##  1 United States  Movie    2058
##  2 India          Movie     893
##  3 United States  TV Show   760
##  4 <NA>           Movie     440
##  5 <NA>           TV Show   391
##  6 United Kingdom TV Show   213
##  7 United Kingdom Movie     206
##  8 Japan          TV Show   169
##  9 South Korea    TV Show   158
## 10 Canada         Movie     122
## # ℹ 839 more rows

What is the average duration for Movies only?

data %>%
  filter(type == "Movie") %>%
  summarise(avg_duration = mean(duration_num, na.rm = TRUE))
## # A tibble: 1 × 1
##   avg_duration
##          <dbl>
## 1         99.6

What is the average duration for TV Shows only?

data %>%
  filter(type == "TV Show") %>%
  summarise(avg_duration = mean(duration_num, na.rm = TRUE))
## # A tibble: 1 × 1
##   avg_duration
##          <dbl>
## 1         1.76

What is the average duration of all content?

mean(data$duration_num, na.rm = TRUE)
## [1] 69.84689

Are there outliers in release year?

Q1 <- quantile(data$release_year, 0.25, na.rm = TRUE)
Q3 <- quantile(data$release_year, 0.75, na.rm = TRUE)
IQR_value <- Q3 - Q1

data.frame(Q1 = Q1, Q3 = Q3, IQR = IQR_value)
##       Q1   Q3 IQR
## 25% 2013 2019   6
#Q1 (25%) = 2013 → 25% of content was released on or before 2013
#Q3 (75%) = 2019 → 75% of content was released on or before 2019
#IQR = 6 → The middle 50% of release years lies within a 6-year range (2013 to 2019)

Outliers in Release Year

ggplot(data, aes(y=release_year)) +
  geom_boxplot(fill="lightgreen") +
  ggtitle("Outliers in Release Year")

#The boxplot shows that most release years are clustered within a normal range, with a few extreme values appearing as outliers.

How has Netflix content grown over time?

ggplot(data, aes(x=year_added)) +
  geom_histogram(binwidth=1, fill="green") +
  ggtitle("Content Added Over Time") +
  xlab("Year Added") + ylab("Content")

#The histogram shows that most content was added in recent years, indicating rapid growth of OTT platforms over time.

What is the distribution shape of release year?

ggplot(data, aes(x=release_year)) +
  geom_histogram(binwidth=1, fill="blue")

#The histogram shows that most content is concentrated in recent years, indicating a higher production of modern releases.

What is correlation between release year and duration?

cor(data$release_year, data$duration_num, use="complete.obs")
## [1] -0.2491815
#The correlation result shows a weak relationship between release year and duration, indicating that content length does not significantly depend on the year of release.

Linear regression

model <- lm(duration_num ~ release_year, data=data)
summary(model)
## 
## Call:
## lm(formula = duration_num ~ release_year, data = data)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -196.87  -59.49   16.91   36.77  247.64 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  2961.22063  119.77939   24.72   <2e-16 ***
## release_year   -1.43551    0.05947  -24.14   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 49.21 on 8802 degrees of freedom
##   (3 observations deleted due to missingness)
## Multiple R-squared:  0.06209,    Adjusted R-squared:  0.06198 
## F-statistic: 582.7 on 1 and 8802 DF,  p-value: < 2.2e-16
#The regression results show that release year has little to no significant effect on duration, indicating a weak relationship between the two variables.

Plotting the curve

ggplot(data, aes(x=release_year, y=duration_num)) +
  geom_point() +
  geom_smooth(method="lm")
## `geom_smooth()` using formula = 'y ~ x'

#The graph shows a slight downward trend, indicating that newer content tends to have shorter durations, but the relationship is weak due to high variability in data.

Model predict duration based on release year

new_data <- data.frame(release_year = 2022)
head(predict(model, new.data = new_data))
##        1        2        3        4        5        6 
## 61.49260 60.05709 60.05709 60.05709 60.05709 60.05709
#The model predicts the expected duration for content released in 2022, which is around 60 mins

Split Data into training and testing

train_index <- sample(1:nrow(data), 0.7*nrow(data))

train_data <- data[train_index, ]
test_data <- data[-train_index, ]



#The dataset is divided into 70% training data and 30% testing data to build and evaluate the model’s performance effectively.

Predicting test data

predictions <- predict(model, new.data=test_data)
head(predictions)
##        1        2        3        4        5        6 
## 61.49260 60.05709 60.05709 60.05709 60.05709 60.05709
#The model predicts duration values for the test dataset, which can be used to assess how well the linear regression model performs on unseen data.

Boxplot for Rating Distribution

library(dplyr)

top_ratings <- data %>%
  count(rating, sort = TRUE) %>%
  head(6) %>%
  pull(rating)

ggplot(data %>% filter(rating %in% top_ratings),
       aes(x = rating, y = duration_num)) +
  geom_boxplot(fill = "lightblue") +
  ggtitle("Duration across Top Rating Categories") +
  xlab("Rating") +
  ylab("Duration")

#The boxplot shows that movie ratings like PG-13 and R have longer durations, while TV ratings such as TV-Y7 have shorter durations,

Creating a binary variable

data$type_bin <- ifelse(data$type == "Movie", 1, 0)  

#A new binary variable is created where Movies are coded as 1 and TV Shows as 0 to enable numerical analysis and modeling.

Logistic Model

model_log <- glm(type_bin ~ duration_num, data=data, family="binomial")
summary(model_log)
## 
## Call:
## glm(formula = type_bin ~ duration_num, family = "binomial", data = data)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  -8.54719    0.76095 -11.232   <2e-16 ***
## duration_num  0.67923    0.07162   9.483   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 10814.417  on 8803  degrees of freedom
## Residual deviance:    89.142  on 8802  degrees of freedom
##   (3 observations deleted due to missingness)
## AIC: 93.142
## 
## Number of Fisher Scoring iterations: 14
#For the classification of movies and shows

Prediction of logistic model

predict=predict(model_log, type="response")
head(predict)
##            1            2            3            4            5            6 
## 1.0000000000 0.0007544841 0.0003826694 0.0003826694 0.0007544841 0.0003826694
#The model outputs probability values between 0 and 1, indicating the likelihood of each content being a Movie (closer to 1) or a TV Show (closer to 0).

Fitting multiple regression model

model_multi <- lm(duration_num ~ release_year + year_added, data = train_data)

summary(model_multi)
## 
## Call:
## lm(formula = duration_num ~ release_year + year_added, data = train_data)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -201.13  -60.68   16.82   36.32  249.82 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -950.29252  803.23957  -1.183    0.237    
## release_year   -1.50488    0.07198 -20.907  < 2e-16 ***
## year_added      2.00660    0.39860   5.034 4.94e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 49.15 on 6152 degrees of freedom
##   (9 observations deleted due to missingness)
## Multiple R-squared:  0.06757,    Adjusted R-squared:  0.06727 
## F-statistic: 222.9 on 2 and 6152 DF,  p-value: < 2.2e-16
#You created multiple regression to improve prediction accuracy by using more than one independent variable.

Prediction on test data

predictions <- predict(model_multi, newdata = test_data)

results <- data.frame(
  Actual = test_data$duration_num,
  Predicted = predictions
)

head(results)
##   Actual Predicted
## 1      2  63.68479
## 2      1  63.68479
## 3    125 105.82145
## 4      1  63.68479
## 5      2  65.18967
## 6     94  63.68479
#The model predictions are nearly constant and do not match actual values well, indicating poor model performance.