Curso
Con la nueva cultura de ver series y películas en maratón, los usuarios consumen contenido a gran ritmo gracias a servicios como Netflix, Prime Video, Hulu y Disney+. Algunas de estas plataformas, como Hulu y YouTube TV, también ofrecen retransmisiones en directo de deportes, conciertos y canales de noticias. El directo aún no lo han adoptado algunas plataformas como Netflix.
Las plataformas de streaming dan más libertad para ver tus series y películas favoritas cuando quieras y en cualquier dispositivo. Estos servicios atraen a públicos jóvenes y actuales por su amplia variedad de contenidos. Además, permiten recuperar programas que te hayas perdido cuando te venga bien. En este tutorial, analizarás datos de películas de Netflix, Prime Video, Hulu y Disney+ e intentarás entender a su audiencia.

Entender el dataset
Este conjunto de datos incluye solo películas disponibles en plataformas de streaming como Netflix, Prime Video, Hulu y Disney+. Puedes descargarlo de Kaggle aquí.
Descripción detallada de los atributos:
- ID: identificador único de cada registro.
- Title: nombre de la película.
- Year: año de estreno.
- Age: grupo de edad objetivo.
- IMDb: valoración en IMDb.
- Rotten Tomatoes: porcentaje en Rotten Tomatoes.
- Netflix: indica si la película está en Netflix.
- Hulu: indica si la película está en Hulu.
- Prime Video: indica si la película está en Prime Video.
- Disney+: indica si la película está en Disney+.
- Type: película o programa de TV.
- Directors: nombre del director o directores.
- Genres: tipo de género.
- Country: país de origen.
- Language: idioma original.
- Runtime: duración de la película.
Importemos los módulos necesarios y carguemos el dataset:
# Import required libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.tokenize import RegexpTokenizer
import numpy as np
from sklearn import preprocessing
from scipy.sparse import hstack
import pandas_profiling
# Load dataset
df = pd.read_csv("tvshow.csv")
df=df.iloc[:,1:] # removing in unnamed index column
df.head()
| Unnamed: 0 | ID | Title | Year | Age | IMDb | Rotten Tomatoes | Netflix | Hulu | Prime Video | Disney+ | Type | Directors | Genres | Country | Language | Runtime | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 1 | Inception | 2010 | 13+ | 8.8 | 87% | 1 | 0 | 0 | 0 | 0 | Christopher Nolan | Action,Adventure,Sci-Fi,Thriller | United States,United Kingdom | English,Japanese,French | 148.0 |
| 1 | 1 | 2 | The Matrix | 1999 | 18+ | 8.7 | 87% | 1 | 0 | 0 | 0 | 0 | Lana Wachowski,Lilly Wachowski | Action,Sci-Fi | United States | English | 136.0 |
| 2 | 2 | 3 | Avengers: Infinity War | 2018 | 13+ | 8.5 | 84% | 1 | 0 | 0 | 0 | 0 | Anthony Russo,Joe Russo | Action,Adventure,Sci-Fi | United States | English | 149.0 |
| 3 | 3 | 4 | Back to the Future | 1985 | 7+ | 8.5 | 96% | 1 | 0 | 0 | 0 | 0 | Robert Zemeckis | Adventure,Comedy,Sci-Fi | United States | English | 116.0 |
| 4 | 4 | 5 | The Good, the Bad and the Ugly | 1966 | 18+ | 8.8 | 97% | 1 | 0 | 1 | 0 | 0 | Sergio Leone | Western | Italy,Spain,West Germany | Italian | 161.0 |
# Show initial information about the dataset
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 16744 entries, 0 to 16743
Data columns (total 16 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 16744 non-null object
1 Title 16744 non-null object
2 Year 16744 non-null int64
3 Age 7354 non-null object
4 IMDb 16173 non-null float64
5 Rotten Tomatoes 5158 non-null object
6 Netflix 16744 non-null int64
7 Hulu 16744 non-null int64
8 Prime Video 16744 non-null int64
9 Disney+ 16744 non-null int64
10 Type 16744 non-null int64
11 Directors 16018 non-null object
12 Genres 16469 non-null object
13 Country 16309 non-null object
14 Language 16145 non-null object
15 Runtime 16152 non-null float64
dtypes: float64(2), int64(6), object(8)
memory usage: 2.0+ MB
df.Type.unique()
array([0])
Trabajar con valores ausentes
En esta sección trabajarás con los valores ausentes usando la función isnull(). Veamos un ejemplo:
#Finding Missing values in all columns
miss = pd.DataFrame(df.isnull().sum())
miss = miss.rename(columns={0:"miss_count"})
miss["miss_%"] = (miss.miss_count/len(df.ID))*100
miss
| misscount | miss% | |
|---|---|---|
| ID | 0 | 0.000000 |
| Title | 0 | 0.000000 |
| Year | 0 | 0.000000 |
| Age | 9390 | 56.079790 |
| IMDb | 571 | 3.410177 |
| Rotten Tomatoes | 11586 | 69.194935 |
| Netflix | 0 | 0.000000 |
| Hulu | 0 | 0.000000 |
| Prime Video | 0 | 0.000000 |
| Disney+ | 0 | 0.000000 |
| Type | 0 | 0.000000 |
| Directors | 726 | 4.335882 |
| Genres | 275 | 1.642379 |
| Country | 435 | 2.597946 |
| Language | 599 | 3.577401 |
| Runtime | 592 | 3.535595 |
Ves que las variables Age y Rotten Tomatoes tienen más del 50% de valores ausentes, lo cual es preocupante.
Ahora trataremos los valores ausentes con estos pasos:
- Eliminar columnas con más del 50% de valores ausentes.
- Eliminar NAs en las columnas IMDb, Directors, Genres, Country, Language y Runtime.
- Reiniciar el índice.
- Convertir la columna Year a tipo objeto.
# Dropping values with missing % more than 50%
df.drop(['Rotten Tomatoes', 'Age'], axis = 1, inplace=True)
# Dropping Na's from the following columns
df.dropna(subset=['IMDb','Directors', 'Genres', 'Country', 'Language', 'Runtime'],inplace=True)
df.reset_index(inplace=True,drop=True)
# Converting into object type
df.Year = df.Year.astype("object")
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 15233 entries, 0 to 15232
Data columns (total 14 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 15233 non-null object
1 Title 15233 non-null object
2 Year 15233 non-null object
3 IMDb 15233 non-null float64
4 Netflix 15233 non-null int64
5 Hulu 15233 non-null int64
6 Prime Video 15233 non-null int64
7 Disney+ 15233 non-null int64
8 Type 15233 non-null int64
9 Directors 15233 non-null object
10 Genres 15233 non-null object
11 Country 15233 non-null object
12 Language 15233 non-null object
13 Runtime 15233 non-null float64
dtypes: float64(2), int64(5), object(7)
memory usage: 1.6+ MB
Gráficos de distribución
Puedes comprobar la distribución de la columna Year con la función distplot() de seaborn. Dibujemos la distribución del año de las películas.
#checking Distribution of years
plt.figure(figsize=(20,5))
sns.distplot(df['Year'])
plt.show()

El gráfico muestra la distribución por año de origen de las películas. Se interpreta que la mayoría se rodaron entre 2000 y 2020. Ahora, tracemos la distribución de valoraciones de IMDb.
# Distribution of IMDb Rating
plt.figure(figsize=(20,5))
sns.distplot(df['IMDb'])
plt.show()

La distribución está algo sesgada. Se aprecia que la valoración media de IMDb de la mayoría de películas ronda el 6,5.
Ahora, tracemos la distribución de la duración.
# Distribution of runtime
sns.distplot(df['Runtime'])
plt.show()

Según el gráfico, la duración media de las películas se sitúa entre 80 y 120 minutos.
Distribución de películas por plataforma de streaming
En esta sección verás la distribución de títulos por plataforma. Primero, crea una función m_cnt() que cuente las películas de una plataforma dada. Después, podrás representarlo con gráficos de tarta para entender la cuota de cada plataforma.
# A function to calculate the movies in different Streaming platforms
def m_cnt(plat, count=False):
if count==False:
print('Platform {} Count: {}'. format(plat, df[plat].sum()))
else:
return df[plat].sum()
# Let's see count of movies/shows of each streaming platform
m_cnt('Netflix')
m_cnt('Hulu')
m_cnt('Prime Video')
m_cnt('Disney+')
Platform Netflix Count: 3152
Platform Hulu Count: 848
Platform Prime Video Count: 11289
Platform Disney+ Count: 542
# Movies on each platform
lab = 'Prime Video','Netflix', 'Hulu', 'Disney'
s = [m_cnt('Prime Video', count=True),
m_cnt('Netflix', count=True),
m_cnt('Hulu', count=True),
m_cnt('Disney+', count=True)]
explode = (0.1, 0.1, 0.1, 0.1)
#plotting
fig1, ax1 = plt.subplots()
ax1.pie(s,
labels = lab,
autopct = '%1.1f%%',
explode = explode,
shadow = True,
startangle = 100)
ax1.axis = ('equal')
plt.show()

Según el gráfico, Prime Video alberga el mayor número de títulos con una cuota del 71%, y Netflix un 20%. Disney+ y Hulu son las que menos títulos tienen, con un 5,4% y un 3,4%, respectivamente.
Distribución por género
En esta sección verás la distribución por género. Primero, debes preparar los datos para manejar varios géneros en una misma celda del dataframe. Para ello, puedes usar las funciones split(), apply() y stack(). split() separa valores por comas y crea una lista; apply(pd.Series,1) crea múltiples columnas (una por género) y stack() las apila en una sola columna.
Tras estas tres operaciones, una nueva columna Genres se unirá al dataframe y podrás visualizar. Puedes mostrar los 10 géneros principales con su recuento usando value_counts() y la función plot() de pandas.
##split the genres by ',' & then stack it one after the other for easy analysis.
g = df['Genres'].str.split(',').apply(pd.Series, 1).stack()
g.index = g.index.droplevel(-1)
# Assign name to column
g.name = 'Genres'
# delete column
del df['Genres']
# join new column with the existing dataframe
df_genres = df.join(g)
# Count of movies according to genre
plt.figure(figsize=(15,5))
sns.countplot(x='Genres', data=df_genres)
plt.xticks(rotation=90)
plt.show()

Según el gráfico, los géneros más comunes son Drama y Comedia.
Distribución por país
En esta sección verás la distribución por país. De nuevo, hay que preparar los datos para manejar varios países en una misma celda. Usa split(), apply() y stack(). split() separa por comas y crea una lista; apply(pd.Series,1) crea columnas por país y stack() las apila en una sola columna.
Tras estas operaciones, la columna Country se unirá al dataframe y podrás graficar. Puedes mostrar los 10 países con más títulos con value_counts() y la función plot() de pandas.
# Split the Country by ',' & then stack it one after the other for easy analysis.
c = df['Country'].str.split(',').apply(pd.Series, 1).stack()
c.index = c.index.droplevel(-1)
# Assign name to column
c.name = 'Country'
# delete column
del df['Country']
# join new column with the existing dataframe
df_country = df.join(c)
# plotting top 10 country and movie count
df_country['Country'].value_counts()[:10].plot(kind='bar',figsize=(15,5))
plt.show()

El gráfico muestra que la mayoría de las películas se hicieron en Estados Unidos.
Distribución por idioma
En esta sección verás la distribución por idioma. De nuevo, debes preparar los datos para manejar varios idiomas en una misma celda del dataframe. Puedes usar split(), apply() y stack(). split() separa por comas y crea una lista; apply(pd.Series,1) crea columnas por idioma; y stack() las apila en una sola columna.
Tras estas operaciones, la columna Language se une al dataframe y ya puedes graficar. Puedes mostrar los 10 idiomas con más títulos con value_counts() y la función plot() de pandas.
# perform stacking operation on language column
l = df['Language'].str.split(',').apply(pd.Series,1).stack()
l.index = l.index.droplevel(-1)
# Assign name to column
l.name = 'Language'
# delete column
del df['Language']
# join new column with the existing dataframe
df_language = df.join(l)
# plotting top 10 Language and movie count
df_language['Language'].value_counts()[:10].plot(kind='bar',figsize=(15,3))
plt.show()

De este gráfico se concluye que la mayoría de las películas están en inglés.
Distribución de valoraciones de IMDb por plataforma
En esta sección representarás la distribución de valoraciones de IMDb por plataforma. Para ello, aplica la función melt() y después un FacetGrid. melt() convierte un dataframe ancho en uno largo. Veamos un ejemplo:
# melting platform columns to create visualization
df2 = pd.melt(df, id_vars=["ID","Title","Year","IMDb","Type","Runtime"], var_name="platform")
df2 = df2[df2.value==1]
df2.drop(columns=["value"],axis=1,inplace=True)
# Distribution of IMDB rating in different platform
g = sns.FacetGrid(df2, col = "platform")
g.map(plt.hist, "IMDb")
plt.show()

El gráfico muestra la distribución de valoraciones medias de IMDb en cada plataforma.

Duración total por plataforma y grupo de edad
En la sección de «Trabajar con valores ausentes» eliminé la columna Age. Para obtener la duración total por plataforma y grupo de edad, necesito cargar los datos de nuevo y aplicar la función melt().
# Load dataset
df = pd.read_csv("tvshow.csv")
df=df.iloc[:,1:]
df.ID = df.ID.astype("object")
# melting platform columns to create visualization
df2 = pd.melt(df, id_vars=["ID","Title","Year","Age","IMDb","Rotten Tomatoes","Type","Runtime"], var_name="platform")
df2 = df2[df2.value==1]
df2.drop(columns=["value"],axis=1,inplace=True)
Tras cargar de nuevo el dataset y hacer el melt, es hora de generar el gráfico de duración frente a plataforma para distintos grupos de edad.
# Total of runtime in different platform
ax = sns.barplot(x="platform", y="Runtime",hue="Age", estimator=sum, data=df2)

El gráfico muestra que la duración total en Prime Video para el grupo de 18+ es muy superior a cualquier otra plataforma. Se puede interpretar que gran parte del contenido de Prime Video está orientado al público de 18+.
Construir un sistema de recomendación
En los últimos años, con el impulso de YouTube, Walmart, Netflix y muchos otros servicios web, los sistemas de recomendación han tenido un impacto enorme en la industria. Desde sugerir productos/servicios y aumentar el valor de las empresas mediante monetización por anuncios online hasta ajustar la relevancia y preferencias del usuario para incentivar la compra, los sistemas de recomendación son ya imprescindibles en nuestro día a día en la web.
En general, son marcos basados en modelos matemáticos que se centran en sugerir a los usuarios finales productos o servicios relevantes a sus necesidades. Por ejemplo, películas para ver, artículos para leer, productos para comprar, música para escuchar o cualquier cosa según el dominio. Hay tres enfoques principales para construir sistemas de recomendación:
- Content-based: definen un modelo para usuarios o ítems con los que han interactuado y, a partir de las características de los ítems, recomiendan a los usuarios otros ítems similares.
- Filtrado colaborativo: definen un modelo de similitud entre usuarios e ítems o entre usuarios y usuarios para recomendar ítems.
- Híbridos: combinan contenido y colaborativo para lograr mejores resultados.
Primero, vamos a preprocesar el dataset para el sistema de recomendación. Empecemos comprobando los valores ausentes:
# Reading Data Again
df = pd.read_csv("tvshow.csv")
df=df.iloc[:,1:]
#Finding Missing values in all columns
miss = pd.DataFrame(df.isnull().sum())
miss = miss.rename(columns={0:"miss_count"})
miss["miss_%"] = (miss.miss_count/len(df.ID))*100
miss
#Dropping values with missing % more than 50%
df.drop(['Rotten Tomatoes', 'Age'], axis = 1, inplace=True)
# Dropping Na's from the following columns
df.dropna(subset=['IMDb','Directors', 'Genres', 'Country', 'Language', 'Runtime'],inplace=True)
df.reset_index(inplace=True,drop=True)
# converting into object type
df.ID = df.ID.astype("object")
df.Year = df.Year.astype("object")
Construirás dos sistemas de recomendación basados en similitud del coseno.
1. Usando solo variables numéricas
2. Usando variables numéricas y categóricas
Usar solo columnas numéricas
Paso 1: seleccionar variables numéricas
ndf = df.select_dtypes(include=['float64',"int64"])
Paso 2: escalar las variables numéricas con MinMaxScaler para reducir complejidad y tiempo de entrenamiento
#importing minmax scaler
from sklearn import preprocessing
# Create MinMaxScaler Object
scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
# Create dataframe after transformation
ndfmx = pd.DataFrame((scaler.fit_transform(ndf)))
# assign column names
ndfmx.columns=ndf.columns
# Show initial 5 records
ndfmx.head()
Paso 3: calcular la similitud con coseno
Ahora, calcula la matriz de similitud usando similitud del coseno.
# Import cosine similarity
from sklearn.metrics.pairwise import cosine_similarity
# Compute the cosine similarity
sig = cosine_similarity(ndfmx, ndfmx)
# Reverse mapping of indices and movie titles
indices = pd.Series(df.index, index=df['Title']).drop_duplicates()
indices.head()
Paso 4: función para obtener recomendaciones a partir de la similitud
- La función recibe el título de una película y la matriz de similitud. Busca el índice del título en la serie de índices.
- Obtiene las puntuaciones de similitud con todas las películas.
- Ordena de mayor a menor y convierte a lista.
- Toma los 10 primeros índices y devuelve los títulos correspondientes del dataframe.
def give_rec(title, sig=sig):
# Get the index corresponding to original_title
idx = indices[title]
# Get the pairwise similarity scores
sig_scores = list(enumerate(sig[idx]))
# Sort the movies
sig_scores = sorted(sig_scores, key=lambda x: x[1], reverse=True)
# Scores of the 10 most similar movies
sig_scores = sig_scores[1:11]
# Movie indices
movie_indices = [i[0] for i in sig_scores]
# Top 10 most similar movies
return df['Title'].iloc[movie_indices]
# Execute get_rec() function for getting recommendation
give_rec("The Matrix",sig = sig)
Las recomendaciones no son del todo buenas. El motivo es que solo usas valoraciones, duración y variables de plataforma. Puedes mejorarlo incorporando información como géneros, directores y país.
Usar columnas numéricas y de texto
Como el sistema anterior no ofrece recomendaciones suficientemente buenas, probaremos un enfoque mejor para mejorar los resultados.
df.head()
Paso 1
Unificaremos las columnas de texto en una sola y aplicaremos un tokenizador y TF-IDF para crear una matriz dispersa con las puntuaciones TF-IDF. Luego seleccionaremos y escalaremos las variables numéricas y las añadiremos a la matriz dispersa. Pasos de preprocesado:
- Seleccionar columnas de tipo objeto y guardarlas en una lista.
- Eliminar las columnas ID y Title.
- Unir todas las columnas de texto con comas en una sola columna.
- Crear un tokenizador para eliminar elementos no deseados como símbolos y números.
- Convertir el texto a vectores TF-IDF. Después, preprocesar las columnas numéricas:
- Seleccionar variables numéricas en un DataFrame.
- Escalarlas con MinMaxScaler en rango (0,1).
- Añadir las variables numéricas a la matriz TF-IDF con hstack (apila horizontalmente en matrices dispersas).
#the function performs all the important preprocessing steps
def preprocess(df):
#combining all text columns
# Selecting all object data type and storing them in list
s = list(df.select_dtypes(include=['object']).columns)
# Removing ID and Title column
s.remove("Title")
s.remove("ID")
# Joining all text/object columns using commas into a single column
df['all_text']= df[s].apply(lambda x: ','.join(x.dropna().astype(str)),axis=1)
# Creating a tokenizer to remove unwanted elements from our data like symbols and numbers
token = RegexpTokenizer(r'[a-zA-Z]+')
# Converting TfidfVector from the text
cv = TfidfVectorizer(lowercase=True,stop_words='english',ngram_range = (1,1),tokenizer = token.tokenize)
text_counts= cv.fit_transform(df['all_text'])
# Aelecting numerical variables
ndf = df.select_dtypes(include=['float64',"int64"])
# Scaling Numerical variables
scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
# Applying scaler on our data and converting i into a data frame
ndfmx = pd.DataFrame((scaler.fit_transform(ndf)))
ndfmx.columns=ndf.columns
# Adding our adding numerical variables in the TF-IDF vector
IMDb = ndfmx.IMDb.values[:, None]
X_train_dtm = hstack((text_counts, IMDb))
Netflix = ndfmx.Netflix.values[:, None]
X_train_dtm = hstack((X_train_dtm, Netflix))
Hulu = ndfmx.Hulu.values[:, None]
X_train_dtm = hstack((X_train_dtm, Hulu))
Prime = ndfmx["Prime Video"].values[:, None]
X_train_dtm = hstack((X_train_dtm, Prime))
Disney = ndfmx["Disney+"].values[:, None]
X_train_dtm = hstack((X_train_dtm, Disney))
Runtime = ndfmx.Runtime.values[:, None]
X_train_dtm = hstack((X_train_dtm, Runtime))
return X_train_dtm
Paso 2: aplicar la función y crear la matriz dispersa
# Preprocessing data
mat =preprocess(df)
mat.shape
Paso 3: volver a calcular la similitud del coseno
# using cosine similarity
from sklearn.metrics.pairwise import cosine_similarity
# Compute the sigmoid kernel
sig2 = cosine_similarity(mat, mat)
# Reverse mapping of indices and movie titles
indices = pd.Series(df.index, index=df['Title']).drop_duplicates()
Paso 4: obtener recomendaciones del sistema mejorado
give_rec("The Matrix",sig=sig2)
Esta vez el sistema de recomendación funciona mucho mejor que el anterior, lo que demuestra que, al añadir datos más relevantes como texto descriptivo, un sistema basado en contenido puede mejorar de forma notable.
Conclusión
¡Enhorabuena por llegar al final del tutorial!
Has realizado un análisis exploratorio del dataset de películas en plataformas de streaming. Has examinado valores ausentes, gráficos de distribución individuales y la distribución de títulos por plataforma. También has obtenido insights sobre género, país, idioma, valoraciones de IMDb y duración. Por último, has visto cómo construir un sistema de recomendación en Python.
Echa un vistazo al tutorial de DataCamp Recommender Systems in Python.

