Introducción

Este trabajo corresponde al proyecto final de la clase de Métodos Cuantitativos. El objetivo es aplicar, sobre datos reales de entretenimiento, las técnicas vistas en el curso para describir patrones, contrastar hipótesis y comunicar resultados de forma clara. Se analizan dos tipos de contenido: series (rendimiento en Netflix en 2023) y películas (calificaciones y metadatos asociados a IMDb), con el fin de comparar qué factores se relacionan con el éxito en cada plataforma o catálogo.

A lo largo del curso utilizamos, entre otras, las siguientes herramientas estadísticas: análisis de covarianza y correlación, distribuciones de probabilidad (modelo normal como referencia), pruebas de hipótesis, la prueba \(\chi^2\) de independencia, la prueba t de Student para comparar medias, el análisis de varianza (ANOVA) de un factor y la regresión lineal simple. Cada sección del informe enlaza una de estas herramientas con una gráfica, su interpretación y una conclusión breve.

Parte Fuente de datos Enfoque del análisis
I — Netflix Fuentes/total_netflix_2023 new.csv Horas vistas, valoraciones, calificación y disponibilidad global
II — Películas (IMDb) Fuentes/movies.json Calificación IMDb, año de estreno, género y tendencias temporales

Parte I — Análisis de Netflix

ruta_netflix <- "Fuentes/total_netflix_2023 new.csv"
netflix <- read.csv(ruta_netflix, stringsAsFactors = FALSE)

netflix <- netflix %>%
  mutate(
    Hours.Viewed = as.numeric(gsub("[^0-9.]", "", Hours.Viewed)),
    Number.of.Ratings = as.numeric(gsub("[^0-9.]", "", Number.of.Ratings)),
    Rating = as.numeric(Rating),
    log_Hours = log10(Hours.Viewed),
    Available.Globally. = factor(Available.Globally., levels = c("No", "Yes")),
    GenreSimple = case_when(
      grepl("Comedy", Genre) ~ "Comedy",
      grepl("Drama", Genre) ~ "Drama",
      grepl("Action", Genre) ~ "Action",
      TRUE ~ "Otro"
    ),
    GenreSimple = factor(GenreSimple)
  ) %>%
  filter(
    !is.na(Hours.Viewed),
    !is.na(Rating),
    !is.na(Number.of.Ratings),
    Hours.Viewed > 0,
    Number.of.Ratings >= 0
  )

n_netflix <- nrow(netflix)

Tras la limpieza hay 14,633 series. Aproximadamente 29% está disponible globalmente. Para horas vistas usamos \(\log_{10}\); la justificación aparece al cierre de esta parte.


1. Análisis de covarianza (Netflix)

Contexto

En series de Netflix preguntamos si horas vistas, número de valoraciones y calificación (0–10) varían juntas. La correlación de Pearson resume esa relación lineal; para horas aplicamos \(\log_{10}\) por el sesgo de la escala original.

vars_cov <- netflix %>%
  select(log_Hours, Number.of.Ratings, Rating) %>%
  rename(
    `log10(Horas vistas)` = log_Hours,
    `N. valoraciones` = Number.of.Ratings,
    Calificacion = Rating
  )

cor_mat <- cor(vars_cov, use = "complete.obs")

tabla_proyecto(
  as.data.frame(round(cor_mat, 3)),
  caption = "Netflix — Matriz de correlación",
  align = "c",
  digits = 3
)
Netflix — Matriz de correlación
log10(Horas vistas) N. valoraciones Calificacion
log10(Horas vistas) 1.000 -0.003 -0.008
N. valoraciones -0.003 1.000 0.117
Calificacion -0.008 0.117 1.000
cor_largo <- as.data.frame(as.table(cor_mat))
names(cor_largo) <- c("Var1", "Var2", "r")

ggplot(cor_largo, aes(Var1, Var2, fill = r)) +
  geom_tile(color = "white", linewidth = 0.8) +
  geom_text(aes(label = sprintf("%.2f", r)), color = "white", fontface = "bold", size = 5) +
  scale_fill_gradientn(colours = paleta_cor, limits = c(-1, 1), name = "Correlación") +
  labs(
    title = titulo_grafica("Análisis de covarianza"),
    subtitle = "Correlación entre log10(horas), valoraciones y calificación",
    x = NULL, y = NULL
  ) +
  tema_proyecto +
  theme(axis.text.x = element_text(angle = 25, hjust = 1, face = "bold"))
