Hito 1: Caracterización y análisis de películas de IMDB

CC5206 Primavera 2021

Integrantes: Alan Acevedo, Diego Kauer, Camila Labarca, Franco Miranda, Julia Paredes

Grupo: 4

Profesores: Andrés Abeliuk, Hernán Sarmiento

Auxiliares: Alison Fernández, Cinthia Sánchez

Fecha de entrega: 28 de septiembre de 2021

Introducción

En la última década, el mercado de las películas y series ha crecido enormemente gracias a los avances tecnológicos y a plataformas online como Netflix, Hulu, Amazon, Disney+, entre otras. Este tema puede ser bastante interesante de analizar ya que prácticamente todas las personas están familiarizadas con éste ámbito.

El objetivo de este proyecto es aplicar distintas técnicas de minería de datos para realizar un estudio sobre un dataset obtenido de la Internet Movie Database que contiene información sobre películas. Los resultados le podrían ser de bastante utilidad a aspirantes en el mundo del cine, pues les daría una amplia imagen de la industria cinematográfica, y junto con ello entender su comportamiento.

Dataset

Se trabajó con el dataset de IMDB, el cual está distribuido en 7 tablas en formato TSV, de las cuales en este hito se ocuparán dos.

La tabla title_basics contiene varios atributos de interés sobre títulos:

La tabla title_ratings contiene dos atributos que también son interesantes:

En primer lugar se cargan las librerías a usar y luego las tablas.

library(tidyr)
library(tidyverse)
library(ggplot2)
library(corrplot)
title_basics <- read.delim2("./datasets/title_basics.tsv", encoding="UTF-8")
title_ratings <- read.delim2("./datasets/title_ratings.tsv", encoding="UTF-8")

Luego, los atributos que correspondan se cambian a categóricos o a númericos.

title_basics$runtimeMinutes = as.numeric(title_basics$runtimeMinutes)
title_basics$genres = as.factor(title_basics$genres)
title_basics$startYear = as.numeric(title_basics$startYear)
title_basics$titleType = as.factor(title_basics$titleType)
title_basics$isAdult = as.factor(title_basics$isAdult)
title_ratings$averageRating = as.numeric(title_ratings$averageRating)
nrow(title_basics)
## [1] 8246400

title_basics tiene 8.246.400 filas.

Para ver cuántos elementos sin valores asignados hay en las tablas se usa la siguiente línea:

sapply(title_basics, function(x) sum(is.na(x)))
##         tconst      titleType   primaryTitle  originalTitle        isAdult 
##              0              0              4              4              0 
##      startYear        endYear runtimeMinutes         genres 
##         972161              0        5920733              0

La columna runtimeMinutes tiene 5.920.733 valores no asignados, lo que corresponde a un gran porcentaje del total de filas. También hay 972.161 valores nulos en startYear. Estos atributos son de interés y podrían ser usados en el estudio, por lo que se hará una limpieza del dataset.

Se realizará un estudio de los datos relacionados a películas, por lo que se filtrará para quitar los títulos de tipo documental o cortometraje, y además solo se considerarán las películas con géneros válidos.

only_movies <- title_basics[title_basics$genres != '\\N' & title_basics$titleType == 'movie', ]
nrow(only_movies)
## [1] 516492

El nuevo dataframe tiene 516.492 filas.

Ahora se puede volver a ver cuantos elementos tienen valores no asignados.

sapply(only_movies, function(x) sum(is.na(x)))
##         tconst      titleType   primaryTitle  originalTitle        isAdult 
##              0              0              0              0              0 
##      startYear        endYear runtimeMinutes         genres 
##          70672              0         170153              0

La cantidad de valores no asignados en runtimeMinutes y startYear disminuyó considerablemente por lo que ahora podemos hacer una limpieza de estos valores.

only_movies <- na.omit(only_movies)
nrow(only_movies)
## [1] 340916

Después de la limpieza de valores nulos, title_basics tiene 340.916 filas. Sigue siendo un número importante de datos que pueden ser estudiados por lo que no es necesario considerar los valores nulos.

Como ya se filtró según la columna titleType, ésta se elimina del dataframe. Además, endYear es siempre \N para las películas, por lo que no entregan información y se eliminan del dataframe. Por último se elimina la columna originalTitle ya que no es de interés (es el nombre de la película en el idioma original).

only_movies$endYear <- NULL
only_movies$titleType <- NULL
only_movies$originalTitle <- NULL

Se hace un join de las tablas only_movies y title_ratings para que cada película tenga su valoración promedio y cantidad de valoraciones, obteniendo la tabla con la que se trabajará.

