Práctico 02: EDA Netflix con Visualizaciones
🎬 Parte 1: Setup y Carga de Datos
# Importar librerías que vamos a usar
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Configurar el estilo de visualizaciones
plt.style.use('default') # establecer estilo visual (ej: 'default', 'seaborn', 'classic')
sns.set_palette("viridis") # definir paleta de colores (ej: 'husl', 'Set1', 'viridis')
plt.rcParams['figure.figsize'] = (10, 6) # tamaño por defecto
print("✅ Setup completo!")
🎬 Paso 2: Cargar y Explorar Dataset Netflix
# === CARGAR DATOS DE NETFLIX ===
# 1. Cargar el dataset desde una URL
url = "https://raw.githubusercontent.com/swapnilg4u/Netflix-Data-Analysis/refs/heads/master/netflix_titles.csv"
df = pd.read_csv(url) # función para leer archivos CSV desde URL o archivo local
print("🎬 DATASET: Netflix Titles")
print(f" 📊 Forma: {df.shape}")
print(f" 📋 Columnas: {list(df.columns)}")
# 2. Explorar los datos básicamente
print("\n🔍 Primeras 5 filas:")
print(df.head()) # método para mostrar las primeras filas del DataFrame
# 3. Información sobre tipos de datos y memoria
print("\n📋 INFORMACIÓN GENERAL:")
print(df.info()) # método que muestra tipos de datos, memoria y valores no nulos
# 4. Estadísticas básicas para columnas numéricas
print("\n📊 ESTADÍSTICAS BÁSICAS:")
print(df.describe()) # método que calcula estadísticas descriptivas (mean, std, min, max, etc.)
🎬 DATASET: Netflix Titles
📊 Forma: (6234, 12)
📋 Columnas: ['show_id', 'type', 'title', 'director', 'cast', 'country', 'date_added', 'release_year', 'rating', 'duration', 'listed_in', 'description']
🔍 Primeras 5 filas:
show_id type title \
0 81145628 Movie Norm of the North: King Sized Adventure
1 80117401 Movie Jandino: Whatever it Takes
2 70234439 TV Show Transformers Prime
3 80058654 TV Show Transformers: Robots in Disguise
4 80125979 Movie #realityhigh
director \
0 Richard Finn, Tim Maltby
1 NaN
2 NaN
3 NaN
4 Fernando Lebrija
cast \
0 Alan Marriott, Andrew Toth, Brian Dobson, Cole...
1 Jandino Asporaat
2 Peter Cullen, Sumalee Montano, Frank Welker, J...
3 Will Friedle, Darren Criss, Constance Zimmer, ...
4 Nesta Cooper, Kate Walsh, John Michael Higgins...
country date_added release_year \
0 United States, India, South Korea, China September 9, 2019 2019
1 United Kingdom September 9, 2016 2016
2 United States September 8, 2018 2013
3 United States September 8, 2018 2016
4 United States September 8, 2017 2017
rating duration listed_in \
0 TV-PG 90 min Children & Family Movies, Comedies
1 TV-MA 94 min Stand-Up Comedy
2 TV-Y7-FV 1 Season Kids' TV
3 TV-Y7 1 Season Kids' TV
4 TV-14 99 min Comedies
description
0 Before planning an awesome wedding for his gra...
1 Jandino Asporaat riffs on the challenges of ra...
2 With the help of three human allies, the Autob...
3 When a prison ship crash unleashes hundreds of...
4 When nerdy high schooler Dani finally attracts...
📋 INFORMACIÓN GENERAL:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6234 entries, 0 to 6233
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 show_id 6234 non-null int64
1 type 6234 non-null object
2 title 6234 non-null object
3 director 4265 non-null object
4 cast 5664 non-null object
5 country 5758 non-null object
6 date_added 6223 non-null object
7 release_year 6234 non-null int64
8 rating 6224 non-null object
9 duration 6234 non-null object
10 listed_in 6234 non-null object
11 description 6234 non-null object
dtypes: int64(2), object(10)
memory usage: 584.6+ KB
None
📊 ESTADÍSTICAS BÁSICAS:
show_id release_year
count 6.234000e+03 6234.00000
mean 7.670368e+07 2013.35932
std 1.094296e+07 8.81162
min 2.477470e+05 1925.00000
25% 8.003580e+07 2013.00000
50% 8.016337e+07 2016.00000
75% 8.024489e+07 2018.00000
max 8.123573e+07 2020.00000
📊 Paso 3: Análisis de Datos Faltantes
# === DETECTAR Y VISUALIZAR DATOS FALTANTES ===
# 1. Calcular datos faltantes por columna
missing_data = df.isnull().sum().sort_values(ascending=False) # detectar valores nulos y contar por columna
missing_percent = (df.isnull().sum() / len(df) * 100).sort_values(ascending=False) # calcular porcentaje de nulos
print("❌ DATOS FALTANTES:")
print(missing_data[missing_data > 0])
print("\n📊 PORCENTAJES:")
print(missing_percent[missing_percent > 0])
# 2. Crear visualización de datos faltantes
plt.figure(figsize=(12, 6))
# Subplot 1: Gráfico de barras de datos faltantes
plt.subplot(1, 2, 1)
sns.barplot(x=missing_percent[missing_percent > 0].values, # función para crear barras horizontales
y=missing_percent[missing_percent > 0].index,
hue=missing_percent[missing_percent > 0].index,
palette='Reds_r')
plt.title('Porcentaje de Datos Faltantes por Columna')
plt.xlabel('Porcentaje (%)')
# Subplot 2: Heatmap de datos faltantes
plt.subplot(1, 2, 2)
sns.heatmap(df.isnull(), cbar=True, cmap='viridis') # función para crear mapa de calor de valores booleanos
plt.title('Patrón de Datos Faltantes')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show() # función para mostrar/renderizar los gráficos en pantalla
❌ DATOS FALTANTES:
director 1969
cast 570
country 476
date_added 11
rating 10
dtype: int64
📊 PORCENTAJES:
director 31.584857
cast 9.143407
country 7.635547
date_added 0.176452
rating 0.160411
dtype: float64
🔍 Paso 4: Detección de Valores Atípicos y Anomalías
# === DETECCIÓN DE OUTLIERS Y ANOMALÍAS ===
# 1. Analizar años de lanzamiento atípicos
print("🔍 ANÁLISIS DE OUTLIERS EN AÑOS:")
df['release_year_clean'] = pd.to_numeric(df['release_year'], errors='coerce')
year_stats = df['release_year_clean'].describe()
print(year_stats)
# Identificar años sospechosos
very_old = df[df['release_year_clean'] < 1950]
future_releases = df[df['release_year_clean'] > 2025]
print(f"\n⚠️ Contenido muy antiguo (< 1950): {len(very_old)} títulos")
if len(very_old) > 0:
print("Ejemplos:")
print(very_old[['title', 'release_year', 'type']].head())
print(f"\n⚠️ Lanzamientos futuros (> 2025): {len(future_releases)} títulos")
if len(future_releases) > 0:
print("Ejemplos:")
print(future_releases[['title', 'release_year', 'type']].head())
# 2. Crear visualizaciones para detectar outliers
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# Gráfico 1: Box plot para detectar outliers en años
sns.boxplot(data=df, y='release_year_clean', ax=axes[0, 0]) # función para mostrar outliers con cajas
axes[0, 0].set_title('Box Plot - Años de Lanzamiento (Outliers)')
axes[0, 0].set_ylabel('Año de Lanzamiento')
# Gráfico 2: Histograma de años para ver distribución
axes[0, 1].hist(df['release_year_clean'].dropna(), bins=50, alpha=0.7, color='skyblue', edgecolor='black') # histograma con muchos bins
axes[0, 1].set_title('Distribución de Años de Lanzamiento')
axes[0, 1].set_xlabel('Año')
axes[0, 1].set_ylabel('Frecuencia')
axes[0, 1].axvline(df['release_year_clean'].mean(), color='red', linestyle='--', label='Media')
axes[0, 1].legend()
# Gráfico 3: Análisis de títulos duplicados
title_counts = df['title'].value_counts() # contar frecuencias de títulos
duplicated_titles = title_counts[title_counts > 1]
print(f"\n🔄 TÍTULOS DUPLICADOS: {len(duplicated_titles)} títulos aparecen múltiples veces")
if len(duplicated_titles) > 0:
top_duplicates = duplicated_titles.head(10)
sns.barplot(y=top_duplicates.index, x=top_duplicates.values, ax=axes[1, 0], hue=top_duplicates.index, palette='Reds') # barras horizontales
axes[1, 0].set_title('Top 10 Títulos Duplicados')
axes[1, 0].set_xlabel('Cantidad de Apariciones')
else:
axes[1, 0].text(0.5, 0.5, 'No se encontraron\ntítulos duplicados',
ha='center', va='center', transform=axes[1, 0].transAxes)
axes[1, 0].set_title('Títulos Duplicados - Sin Datos')
# Gráfico 4: Longitud de títulos (outliers en texto)
df['title_length'] = df['title'].str.len()
title_length_stats = df['title_length'].describe()
sns.boxplot(data=df, y='title_length', ax=axes[1, 1]) # box plot para longitud de títulos
axes[1, 1].set_title('Box Plot - Longitud de Títulos')
axes[1, 1].set_ylabel('Caracteres en el Título')
# Identificar títulos extremadamente largos o cortos
very_long_titles = df[df['title_length'] > df['title_length'].quantile(0.99)]
very_short_titles = df[df['title_length'] < 5]
print(f"\n📏 TÍTULOS EXTREMOS:")
print(f" Muy largos (> percentil 99): {len(very_long_titles)} títulos")
if len(very_long_titles) > 0:
print(f" Ejemplo más largo: '{very_long_titles.loc[very_long_titles['title_length'].idxmax(), 'title']}'")
print(f" Muy cortos (< 5 caracteres): {len(very_short_titles)} títulos")
if len(very_short_titles) > 0:
print(" Ejemplos:")
print(very_short_titles[['title', 'title_length', 'type']].head())
plt.tight_layout()
plt.show()
print("✅ Análisis de outliers completado!")
🔍 ANÁLISIS DE OUTLIERS EN AÑOS:
count 6234.00000
mean 2013.35932
std 8.81162
min 1925.00000
25% 2013.00000
50% 2016.00000
75% 2018.00000
max 2020.00000
Name: release_year_clean, dtype: float64
⚠️ Contenido muy antiguo (< 1950): 16 títulos
Ejemplos:
title release_year type
2005 Know Your Enemy - Japan 1945 Movie
2006 Let There Be Light 1946 Movie
2009 Nazi Concentration Camps 1945 Movie
2011 Prelude to War 1942 Movie
2012 San Pietro 1945 Movie
⚠️ Lanzamientos futuros (> 2025): 0 títulos
🔄 TÍTULOS DUPLICADOS: 57 títulos aparecen múltiples veces
📏 TÍTULOS EXTREMOS:
Muy largos (> percentil 99): 62 títulos
Ejemplo más largo: 'Jim & Andy: The Great Beyond - Featuring a Very Special, Contractually Obligated Mention of Tony Clifton'
Muy cortos (< 5 caracteres): 173 títulos
Ejemplos:
title title_length type
19 Love 4 Movie
41 PK 2 Movie
45 ATM 3 Movie
89 5CM 3 Movie
209 ARQ 3 Movie
✅ Análisis de outliers completado!
🎭 Paso 5: Análisis de Tipos de Contenido
# === ANÁLISIS DE TIPOS DE CONTENIDO ===
# 1. Calcular frecuencias
type_counts = df['type'].value_counts() # método para contar frecuencias de cada categoría única
type_percent = df['type'].value_counts(normalize=True) * 100 # mismo método pero calculando porcentajes
print("🎭 TIPOS DE CONTENIDO:")
print(type_counts)
print(f"\nPorcentajes:")
print(type_percent)
# 2. Crear visualizaciones múltiples
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
# Gráfico 1: Countplot básico
sns.countplot(data=df, x='type', ax=axes[0, 0], palette='Set2') # función para contar y graficar categorías
axes[0, 0].set_title('Distribución: Movies vs TV Shows')
axes[0, 0].set_ylabel('Cantidad')
# Gráfico 2: Pie chart
axes[0, 1].pie(type_counts.values, labels=type_counts.index, # función para crear gráfico circular/torta
autopct='%1.1f%%', startangle=90, colors=['skyblue', 'lightcoral'])
axes[0, 1].set_title('Proporción Movies vs TV Shows')
# Gráfico 3: Barplot horizontal
sns.barplot(y=type_counts.index, x=type_counts.values, ax=axes[1, 0], palette='viridis') # función para barras horizontales
axes[1, 0].set_title('Cantidad por Tipo (Horizontal)')
axes[1, 0].set_xlabel('Cantidad')
# Gráfico 4: Donut chart (más avanzado)
wedges, texts, autotexts = axes[1, 1].pie(type_counts.values, labels=type_counts.index, # misma función de torta para donut
autopct='%1.1f%%', startangle=90,
colors=['gold', 'lightgreen'])
# Crear el hueco del donut
centre_circle = plt.Circle((0,0), 0.70, fc='white')
axes[1, 1].add_artist(centre_circle)
axes[1, 1].set_title('Donut Chart - Tipos de Contenido')
plt.tight_layout()
plt.show()
🎭 TIPOS DE CONTENIDO:
type
Movie 4265
TV Show 1969
Name: count, dtype: int64
Porcentajes:
type
Movie 68.415143
TV Show 31.584857
Name: proportion, dtype: float64
📅 Paso 6: Análisis Temporal
# === ANÁLISIS DE TENDENCIAS TEMPORALES ===
# 1. Preparar datos temporales
df['release_year'] = pd.to_numeric(df['release_year'], errors='coerce') # convertir a numérico, NaN si no es posible
yearly_releases = df['release_year'].value_counts().sort_index() # contar frecuencias por año y ordenar
# Filtrar años recientes para mejor visualización
recent_years = yearly_releases[yearly_releases.index >= 2000]
# 2. Crear visualizaciones temporales múltiples
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# Gráfico 1: Línea temporal
axes[0, 0].plot(recent_years.index, recent_years.values, # función para crear líneas conectando puntos
marker='o', linewidth=2, markersize=4, color='darkblue')
axes[0, 0].set_title('Cantidad de Contenido por Año (2000-2021)')
axes[0, 0].set_xlabel('Año')
axes[0, 0].set_ylabel('Cantidad de Títulos')
axes[0, 0].grid(True, alpha=0.3)
# Gráfico 2: Área bajo la curva
axes[0, 1].fill_between(recent_years.index, recent_years.values, # función para rellenar área bajo la línea
alpha=0.7, color='lightcoral')
axes[0, 1].set_title('Área - Lanzamientos por Año')
axes[0, 1].set_xlabel('Año')
axes[0, 1].set_ylabel('Cantidad')
# Gráfico 3: Análisis por tipo de contenido
df_recent = df[df['release_year'] >= 2010]
yearly_by_type = df_recent.groupby(['release_year', 'type']).size().unstack(fill_value=0)
yearly_by_type.plot(kind='bar', ax=axes[1, 0], # tipo de gráfico con barras lado a lado (no apiladas)
color=['skyblue', 'lightgreen'], alpha=0.8)
axes[1, 0].set_title('Lanzamientos por Tipo (2010-2021)')
axes[1, 0].set_xlabel('Año')
axes[1, 0].set_ylabel('Cantidad')
axes[1, 0].legend(title='Tipo')
# Gráfico 4: Heatmap de lanzamientos por década y tipo
df['decade'] = (df['release_year'] // 10) * 10
decade_type = df.groupby(['decade', 'type']).size().unstack(fill_value=0)
sns.heatmap(decade_type, annot=True, fmt='d', ax=axes[1, 1], cmap='YlOrRd') # función para mapa de calor con anotaciones
axes[1, 1].set_title('Heatmap: Lanzamientos por Década y Tipo')
plt.tight_layout()
plt.show()
print("📅 AÑOS CON MÁS LANZAMIENTOS:")
print(yearly_releases.tail(10))
📅 AÑOS CON MÁS LANZAMIENTOS:
release_year
2011 136
2012 183
2013 237
2014 288
2015 517
2016 830
2017 959
2018 1063
2019 843
2020 25
Name: count, dtype: int64
🌍 Paso 7: Análisis Geográfico
# === ANÁLISIS DE PAÍSES CON VISUALIZACIONES ===
# 1. Preparar datos de países (limpiar y separar países múltiples)
df_countries = df.dropna(subset=['country']).copy()
# Separar países que están separados por comas
countries_expanded = df_countries['country'].str.split(', ').explode()
country_counts = countries_expanded.value_counts().head(20) # contar frecuencias de países y tomar top 20
print("🌍 TOP 20 PAÍSES CON MÁS CONTENIDO:")
print(country_counts)
# 2. Crear visualizaciones geográficas
fig, axes = plt.subplots(2, 2, figsize=(18, 12))
# Gráfico 1: Top 15 países - barras horizontales
top_15_countries = country_counts.head(15)
sns.barplot(y=top_15_countries.index, x=top_15_countries.values, # función para barras horizontales
ax=axes[0, 0], hue=top_15_countries.index, palette='viridis')
axes[0, 0].set_title('Top 15 Países con Más Contenido')
axes[0, 0].set_xlabel('Cantidad de Títulos')
# Gráfico 2: Treemap simulado con scatter
top_10 = country_counts.head(10)
colors = plt.cm.Set3(np.linspace(0, 1, len(top_10)))
axes[0, 1].scatter(range(len(top_10)), top_10.values, # función para gráfico de burbujas/puntos
s=top_10.values*3, c=colors, alpha=0.7)
for i, (country, count) in enumerate(top_10.items()):
axes[0, 1].annotate(f'{country}\n({count})',
(i, count), ha='center', va='center')
axes[0, 1].set_title('Bubble Chart - Top 10 Países')
axes[0, 1].set_xticks(range(len(top_10)))
axes[0, 1].set_xticklabels(top_10.index, rotation=45)
# Gráfico 3: Análisis de contenido por país y tipo
top_countries = country_counts.head(10).index
df_top_countries = df_countries[df_countries['country'].isin(top_countries)]
country_type = df_top_countries.groupby(['country', 'type']).size().unstack(fill_value=0)
country_type.plot(kind='bar', ax=axes[1, 0], # tipo de gráfico con barras agrupadas lado a lado
color=['lightblue', 'salmon'], width=0.8)
axes[1, 0].set_title('Movies vs TV Shows por País (Top 10)')
axes[1, 0].set_ylabel('Cantidad')
axes[1, 0].legend(title='Tipo')
axes[1, 0].tick_params(axis='x', rotation=45)
# Gráfico 4: Heatmap de correlación entre países principales
# Crear matriz de co-ocurrencia de países
from itertools import combinations
co_occurrence = {}
for countries_str in df_countries['country']:
if pd.notna(countries_str) and ',' in countries_str:
countries_list = [c.strip() for c in countries_str.split(',')]
for c1, c2 in combinations(countries_list, 2):
if c1 in top_10.index and c2 in top_10.index:
key = tuple(sorted([c1, c2]))
co_occurrence[key] = co_occurrence.get(key, 0) + 1
# Crear matriz para heatmap
co_matrix = np.zeros((len(top_10), len(top_10)))
for i, c1 in enumerate(top_10.index):
for j, c2 in enumerate(top_10.index):
if i != j:
key = tuple(sorted([c1, c2]))
co_matrix[i, j] = co_occurrence.get(key, 0)
sns.heatmap(co_matrix, annot=True, fmt='.0f', # función para mapa de calor (formato .0f para números flotantes)
xticklabels=top_10.index, yticklabels=top_10.index,
ax=axes[1, 1], cmap='Reds')
axes[1, 1].set_title('Co-producción entre Países')
axes[1, 1].tick_params(axis='x', rotation=45)
axes[1, 1].tick_params(axis='y', rotation=0)
plt.tight_layout()
plt.show()
🌍 TOP 20 PAÍSES CON MÁS CONTENIDO:
country
United States 2609
India 838
United Kingdom 601
Canada 318
France 271
Japan 231
Spain 178
South Korea 162
Germany 151
Mexico 129
Australia 126
China 120
Hong Kong 97
Turkey 87
Taiwan 75
Argentina 68
Italy 67
Belgium 66
Brazil 66
Thailand 56
Name: count, dtype: int64
🎨 Paso 8: Análisis de Géneros y Ratings
# === ANÁLISIS DE RATINGS Y GÉNEROS ===
# 1. Preparar datos de ratings
rating_counts = df['rating'].value_counts().head(10) # contar frecuencias de ratings y tomar top 10
print("🔞 TOP 10 RATINGS MÁS COMUNES:")
print(rating_counts)
# 2. Crear dashboard de ratings
fig, axes = plt.subplots(2, 3, figsize=(20, 12))
# Gráfico 1: Countplot de ratings
sns.countplot(data=df, x='rating', order=rating_counts.index, # función para contar y graficar categorías ordenadas
ax=axes[0, 0], palette='Set1', hue='rating', legend=False) # Corregido: Añadir hue y legend=False
axes[0, 0].set_title('Distribución de Ratings')
axes[0, 0].tick_params(axis='x', rotation=45)
# Gráfico 2: Ratings por tipo de contenido
sns.countplot(data=df, x='rating', hue='type', # misma función pero separando por otra variable (hue)
order=rating_counts.index, ax=axes[0, 1])
axes[0, 1].set_title('Ratings por Tipo de Contenido')
axes[0, 1].tick_params(axis='x', rotation=45)
axes[0, 1].legend(title='Tipo')
# Gráfico 3: Pie chart de ratings principales
top_5_ratings = rating_counts.head(5)
axes[0, 2].pie(top_5_ratings.values, labels=top_5_ratings.index, # función para gráfico circular/torta
autopct='%1.1f%%', startangle=90)
axes[0, 2].set_title('Top 5 Ratings - Proporción')
# Gráfico 4: Box plot de años de lanzamiento por rating
df_clean = df.dropna(subset=['rating', 'release_year']).copy() # Añadir .copy() para evitar SettingWithCopyWarning
top_ratings = rating_counts.head(6).index
df_top_ratings = df_clean[df_clean['rating'].isin(top_ratings)]
sns.boxplot(data=df_top_ratings, x='rating', y='release_year', # función para mostrar distribución con cajas y bigotes
order=top_ratings, ax=axes[1, 0])
axes[1, 0].set_title('Distribución de Años por Rating')
axes[1, 0].tick_params(axis='x', rotation=45)
# Gráfico 5: Violin plot alternativo
sns.violinplot(data=df_top_ratings, x='rating', y='release_year', # función para mostrar densidad como "violines"
order=top_ratings, ax=axes[1, 1], palette='muted', hue='rating', legend=False) # Corregido: Añadir hue y legend=False
axes[1, 1].set_title('Densidad de Años por Rating')
axes[1, 1].tick_params(axis='x', rotation=45)
# Gráfico 6: Heatmap de rating vs década
df_clean.loc[:, 'decade'] = (df_clean['release_year'] // 10) * 10 # Corregido: Usar .loc para evitar SettingWithCopyWarning
rating_decade = df_clean.groupby(['rating', 'decade']).size().unstack(fill_value=0)
# Filtrar solo ratings principales y décadas recientes
rating_decade_filtered = rating_decade.loc[top_ratings, rating_decade.columns >= 1980]
sns.heatmap(rating_decade_filtered, annot=True, fmt='d', # función para mapa de calor con valores enteros
ax=axes[1, 2], cmap='Blues')
axes[1, 2].set_title('Heatmap: Rating vs Década')
axes[1, 2].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
🔞 TOP 10 RATINGS MÁS COMUNES:
rating
TV-MA 2027
TV-14 1698
TV-PG 701
R 508
PG-13 286
NR 218
PG 184
TV-Y7 169
TV-G 149
TV-Y 143
Name: count, dtype: int64
📊 Paso 9: Dashboard Final
# === CREAR DASHBOARD FINAL INTERACTIVO ===
# 1. Calcular estadísticas finales
total_titles = len(df)
total_movies = len(df[df['type'] == 'Movie'])
total_shows = len(df[df['type'] == 'TV Show'])
latest_year = df['release_year'].max()
oldest_year = df['release_year'].min()
print(f"📊 RESUMEN EJECUTIVO NETFLIX:")
print(f" Total de títulos: {total_titles:,}")
print(f" Películas: {total_movies:,} ({total_movies/total_titles*100:.1f}%)")
print(f" Series: {total_shows:,} ({total_shows/total_titles*100:.1f}%)")
print(f" Rango de años: {oldest_year} - {latest_year}")
# 2. Crear figura principal con subplots
fig = plt.figure(figsize=(20, 15))
gs = fig.add_gridspec(3, 4, hspace=0.3, wspace=0.3)
# Dashboard panel 1: Tipos (grande)
ax1 = fig.add_subplot(gs[0, :2])
type_data = df['type'].value_counts()
colors = ['#FF6B6B', '#4ECDC4']
wedges, texts, autotexts = ax1.pie(type_data.values, labels=type_data.index, # función para gráfico de torta en dashboard
autopct='%1.1f%%', startangle=90,
colors=colors, textprops={'fontsize': 12})
ax1.set_title('Distribución Movies vs TV Shows', fontsize=14, fontweight='bold')
# Dashboard panel 2: Timeline
ax2 = fig.add_subplot(gs[0, 2:])
yearly = df.groupby('release_year').size()
recent_years = yearly[yearly.index >= 2000]
ax2.fill_between(recent_years.index, recent_years.values, color='#FF6B6B', alpha=0.7) # función para rellenar área bajo curva
ax2.plot(recent_years.index, recent_years.values, color='darkred', linewidth=2)
ax2.set_title('Evolución Temporal (2000+)', fontsize=14, fontweight='bold')
ax2.set_xlabel('Año')
ax2.set_ylabel('Títulos Lanzados')
ax2.grid(True, alpha=0.3)
# Dashboard panel 3: Top países
ax3 = fig.add_subplot(gs[1, :2])
country_clean = df.dropna(subset=['country'])['country'].str.split(', ').explode()
top_countries = country_clean.value_counts().head(10)
sns.barplot(y=top_countries.index, x=top_countries.values, hue=top_countries.index, ax=ax3, palette='viridis') # función para barras horizontales
ax3.set_title('Top 10 Países Productores', fontsize=14, fontweight='bold')
ax3.set_xlabel('Cantidad de Títulos')
# Dashboard panel 4: Ratings
ax4 = fig.add_subplot(gs[1, 2:])
top_ratings = df['rating'].value_counts().head(8)
sns.countplot(data=df[df['rating'].isin(top_ratings.index)], # función para contar categorías separadas por hue
x='rating', hue='type', order=top_ratings.index, ax=ax4)
ax4.set_title('Ratings por Tipo de Contenido', fontsize=14, fontweight='bold')
ax4.tick_params(axis='x', rotation=45)
ax4.legend(title='Tipo')
# Dashboard panel 5: Heatmap temporal (full width)
ax5 = fig.add_subplot(gs[2, :])
df['decade'] = (df['release_year'] // 10) * 10
year_type_decade = df.groupby(['decade', 'type']).size().unstack(fill_value=0)
sns.heatmap(year_type_decade.T, annot=True, fmt='d', ax=ax5, # función para mapa de calor transpuesto (.T)
cmap='YlOrRd', cbar_kws={'label': 'Cantidad de Títulos'})
ax5.set_title('Evolución por Décadas y Tipo de Contenido', fontsize=14, fontweight='bold')
ax5.set_xlabel('Década')
ax5.set_ylabel('Tipo de Contenido')
# Guardar el dashboard
plt.suptitle('NETFLIX CONTENT ANALYSIS DASHBOARD', fontsize=18, fontweight='bold', y=0.95) # Corregido: Eliminar emoji
plt.savefig(f'netflix_dashboard.png', dpi=300, bbox_inches='tight') # función para guardar figura como archivo
plt.show()
print("\n✅ Dashboard guardado como 'netflix_dashboard.png'")
📊 RESUMEN EJECUTIVO NETFLIX:
Total de títulos: 6,234
Películas: 4,265 (68.4%)
Series: 1,969 (31.6%)
Rango de años: 1925 - 2020
✅ Dashboard guardado como 'netflix_dashboard.png'
📋 Paso 10: Análisis de Géneros y Duración
# === ANÁLISIS AVANZADO DE GÉNEROS ===
# 1. Separar géneros que están en lista separada por comas
genres_expanded = df.dropna(subset=['listed_in'])['listed_in'].str.split(', ').explode()
top_genres = genres_expanded.value_counts().head(15) # contar frecuencias de géneros y tomar top 15
print("🎬 TOP 15 GÉNEROS MÁS POPULARES:")
print(top_genres)
# 2. Crear visualización de géneros
fig, axes = plt.subplots(2, 2, figsize=(16, 10))
# Word cloud simulado con scatter
axes[0, 0].scatter(range(len(top_genres)), top_genres.values, # función para gráfico de burbujas (scatter)
s=top_genres.values*2, alpha=0.6, c=range(len(top_genres)), cmap='viridis')
for i, (genre, count) in enumerate(top_genres.items()):
axes[0, 0].annotate(genre, (i, count), ha='center', va='center', fontsize=8)
axes[0, 0].set_title('Bubble Chart - Géneros Populares')
axes[0, 0].set_xticks([])
# Barras horizontales de géneros
sns.barplot(y=top_genres.head(10).index, x=top_genres.head(10).values, # función para barras horizontales
ax=axes[0, 1], hue=top_genres.head(10).index ,palette='Set2')
axes[0, 1].set_title('Top 10 Géneros')
axes[0, 1].set_xlabel('Cantidad')
# Análisis de duración de películas
movies_df = df[df['type'] == 'Movie'].copy()
movies_df['duration_min'] = movies_df['duration'].str.extract(r'(\d+)').astype(float) # Corregido: Usar raw string
axes[1, 0].hist(movies_df['duration_min'], bins=30, alpha=0.7, color='lightcoral', edgecolor='black') # función para histograma de frecuencias
axes[1, 0].set_title('Distribución Duración Películas')
axes[1, 0].set_xlabel('Duración (minutos)')
axes[1, 0].set_ylabel('Frecuencia')
axes[1, 0].axvline(movies_df['duration_min'].mean(), color='red', linestyle='--',
label=f'Media: {movies_df["duration_min"].mean():.0f} min')
axes[1, 0].legend()
# Análisis de temporadas de series
tv_shows_df = df[df['type'] == 'TV Show'].copy()
tv_shows_df['seasons'] = tv_shows_df['duration'].str.extract(r'(\d+)').astype(float) # Corregido: Usar raw string
axes[1, 1].hist(tv_shows_df['seasons'], bins=range(1, 20), alpha=0.7, color='lightblue', edgecolor='black') # función para histograma con bins personalizados
axes[1, 1].set_title('Distribución Temporadas TV Shows')
axes[1, 1].set_xlabel('Número de Temporadas')
axes[1, 1].set_ylabel('Frecuencia')
axes[1, 1].axvline(tv_shows_df['seasons'].mean(), color='blue', linestyle='--',
label=f'Media: {tv_shows_df["seasons"].mean():.1f} temporadas')
axes[1, 1].legend()
plt.tight_layout()
plt.show()
print(f"\n📊 ESTADÍSTICAS DE DURACIÓN:")
print(f" Película promedio: {movies_df['duration_min'].mean():.0f} minutos")
print(f" Película más corta: {movies_df['duration_min'].min():.0f} minutos")
print(f" Película más larga: {movies_df['duration_min'].max():.0f} minutos")
print(f" Serie promedio: {tv_shows_df['seasons'].mean():.1f} temporadas")
print(f" Serie más larga: {tv_shows_df['seasons'].max():.0f} temporadas")
🎬 TOP 15 GÉNEROS MÁS POPULARES:
listed_in
International Movies 1927
Dramas 1623
Comedies 1113
International TV Shows 1001
Documentaries 668
TV Dramas 599
Action & Adventure 597
Independent Movies 552
TV Comedies 436
Thrillers 392
Children & Family Movies 378
Romantic Movies 376
Crime TV Shows 363
Kids' TV 328
Stand-Up Comedy 281
Name: count, dtype: int64
📊 ESTADÍSTICAS DE DURACIÓN:
Película promedio: 99 minutos
Película más corta: 3 minutos
Película más larga: 312 minutos
Serie promedio: 1.8 temporadas
Serie más larga: 15 temporadas