Netflix: matriz de correlación.

Netflix: matriz de correlación.

Conclusión

La correlación más fuerte aparece entre N. valoraciones y Calificacion (0.117). Entre log-horas y calificación la relación es débil (-0.008): más reproducción no garantiza mejor nota en esta muestra.


2. Distribución de probabilidades (Netflix)

Contexto

Describimos la distribución de calificaciones de series y la comparamos con una curva normal teórica (media y desviación muestrales) como referencia visual.

mu_n <- mean(netflix$Rating)
sd_n <- sd(netflix$Rating)

tabla_proyecto(
  data.frame(
    Estadistico = c("Media", "Desv. est.", "Mínimo", "Máximo"),
    Valor = c(mu_n, sd_n, min(netflix$Rating), max(netflix$Rating))
  ),
  digits = 3,
  caption = "Netflix — Resumen de calificaciones",
  align = c("l", "r")
)
Netflix — Resumen de calificaciones
Estadistico Valor
Media 6.624
Desv. est. 1.256
Mínimo 1.400
Máximo 10.000
x_seq <- seq(min(netflix$Rating), max(netflix$Rating), length.out = 200)
curva_norm <- data.frame(Rating = x_seq, densidad = dnorm(x_seq, mu_n, sd_n))

ggplot(netflix, aes(x = Rating, fill = GenreSimple)) +
  geom_histogram(aes(y = after_stat(density)), bins = 25, alpha = 0.75, color = "white", position = "identity") +
  geom_line(data = curva_norm, aes(x = Rating, y = densidad), inherit.aes = FALSE, color = col_linea, linewidth = 1.4) +
  scale_fill_manual(values = col_genero, name = "Género") +
  labs(
    title = titulo_grafica("Distribución de probabilidades"),
    subtitle = "Histograma por género con curva normal teórica superpuesta",
    x = "Calificación (0–10)", y = "Densidad"
  ) +
  tema_proyecto +
  theme(legend.position = "bottom")
Netflix: distribución de calificaciones.

Netflix: distribución de calificaciones.

Conclusión

Las calificaciones se concentran cerca de 6.62 (DE = 1.26). La mayoría de series reciben notas medias-altas; la campana roja es referencia, no prueba formal de normalidad.


3. Prueba de hipótesis y t de Student (Netflix)

Contexto

Contrastamos \(\log_{10}\)(horas vistas) entre series no disponibles globalmente frente a disponibles, con prueba t de Welch (\(H_0\): medias iguales).

t_prueba <- t.test(log_Hours ~ Available.Globally., data = netflix, var.equal = FALSE)

tabla_proyecto(
  data.frame(
    Estadistico = c("t", "gl", "valor-p", "Media log10 (No)", "Media log10 (Yes)"),
    Valor = c(
      round(t_prueba$statistic, 4),
      round(t_prueba$parameter, 2),
      format.pval(t_prueba$p.value, digits = 4),
      round(t_prueba$estimate[1], 3),
      round(t_prueba$estimate[2], 3)
    )
  ),
  caption = "Netflix — Prueba t de Student",
  align = c("l", "r")
)
Netflix — Prueba t de Student
Estadistico Valor
t t -12.5488
df gl 7683.76
valor-p < 2.2e-16
mean in group No Media log10 (No) 6.381
mean in group Yes Media log10 (Yes) 6.553
ggplot(netflix, aes(x = Available.Globally., y = log_Hours, fill = Available.Globally.)) +
  geom_boxplot(alpha = 0.75, width = 0.55, color = color_vino, outlier.color = "#FFD166") +
  geom_jitter(aes(color = Available.Globally.), width = 0.15, alpha = 0.25, size = 0.6) +
  stat_summary(fun = mean, geom = "point", shape = 18, size = 3.5, color = col_punto) +
  scale_fill_manual(values = col_global, guide = "none") +
  scale_color_manual(values = col_global, guide = "none") +
  labs(
    title = titulo_grafica("Prueba t de Student"),
    subtitle = "Boxplots lado a lado; rombo negro = media muestral",
    x = "Disponible globalmente",
    y = expression(log[10](Horas~vistas))
  ) +
  annotate("text", x = 1.5, y = max(netflix$log_Hours) * 0.98, color = color_vino,
           label = paste0("valor-p = ", format.pval(t_prueba$p.value, digits = 3))) +
  tema_proyecto