movies <- merge(only_movies, title_ratings, by='tconst')

En la tabla movies hay una columna “genres” que lista hasta 3 géneros de una misma película, éstos se pueden separar para hacer más sencillo el estudio. Se considerará el primer género como el principal.

movies_alt <- separate(movies, "genres", paste("Genero", 1:3, sep=""), sep=",")

Se puede ver la cantidad de NA’s en las columnas.

sapply(movies, function(x) sum(is.na(x)))
##         tconst   primaryTitle        isAdult      startYear runtimeMinutes 
##              0              0              0              0              0 
##         genres  averageRating       numVotes 
##              0              0              0

No hay nulos en ninguna columna, por lo que el dataframe está completamente limpio.

Luego de haber limpiado el dataset, se puede hacer un summary para analizar si hay algún dato inconsistente,

summary(movies)
##     tconst          primaryTitle          isAdult         startYear   
##  Length:235209      Length:235209      0      :231633   Min.   :1896  
##  Class :character   Class :character   1      :  3576   1st Qu.:1977  
##  Mode  :character   Mode  :character   \\N    :     0   Median :2004  
##                                        1964   :     0   Mean   :1993  
##                                        1965   :     0   3rd Qu.:2014  
##                                        1972   :     0   Max.   :2021  
##                                        (Other):     0                 
##  runtimeMinutes               genres       averageRating       numVotes      
##  Min.   :    1.00   Drama        : 41062   Min.   : 1.000   Min.   :      5  
##  1st Qu.:   81.00   Documentary  : 23770   1st Qu.: 5.400   1st Qu.:     22  
##  Median :   91.00   Comedy       : 20651   Median : 6.300   Median :     75  
##  Mean   :   94.34   Comedy,Drama :  8170   Mean   : 6.141   Mean   :   3890  
##  3rd Qu.:  103.00   Drama,Romance:  7488   3rd Qu.: 7.000   3rd Qu.:    376  
##  Max.   :51420.00   Horror       :  5279   Max.   :10.000   Max.   :2455855  
##                     (Other)      :128789

En isAdult solo se entrega 0 o 1 dependiendo de si existe contenido explícito, por lo que esta columna está correcta. En startYear se muestran años desde 1896 hasta el año actual, no existen películas en años futuros por lo que está correcta esta columna. En runtimeMinutes existen películas con 1 minuto de duración y con más de 50 mil minutos de duración, estos valores posiblemente sean outliers y entonces se pueden trabajar sin problemas. En genres solo existen géneros válidos. Los valores de la columna averageRating se mueven entre 1 y 10 lo que es consistente. Por último, la columna numVotes se mueve entre valores positivos y entonces es consistente.

Ahora se analizarán las películas exitosas y películas muy malas. Para ello, se mostrarán los deciles que corresponden a rating y número de votos. Con ello, se definirá una película exitosa la que esté sobre el decil 9 en ambas categorías, y una película muy mala será aquella bajo el decil 1 para rating.

Deciles para rating.

quantile(movies$averageRating, probs = seq(0, 1, by = .1))
##   0%  10%  20%  30%  40%  50%  60%  70%  80%  90% 100% 
##  1.0  4.4  5.1  5.6  6.0  6.3  6.6  6.9  7.2  7.7 10.0

Deciles para numVotes.

quantile(movies$numVotes, probs = seq(0, 1, by = .1))
##      0%     10%     20%     30%     40%     50%     60%     70%     80%     90% 
##       5      11      17      27      45      75     133     255     585    2097 
##    100% 
## 2455855

Vistos los deciles, se define una película exitosa como toda película que tiene un rating promedio de al menos 7.7 y una cantidad de votos mayor o igual a 2097.

best_movies <- movies[movies$averageRating >= 7.7 & movies$numVotes >= 2097, ][2:ncol(movies)]

De la misma forma, se define una película muy mala como toda película que tenga un rating inferior o igual a 4.4.

worst_movies <- movies[movies$averageRating <= 4.4, ][2:ncol(movies)]

A partir de esto, se puede ver qué porcentaje de películas del total puede considerarse exitosa.

round((nrow(best_movies) / nrow(movies)) * 100, 2)
## [1] 1.12

Solo el 1.12% de películas puede considerarse exitosa, que corresponde a un porcentaje muy bajo, lo que puede hablar de que es muy difícil crear una película y que ésta sea exitosa.

También, se puede ver qué porcentaje del total de películas corresponde a películas muy malas.

round((nrow(worst_movies) / nrow(movies)) * 100, 2)
## [1] 10.81

Un 10.81% de las películas es muy mala, un porcentaje bastante mayor al de películas exitosas, lo que habla de que es mucho más sencillo crear una película muy mala a una que tenga éxito.

Se puede analizar cuantas películas exitosas y cuantas muy malas tienen contenido explícito.

nrow(best_movies[best_movies$isAdult == 1, ])
## [1] 0

Ninguna película exitosa tiene contenido explícito, lo que puede puede dar luces de que una película para un amplio público puede ser bien calificada.

nrow(worst_movies[worst_movies$isAdult == 1, ])
## [1] 561

Hay muchas películas malas en comparación a las exitosas que tienen contenido explícito, lo que puede significar que las películas con contenido explícito tienen mayor probabilidad de no tener éxito. Esto puede deberse a la misma razón mencionada anteriormente, es decir, que estas películas están hechas para un público más pequeño y específico, por lo que no serán bien recibidas por todos y todas.

Luego de realizar el análisis anterior, se puede consultar sobre un top 10 de películas mejor y peor calificadas a partir de 1970, esto último para solo considerar películas contemporáneas.

best_movies1970 <- best_movies[best_movies$startYear >= 1970, ]
best_movies1970[order(-best_movies1970$averageRating), ][1:10, ]
##                    primaryTitle isAdult startYear runtimeMinutes
## 151110                  Methagu       0      2021            100
## 57392  The Shawshank Redemption       0      1994            142
## 88853           The Chaos Class       0      1975             87
## 34182             The Godfather       0      1972            175
## 136088         Chal Mera Putt 2       0      2020            124
## 107667        Aguner Poroshmoni       0      1994            123
## 126843          Soorarai Pottru       0      2020            153
## 129132                    #Home       0      2021            158
## 178514   CM101MMXI Fundamentals       0      2013            139
## 206913              Mirror Game       0      2016            147
##                        genres averageRating numVotes
## 151110      Biography,History           9.6     8416
## 57392                   Drama           9.3  2455855
## 88853            Comedy,Drama           9.3    38992
## 34182             Crime,Drama           9.2  1699389
## 136088                  Drama           9.2     2617
## 107667              Drama,War           9.1     2991
## 126843                  Drama           9.1    89377
## 129132                  Drama           9.1     7949
## 178514     Comedy,Documentary           9.1    44645
## 206913 Crime,Mystery,Thriller           9.1    24835
worst_movies1970 <- worst_movies[worst_movies$startYear >= 1970, ]
worst_movies1970[order(worst_movies1970$averageRating), ][1:10, ]
##                               primaryTitle isAdult startYear runtimeMinutes
## 73779                  Steckler Interviews       0      1994             60
## 127426 Konjaku monogatari: The new edition       0      2007             88
## 141317                     Princess Europe       0      2020            108
## 143627                    Hearts Are Trump       0      2020            112
## 144439              Bootleg Death Tape III       0      2020             45
## 145070                          321 Action       0      2020            100
## 153006                    Play in the Gray       0      2009             85
## 153200                             Macbeth       0      2009             56
## 155255                      Hito no sabaku       0      2010            121
## 156186              Stand Up Face the Fear       0      2008             65
##                              genres averageRating numVotes
## 73779                   Documentary             1       19
## 127426                 Comedy,Drama             1      500
## 141317                  Documentary             1      584
## 143627                        Drama             1       21
## 144439                       Horror             1        8
## 145070                        Drama             1     9341
## 153006 Biography,Comedy,Documentary             1      132
## 153200                        Drama             1       11
## 155255                        Drama             1      475
## 156186           Comedy,Documentary             1       35

Se puede ver que tanto en las mejores como en las peores hay muchas películas de los últimos años. Esto podría deberse a que en los últimos años se han votado las películas que han salido y esos datos se han guardado en el dataset de forma correcta, lo que puede no haber sido así para películas más antiguas.

Se definen funciones que pueden ser útiles para el análisis de los datos.

# str -> double
# Entrega el porcentaje de películas del género que pertenecen a las mejores películas.
best_movies_por_genero <- function(genre) {
  genero <- best_movies[grepl(genre, best_movies$genres), ]
  round((nrow(genero) / nrow(best_movies)) * 100, 2)
}

# str -> double
# Entrega el porcentaje de películas del género que pertenecen a las peores películas
worst_movies_por_genero <- function(genre) {
  genero <- worst_movies[grepl(genre, worst_movies$genres), ]
  round((nrow(genero) / nrow(worst_movies)) * 100, 2)
}