Netflix: boxplots lado a lado (log10 horas).

Netflix: boxplots lado a lado (log10 horas).

Conclusión

Con valor-p = <2e-16, rechazamos \(H_0\). Las series globales tienen media log-horas 6.553 vs 6.381 en las no globales: la disponibilidad global se asocia con mayor audiencia.


4. Prueba Chi-cuadrado (Netflix)

Contexto

Evaluamos si disponibilidad global y género simplificado son independientes mediante \(\chi^2\) de Pearson.

tab_chi <- table(netflix$Available.Globally., netflix$GenreSimple)
dimnames(tab_chi) <- list(Disponibilidad = c("No", "Yes"), Genero = levels(netflix$GenreSimple))

tabla_chi_df <- data.frame(
  Disponibilidad = rownames(tab_chi),
  as.data.frame.matrix(tab_chi),
  check.names = FALSE
)
names(tabla_chi_df)[-1] <- colnames(tab_chi)

tabla_proyecto(
  tabla_chi_df,
  caption = "Netflix — Tabla de contingencia",
  align = c("l", rep("r", ncol(tab_chi)))
)
Netflix — Tabla de contingencia
Disponibilidad Action Comedy Drama Otro
No No 853 3493 2618 3420
Yes Yes 342 1442 1104 1361
chi_resultado <- chisq.test(tab_chi)

tabla_proyecto(
  data.frame(
    `Chi-cuadrado` = round(chi_resultado$statistic, 3),
    gl = chi_resultado$parameter,
    `valor-p` = format.pval(chi_resultado$p.value, digits = 4),
    check.names = FALSE
  ),
  caption = "Netflix — Resultado Chi-cuadrado",
  align = c("l", "r", "r", "r")
)
Netflix — Resultado Chi-cuadrado
Chi-cuadrado gl valor-p
X-squared 1.64 3 0.6504

Conclusión

\(\chi^2\) = 1.64, valor-p = 0.65. No rechazamos independencia: el género simplificado no parece asociarse con la disponibilidad global en este catálogo.


5. Análisis de regresión (Netflix)

Contexto

Modelamos calificación ~ \(\log_{10}\)(horas vistas) para ver si más audiencia implica mejor nota promedio.

mod_n <- lm(Rating ~ log_Hours, data = netflix)
coef_n <- as.data.frame(summary(mod_n)$coefficients)
coef_n$Termino <- rownames(coef_n)
rownames(coef_n) <- NULL

tabla_proyecto(
  coef_n[, c("Termino", "Estimate", "Std. Error", "Pr(>|t|)")],
  digits = 6, caption = "Netflix — Regresión simple",
  align = c("l", "r", "r", "r")
)
Netflix — Regresión simple
Termino Estimate Std. Error Pr(>|t|)
(Intercept) 6.708467 0.090008 0.000000
log_Hours -0.013170 0.013903 0.343518
tabla_proyecto(
  data.frame(
    Metrica = c("R²", "R² ajustado"),
    Valor = c(summary(mod_n)$r.squared, summary(mod_n)$adj.r.squared)
  ),
  digits = 4, caption = "Netflix — Bondad de ajuste",
  align = c("l", "r")
)
Netflix — Bondad de ajuste
Metrica Valor
1e-04
R² ajustado 0e+00
ggplot(netflix, aes(x = log_Hours, y = Rating, color = GenreSimple)) +
  geom_point(alpha = 0.35, size = 1.2) +
  geom_smooth(aes(group = 1), method = "lm", se = TRUE, color = col_linea, fill = col_relleno_banda, linewidth = 1.2) +
  scale_color_manual(values = col_genero, name = "Género") +
  labs(
    title = titulo_grafica("Análisis de regresión"),
    subtitle = "Modelo lineal: calificación ~ log10(horas vistas)",
    x = expression(log[10](Horas~vistas)), y = "Calificación"
  ) +
  tema_proyecto +
  theme(legend.position = "bottom")
Netflix: regresión calificación ~ log10(horas).

Netflix: regresión calificación ~ log10(horas).

Conclusión