# str -> boxplot
# Crea un boxplot de los rating de un género
boxplot_rating_por_genero <- function(genre) {
  ggplot(movies[grepl(genre, movies$genres), ], aes(x=isAdult, y=averageRating)) +
    geom_boxplot() +
    xlab("Es para adultos? (0=No, 1=Sí)") +
    ylab("Rating") +
    ggtitle(paste("Boxplot de ratings para género", genre, sep=" ")) +
    theme(plot.title = element_text(hjust = 0.5, size = 10))
}

# str -> boxplot
# Crea un boxplot con la distribución de ratings para una película con género principal genre
# Género principal es el primer género en la lista de géneros
boxplot_rating_genero_principal <- function(main_genre) {
  ggplot(movies_alt[movies_alt$Genero1 == main_genre, ], aes(x=isAdult, y=averageRating)) +
    geom_boxplot() +
    xlab("Es para adultos? (0=No, 1=Sí)") +
    ylab("Rating") +
    ggtitle(paste("Boxplot de ratings para género principal", main_genre, sep=" ")) +
    theme(plot.title = element_text(hjust = 0.5, size = 10))
}

# str -> ggplot
# Crea un gráfico de dispersión que muestra la relación entre rating y duración para películas de un género
dispersion_rating_duracion <- function(genre) {
  ggplot(movies[grepl(genre, movies$genres), ], aes(x=runtimeMinutes, y=averageRating)) +
    geom_point() +
    ggtitle(paste("Dispersión entre rating y duración para el género", genre, sep=" ")) +
    xlab("Duración") + 
    ylab("Rating") +
    theme(plot.title = element_text(hjust = 0.5, size = 9))
}

# str -> ggplot
# Crea un gráfico de dispersión que muestra la relación entre rating y cantidad de votos para películas de un género
dispersion_rating_votos <- function(genre) {
  ggplot(movies[grepl(genre, movies$genres), ], aes(x=numVotes, y=averageRating)) +
    geom_point() +
    ggtitle(paste("Dispersión entre rating y votos para género", genre, sep=" ")) +
    xlab("Número de votos") +
    ylab("Rating") +
    theme(plot.title = element_text(hjust = 0.5, size = 9))
}

Luego de haber definido las funciones, se pueden realizar algunas consultas interesantes, como ver qué porcentaje de películas exitosas o muy malas corresponde a cierto género.

Porcentaje de películas de un género que son exitosas.

best_movies_por_genero("Action")
## [1] 14.25
best_movies_por_genero("Drama")
## [1] 67.32
best_movies_por_genero("Crime")
## [1] 16.11
best_movies_por_genero("Comedy")
## [1] 24.83
best_movies_por_genero("Romance")
## [1] 15.73
best_movies_por_genero("Mystery")
## [1] 6.03
best_movies_por_genero("Family")
## [1] 3.94
best_movies_por_genero("Adult")
## [1] 0
best_movies_por_genero("Sport")
## [1] 2.81
best_movies_por_genero("Sci-Fi")
## [1] 2.81

Porcentaje de películas de un género que son muy malas.

worst_movies_por_genero("Action")
## [1] 17.47
worst_movies_por_genero("Drama")
## [1] 30.61
worst_movies_por_genero("Crime")
## [1] 8.44
worst_movies_por_genero("Comedy")
## [1] 29.91
worst_movies_por_genero("Romance")
## [1] 8.47
worst_movies_por_genero("Mystery")
## [1] 4.79
worst_movies_por_genero("Family")
## [1] 3.22
worst_movies_por_genero("Adult")
## [1] 2.03
worst_movies_por_genero("Sport")
## [1] 0.68
worst_movies_por_genero("Sci-Fi")
## [1] 6.78

Comparando ambos resultados, lo primero que se destaca es que no hay películas para adultos exitosas y sí lo hay en películas muy malas, lo que confirma lo dicho en un punto anterior. También, en ambos casos el género “Drama” es el que más aparece, seguido por “Comedy” y “Action”, esto puede dar a entender que se realizan muchas películas de estos géneros porque son géneros populares, y por ende hay muchas que son tanto exitosas como muy malas. Se puede notar que hay un alto porcentaje de películas del género “Romance” que son exitosas, sin embargo este número disminuye bastante en las películas muy malas, lo mismo ocurre con “Crime”.

Se realizará un gráfico de densidad que mostrará cómo se distribuyen los rating para todas las películas.

ggplot(movies) +
  geom_density(aes(x = averageRating), fill = "steelblue") +
  xlab("Rating") +
  ylab("Frecuencia") +
  ggtitle("Densidad de rating para todas las películas") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 10))

mean(movies$averageRating)
## [1] 6.140733
sd(movies$averageRating)
## [1] 1.312389