\(R^2\) = 10^{-4} (0.01% de variación explicada). Las horas en escala log no predicen significativamente la calificación: audiencia y opinión del público miden dimensiones distintas.


Escala logarítmica en horas vistas (Netflix)

Contexto

Las horas vistas tienen sesgo positivo: pocos títulos concentran cifras enormes y comprimen el resto en gráficos en escala original. \(\log_{10}\) equilibra la visualización sin alterar el orden de los títulos.

tabla_proyecto(
  data.frame(
    Escala = c("Original", "log10"),
    Media = c(mean(netflix$Hours.Viewed), mean(netflix$log_Hours)),
    Mediana = c(median(netflix$Hours.Viewed), median(netflix$log_Hours)),
    Sesgo = c(sesgo(netflix$Hours.Viewed), sesgo(netflix$log_Hours))
  ),
  digits = c(0, 0, 0, 2),
  format.args = list(big.mark = ","),
  caption = "Netflix — Comparación de escalas",
  align = c("l", "r", "r", "r")
)
Netflix — Comparación de escalas
Escala Media Mediana Sesgo
Original 10,982,615 2,900,000 11.04
log10 6 6 0.01
datos_escalas <- bind_rows(
  data.frame(valor = netflix$Hours.Viewed, Escala = "Original"),
  data.frame(valor = netflix$log_Hours, Escala = "log10")
)

ggplot(datos_escalas, aes(x = valor, fill = Escala)) +
  geom_histogram(bins = 40, alpha = 0.85, color = "white") +
  facet_wrap(~Escala, scales = "free_x", ncol = 2) +
  scale_fill_manual(values = c("Original" = col_terciario, "log10" = col_secundario), guide = "none") +
  scale_x_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
  labs(
    title = titulo_grafica("Comparación de escalas en horas vistas"),
    subtitle = "Escala original frente a transformación log10",
    x = NULL, y = "Frecuencia"
  ) +
  tema_proyecto
Netflix: escala original vs. log10.

Netflix: escala original vs. log10.

Conclusión

El sesgo cae al pasar a log₁₀; por eso covarianza, prueba t y regresión de series usan log_Hours.


Parte II — Análisis de películas (IMDb)

Basado en el proyecto de web scraping y análisis IMDb (Final Project - MCAA 2026-2 (2).R). Fuente: Fuentes/movies.json.

ruta_imdb <- "Fuentes/movies.json"
peliculas <- as.data.frame(fromJSON(ruta_imdb))

peliculas <- peliculas %>%
  mutate(
    `Release Date` = as.Date(`Release Date`, format = "%b %d %Y"),
    Year = as.numeric(format(`Release Date`, "%Y")),
    Rating = `IMDB Rating`,
    Decade = floor(Year / 10) * 10,
    Worldwide.Gross = as.numeric(gsub("[^0-9.]", "", as.character(`Worldwide Gross`))),
    Major.Genre = as.factor(`Major Genre`)
  ) %>%
  filter(!is.na(Rating), !is.na(Year))

n_peliculas <- nrow(peliculas)
top15 <- peliculas %>% arrange(desc(Rating)) %>% head(15)

peliculas_genero <- peliculas %>%
  filter(!is.na(Major.Genre)) %>%
  add_count(Major.Genre, name = "n_genero") %>%
  filter(n_genero >= 50) %>%
  mutate(Major.Genre = droplevels(Major.Genre))

resumen_genero <- peliculas_genero %>%
  group_by(Major.Genre) %>%
  summarise(
    Peliculas = n(),
    Promedio = mean(Rating, na.rm = TRUE),
    Mediana = median(Rating, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  arrange(desc(Promedio))

generos_facet <- levels(peliculas_genero$Major.Genre)

decade_genero <- peliculas %>%
  filter(Major.Genre %in% generos_facet, !is.na(Decade), Decade >= 1920) %>%
  group_by(Major.Genre, Decade) %>%
  summarise(
    Promedio = mean(Rating, na.rm = TRUE),
    Peliculas = n(),
    .groups = "drop"
  ) %>%
  filter(Peliculas >= 5)

Hay 2,988 películas con calificación y año válidos. Calificación media: 6.28 / 10.


1. Top 15 películas (IMDb)

Contexto

Identificamos las 15 películas con mayor calificación IMDb del conjunto. Este gráfico de barras resume el extremo superior de la distribución y facilita comparar títulos destacados.

ggplot(top15, aes(x = reorder(Title, Rating), y = Rating, fill = Rating)) +
  geom_col(show.legend = FALSE) +
  scale_fill_gradient(low = "#C9A0A4", high = col_primario) +
  coord_flip() +
  labs(
    title = titulo_grafica("Top 15 películas mejor calificadas", "IMDb"),
    subtitle = "Barras ordenadas por calificación IMDb",
    x = NULL, y = "Calificación IMDb"
  ) +
  tema_proyecto
Películas (IMDb): Top 15 por calificación.

Películas (IMDb): Top 15 por calificación.

Conclusión

El tope del ranking supera 8.8 puntos; las mejores películas concentran calificaciones muy altas, lo que refleja la selección positiva típica de catálogos curados o muy valorados por usuarios.


2. Distribución de probabilidades (IMDb)

Contexto

El histograma muestra cómo se reparten las calificaciones IMDb en todo el catálogo. Superponemos la curva normal teórica (media y desviación muestrales) para comparar la forma empírica con el modelo gaussiano, igual que en las series de Netflix.

mu_p <- mean(peliculas$Rating)
sd_p <- sd(peliculas$Rating)

tabla_proyecto(
  data.frame(
    Estadistico = c("Media", "Desv. est.", "Mínimo", "Máximo"),
    Valor = c(mu_p, sd_p, min(peliculas$Rating), max(peliculas$Rating))
  ),
  digits = 3,
  caption = "IMDb — Resumen de calificaciones",
  align = c("l", "r")
)
IMDb — Resumen de calificaciones
Estadistico Valor
Media 6.283
Desv. est. 1.252
Mínimo 1.400
Máximo 9.200
x_seq_p <- seq(min(peliculas$Rating), max(peliculas$Rating), length.out = 200)
curva_norm_p <- data.frame(Rating = x_seq_p, densidad = dnorm(x_seq_p, mu_p, sd_p))

ggplot(peliculas, aes(x = Rating)) +
  geom_histogram(aes(y = after_stat(density)), binwidth = 0.2, fill = col_secundario, color = "white", alpha = 0.85) +
  geom_line(data = curva_norm_p, aes(x = Rating, y = densidad), inherit.aes = FALSE, color = col_linea, linewidth = 1.4) +
  labs(
    title = titulo_grafica("Distribución de probabilidades", "IMDb"),
    subtitle = "Histograma con curva normal teórica superpuesta",
    x = "Calificación IMDb", y = "Densidad"
  ) +
  tema_proyecto
Películas (IMDb): histograma de calificaciones con curva normal.

Películas (IMDb): histograma de calificaciones con curva normal.

Conclusión

Las calificaciones se concentran cerca de 6.28 (DE = 1.25). La mayoría de películas supera 6.0 puntos; la curva normal es referencia visual, no prueba formal de normalidad.


3. Calificación por género — Prueba de hipótesis (IMDb)

Contexto

¿La calificación IMDb depende del género principal? Agrupamos los géneros con al menos 50 películas y comparamos sus distribuciones con boxplots lado a lado. Además aplicamos ANOVA de un factor (\(H_0\): medias iguales entre géneros).

tabla_proyecto(
  resumen_genero,
  digits = 3,
  caption = "IMDb — Calificación promedio por género (mínimo 50 películas)",
  align = c("l", "r", "r", "r")
)
IMDb — Calificación promedio por género (mínimo 50 películas)
Major.Genre Peliculas Promedio Mediana
Drama 738 6.773 6.90
Musical 50 6.448 6.85
Thriller/Suspense 233 6.361 6.40
Adventure 251 6.345 6.40
Action 392 6.115 6.20
Romantic Comedy 130 5.873 5.85
Comedy 635 5.854 6.00
Horror 209 5.676 5.60
anova_genero_p <- aov(Rating ~ Major.Genre, data = peliculas_genero)
anova_tabla_p <- as.data.frame(summary(anova_genero_p)[[1]])
anova_tabla_p$Fuente <- rownames(anova_tabla_p)
rownames(anova_tabla_p) <- NULL

tabla_proyecto(
  anova_tabla_p[, c("Fuente", "Df", "Sum Sq", "Mean Sq", "F value", "Pr(>F)")],
  digits = 4,
  caption = "IMDb — ANOVA: calificación por género",
  align = c("l", "r", "r", "r", "r", "r"),
  na = ""
)
IMDb — ANOVA: calificación por género
Fuente Df Sum Sq Mean Sq F value Pr(>F)
Major.Genre 7 403.1377 57.5911 40.9151 0
Residuals 2630 3701.9285 1.4076 NA NA
genero_orden <- resumen_genero %>% arrange(Promedio) %>% pull(Major.Genre)

ggplot(peliculas_genero, aes(x = reorder(Major.Genre, Rating, FUN = median), y = Rating, fill = Major.Genre)) +
  geom_boxplot(alpha = 0.75, width = 0.65, color = col_primario, outlier.color = col_outlier) +
  geom_jitter(width = 0.12, alpha = 0.15, size = 0.5, color = col_punto) +
  stat_summary(fun = mean, geom = "point", shape = 18, size = 3, color = col_punto) +
  scale_fill_manual(values = paleta_genero_imdb, guide = "none") +
  scale_x_discrete(limits = genero_orden) +
  labs(
    title = titulo_grafica("Calificación por género cinematográfico", "IMDb"),
    subtitle = "Géneros con ≥ 50 películas; rombo negro = media",
    x = "Género principal", y = "Calificación IMDb"
  ) +
  tema_proyecto +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
Películas (IMDb): calificación por género principal.

Películas (IMDb): calificación por género principal.

Conclusión

El género con mayor promedio es Drama (6.77); el menor, Horror (5.68). ANOVA: valor-p = <2e-16rechazamos \(H_0\). El género sí se asocia con diferencias en calificación, aunque el gráfico muestra matices (p. ej. comedia vs. terror).


4. Evolución del promedio por década (IMDb)

Contexto

Comparamos cómo cambia la calificación promedio por década de estreno dentro de cada género principal (con al menos 5 películas por década). El panel de gráficas con facet_wrap permite ver si la tendencia temporal es similar en drama, comedia, acción y otros géneros.

ggplot(decade_genero, aes(x = Decade, y = Promedio)) +
  geom_line(color = col_primario, linewidth = 1) +
  geom_point(color = col_secundario, size = 2) +
  facet_wrap(~Major.Genre, scales = "free_y", ncol = 3) +
  labs(
    title = titulo_grafica("Evolución de la calificación por década", "IMDb"),
    subtitle = "Un panel por género principal (mínimo 5 películas por década)",
    x = "Década de estreno", y = "Calificación promedio"
  ) +
  tema_proyecto +
  theme(strip.text = element_text(face = "bold", color = col_primario))
Películas (IMDb): evolución por década según género.

Películas (IMDb): evolución por década según género.

Conclusión

No todas las décadas ni todos los géneros evolucionan igual: algunos paneles muestran ligero ascenso o descenso según la época, mientras otros se mantienen estables. Esto indica que el año de estreno interactúa con el género; conviene interpretar la tendencia global junto con cada categoría, no solo el promedio de todo el catálogo.


5. Análisis de regresión (IMDb)

Contexto

¿Las películas más recientes reciben mejores o peores calificaciones? Ajustamos una regresión lineal simple (año → calificación), equivalente al análisis del script original.

mod_p <- lm(Rating ~ Year, data = peliculas)
coef_p <- as.data.frame(summary(mod_p)$coefficients)
coef_p$Termino <- rownames(coef_p)
rownames(coef_p) <- NULL

tabla_proyecto(
  coef_p[, c("Termino", "Estimate", "Std. Error", "Pr(>|t|)")],
  digits = 6, caption = "IMDb — Regresión calificación ~ año",
  align = c("l", "r", "r", "r")
)
IMDb — Regresión calificación ~ año
Termino Estimate Std. Error Pr(>|t|)
(Intercept) 27.642287 3.828858 0
Year -0.010689 0.001916 0
cor_ano <- cor(peliculas$Year, peliculas$Rating, use = "complete.obs")
ggplot(peliculas, aes(x = Year, y = Rating)) +
  geom_point(color = col_terciario, alpha = 0.35, size = 1) +
  geom_smooth(method = "lm", se = TRUE, color = col_linea, fill = col_relleno_banda, linewidth = 1.1) +
  labs(
    title = titulo_grafica("Análisis de regresión", "IMDb"),
    subtitle = "Modelo lineal: calificación ~ año de estreno",
    x = "Año de estreno", y = "Calificación IMDb"
  ) +
  tema_proyecto
Películas (IMDb): año de estreno vs. calificación con recta de regresión.

Películas (IMDb): año de estreno vs. calificación con recta de regresión.

Conclusión

Correlación año–rating: -0.102; \(R^2\) = 0.0103. La pendiente es significativa pero el modelo explica poca variación: el año de estreno por sí solo no determina la calificación IMDb en este conjunto amplio.


6. Nube de palabras — Títulos (IMDb)

Contexto

La nube de palabras resume las palabras más frecuentes en los títulos de las películas, tras eliminar muletillas y stop words (artículos, preposiciones y términos vacíos en inglés y español) para resaltar sustantivos y nombres relevantes.

if (requireNamespace("wordcloud", quietly = TRUE) &&
    requireNamespace("tm", quietly = TRUE)) {
  library(wordcloud)
  library(tm)

  palabras_vacias <- unique(c(
    stopwords("english"),
    stopwords("spanish"),
    "movie", "film", "story",
    "part", "chapter", "vs", "ii", "iii", "iv", "v", "vi",
    "the", "and", "of", "in", "to", "a", "an", "for", "on", "at", "by",
    "de", "la", "el", "los", "las", "y", "en", "un", "una", "del", "al"
  ))

  corpus_titulos <- Corpus(VectorSource(peliculas$Title))
  corpus_titulos <- tm_map(corpus_titulos, content_transformer(tolower))
  corpus_titulos <- tm_map(corpus_titulos, removePunctuation)
  corpus_titulos <- tm_map(corpus_titulos, removeNumbers)
  corpus_titulos <- tm_map(corpus_titulos, removeWords, palabras_vacias)
  corpus_titulos <- tm_map(corpus_titulos, stripWhitespace)

  tdm <- TermDocumentMatrix(corpus_titulos)
  m <- as.matrix(tdm)
  freq <- sort(rowSums(m), decreasing = TRUE)
  freq <- freq[nchar(names(freq)) > 2]

  par(bg = "white", fg = col_texto_eje, mar = rep(0.5, 4))
  wordcloud(
    words = names(freq),
    freq = freq,
    random.order = FALSE,
    colors = c(col_primario, col_secundario, col_terciario, col_linea, "#9B5DE5", "#F77F00"),
    max.words = 80,
    scale = c(3, 0.5)
  )
} else {
  plot.new()
  text(0.5, 0.5, "Instale los paquetes wordcloud y tm para ver esta figura")
}
Películas (IMDb): nube de palabras sin muletillas.

Películas (IMDb): nube de palabras sin muletillas.

Conclusión

Tras quitar muletillas, emergen términos con significado narrativo o temático (nombres, lugares, conceptos) en lugar de artículos repetidos. La nube refleja la diversidad léxica del catálogo y no debe leerse como ranking de películas, sino como exploración del lenguaje usado en los títulos.


Conclusión general

Netflix

  • Correlación fuerte entre valoraciones y horas (log), pero casi nula entre horas y calificación.
  • Disponibilidad global se vincula con más horas vistas (log).
  • Género y disponibilidad global parecen independientes (\(\chi^2\)).
  • La regresión confirma que audiencia ≠ calificación (\(R^2\) muy bajo).

Películas (IMDb)

  • Las calificaciones se concentran en valores medios-altos; la normal teórica es solo referencia.
  • El Top 15 muestra el extremo superior del ranking.
  • El género se relaciona con diferencias en calificación (ANOVA y boxplots).
  • La evolución por década varía según el género en los paneles de facet_wrap.
  • El año de estreno explica poca variación individual en la regresión.

Comparación entre ambos catálogos

Los dos catálogos difieren en tamaño y en tipos de variables disponibles. La tabla resume filas, columnas y cuántas variables son numéricas, categóricas, de fecha o lógicas.

Comparación estructural de los catálogos
Catalogo Filas Columnas Numéricas Categóricas Fechas Lógicas
Netflix (archivo original) 18332 9 0 9 0 0
Netflix (datos analizados) 14633 11 4 7 0 0
IMDb (archivo original) 3201 16 8 8 0 0
IMDb (datos analizados) 2988 21 12 8 1 0