Analizando el gráfico junto con el promedio y desviación estándar para el rating, se puede notar que el gráfico se asemeja bastante a una distribución normal con media 6.14 y desviación estándar 1.31. El valor de la densidad aumenta levemente hasta llegar a su máximo, para luego caer rápidamente, esto significa que hay muy poca cantidad de películas con rating sobre el promedio en comparación a películas con rating bajo el promedio.

Se crearán boxplots de ratings para géneros utilizando dos métodos. Primero, se creará un boxplot si es que la película es de un único género, y segundo, en caso de que el título esté asociado a más de un género, se creará un boxplot solo considerando el primer género de la lista, esto para ver si hay mayores diferencias a cuando el género es único o no. Se analizará para los géneros “Action”, “Drama” y “Comedy” y se diferenciará por contenido explícito.

Boxplots para “Action”

boxplot_rating_por_genero("Action")
boxplot_rating_genero_principal("Action")

En este caso, no se ven cambios respecto a cuando “Action” es el género principal de una película o no.

Boxplots para “Drama”

boxplot_rating_por_genero("Drama")
boxplot_rating_genero_principal("Drama")

En este caso, si la película no es para adultos no se ven cambios, pero de serlo, la distribución cambia, ésta se traslada un poco hacia abajo, lo que significa que el primer, segundo y tercer cuartil de ratings son más bajos.

Boxplots para “Comedy”

boxplot_rating_por_genero("Comedy")
boxplot_rating_genero_principal("Comedy")

En este último caso se ven cambios tanto si la película es para adultos como si no. Cuando no es para adultos, el tercer cuartil se desplaza un poco para abajo y hay más outliers superiores, mientras que cuando es para adultos, el boxplot se desplaza bastante hacia abajo, y se puede concluir lo mismo que para el género anterior.

Se pueden realizar gráficos de dispersión para analizar la relación entre ciertas variables. En particular, se analizará la relación entre rating y duración, y rating y cantidad de votos para los géneros “Action”, “Drama”, “Comedy”.

Gráficos de dispersión entre rating y duración.

dispersion_rating_duracion("Action")
dispersion_rating_duracion("Drama")
dispersion_rating_duracion("Comedy")

Luego de realizar el gráfico de dispersión entre rating y duración de las películas para los 3 géneros, se puede notar que hay 2 bastante similares, “Action” y “Comedy”, es decir, su duración fluctúa en un rango amplio sin importar el rating, mientras que para “Drama” la duración es notoriamente menor y tampoco parece importar el rating.

Gráficos de duración entre rating y cantidad de votos.

dispersion_rating_votos("Action")
dispersion_rating_votos("Drama")
dispersion_rating_votos("Comedy")

Con los gráficos de dispersión entre rating y cantidad de votos se puede ver una clara tendencia, a partir del rating 5.0 aproximadamente comienza a haber un mayor número de votos, lo cual hace sentido, pues si una película es bien calificada más gente se verá interesada en seguir votándola.

Con los valores numéricos del dataset se puede crear una matriz de correlación. Para ver si el título de la película tiene alguna correlación con otra variable se usará el largo del título.

movies_cor <- cbind(movies[1], lapply(movies[2], FUN=nchar), movies[3:8])
cor <- cor(movies_cor %>% select(primaryTitle, startYear, runtimeMinutes, averageRating, numVotes))
colnames(cor) <- c("titleLength", "startYear", "runtimeMinutes", "averageRating", "numVotes")
corrplot(cor, title = "Correlaciones para la tabla 'movies'", mar = c(0, 0, 1, 0), method = "shade", tl.pos = 'n', addCoef.col = "black")

La mayor correlación que existe es entre el rating y el número de votos, lo que es consistente con lo dicho anteriormente, pero aun así sigue siendo un valor extremadamente pequeño (ni siquiera 0.1). Entonces, se puede decir que todas las variables son prácticamente independientes entre sí.

Preguntas y problemas

Luego de realizar el analisis exploratorio de los datos se definieron las siguientes preguntas y problemas a responder:

Contribución de cada miembro

Alan Acevedo: Obtención y limpieza de datos. Presentación.

Diego Kauer: Gráficos de dispersión, matriz de correlación. Presentación.

Camila Labarca: Gráficos de dispersión, largo de titulo para matriz de correlación, formulación de preguntas y problemas. Presentación.

Franco Miranda: Análisis de mejores y peores películas . Funciones para gráficos. Preguntas y problemas. Presentación.

Julia Paredes: Análisis y exploración de datos, formulación de preguntas y problemas. Presentación.