Curso
Com a nova cultura de maratonar séries e filmes, os usuários estão consumindo conteúdo em ritmo acelerado com serviços como Netflix, Prime Video, Hulu e Disney+. Algumas dessas plataformas, como Hulu e YouTube TV, também oferecem transmissões ao vivo de eventos, como esportes, shows e canais de notícias. Já o live streaming ainda não foi adotado por algumas plataformas, como a Netflix.
As plataformas de streaming oferecem mais flexibilidade para você assistir seus programas e filmes favoritos a qualquer hora e em qualquer dispositivo. Esses serviços atraem um público mais jovem e conectado por conta da grande variedade de conteúdos. Eles também permitem assistir a programas perdidos quando você tiver disponibilidade. Neste tutorial, você vai analisar dados de filmes das plataformas Netflix, Prime Video, Hulu e Disney+ e tentar entender seus públicos.

Entendendo o conjunto de dados
Este conjunto traz apenas filmes disponíveis em plataformas de streaming como Netflix, Prime Video, Hulu e Disney+. Você pode baixá-lo no Kaggle aqui.
Descrição dos atributos:
- ID: identificador único de cada registro.
- Title: nome do filme
- Year: ano de lançamento do filme.
- Age: faixa etária alvo
- IMDb: nota do IMDb.
- Rotten Tomatoes: % no Rotten Tomatoes
- Netflix: se o filme está disponível na Netflix
- Hulu: se o filme está disponível no Hulu
- Prime Video: se o filme está disponível no Prime Video
- Disney+: se o filme está disponível no Disney+
- Type: filme ou programa de TV
- Directors: nome(s) do diretor
- Genres: gênero(s)
- Country: país de origem
- Language: idioma de origem
- Runtime: duração do filme
Vamos importar os módulos necessários e carregar o 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])
Trabalhando com valores ausentes
Nesta seção, você vai trabalhar com valores ausentes usando a função isnull(). Veja um exemplo abaixo:
#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 |
Perceba que as variáveis Age e Rotten Tomatoes têm mais de 50% de valores ausentes, o que é preocupante.
Agora, vamos tratar os valores ausentes com os passos a seguir:
- Excluir colunas com mais de 50% de valores ausentes
- Remover NAs das colunas IMDb, Directors, Genres, Country, Language e Runtime
- Resetar o índice
- Converter a coluna Year para object
# 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 distribuição
Você pode verificar a distribuição da coluna Year usando a função distplot() do seaborn. Vamos plotar a distribuição dos anos dos filmes.
#checking Distribution of years
plt.figure(figsize=(20,5))
sns.distplot(df['Year'])
plt.show()

O gráfico mostra a distribuição do ano de origem dos filmes. Dá para interpretar que a maioria foi produzida entre 2000 e 2020. Agora, vamos plotar a distribuição das notas do IMDb.
# Distribution of IMDb Rating
plt.figure(figsize=(20,5))
sns.distplot(df['IMDb'])
plt.show()

A distribuição acima é levemente assimétrica. Podemos interpretar que a média do IMDb da maioria dos filmes gira em torno de 6,5.
Vamos plotar a distribuição da duração dos filmes.
# Distribution of runtime
sns.distplot(df['Runtime'])
plt.show()

Pelo gráfico, dá para interpretar que a duração média dos filmes fica entre 80 e 120 minutos.
Distribuição de filmes em cada plataforma de streaming
Nesta seção, você verá a distribuição de filmes por plataforma. Primeiro, crie uma função m_cnt() que conta os filmes para uma plataforma específica. Depois, podemos plotar em gráficos de pizza e entender a participação 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()

Pelo gráfico acima, dá para dizer que o Prime Video hospeda o maior número de títulos, com 71% de participação, e a Netflix fica com 20%. Disney+ e Hulu têm as menores fatias, 5,4% e 3,4%, respectivamente.
Distribuição de filmes por gênero
Aqui, você verá a distribuição por gênero. Antes, é preciso preparar os dados: lidar com múltiplos gêneros em uma única célula do dataframe. Para isso, use as funções split(), apply() e stack(). A função split() separa valores por vírgula e cria uma lista. apply(pd.Series,1) cria múltiplas colunas para cada gênero e stack() empilha tudo em uma única coluna.
Depois dessas operações, uma nova coluna Genres será juntada ao dataframe existente e você poderá plotar. É possível mostrar os 10 principais gêneros com suas contagens usando value_counts() e o plot() do 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()

Pelo gráfico, dá para dizer que os gêneros mais comuns são Drama e Comédia.
Distribuição de filmes por país
Nesta parte, você verá a distribuição por país. Novamente, é preciso preparar os dados para lidar com múltiplos países numa única célula. Use split(), apply() e stack(). A função split() separa por vírgulas e cria uma lista; apply(pd.Series,1) cria múltiplas colunas; e stack() empilha tudo em uma coluna.
Depois dessas operações, uma nova coluna Country será juntada ao dataframe e você poderá plotar. Mostre os 10 principais países com value_counts() e o plot() do 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()

O gráfico mostra que a maior parte dos filmes foi produzida nos Estados Unidos.
Distribuição de filmes por idioma
Aqui, você verá a distribuição por idioma. É preciso preparar os dados para lidar com múltiplos idiomas em uma única célula do dataframe. Para isso, use split(), apply() e stack(). A função split() separa por vírgulas e cria uma lista; apply(pd.Series,1) cria múltiplas colunas; e stack() empilha tudo em uma coluna.
Depois dessas etapas, uma nova coluna Language será juntada ao dataframe e você poderá plotar. Mostre os 10 principais idiomas com value_counts() e use o plot() do 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()

O gráfico indica que a maioria dos filmes está em inglês.
Distribuição das notas do IMDb em cada plataforma
Nesta seção, você vai plotar a distribuição das notas do IMDb por plataforma. Para isso, aplique a função melt() e use um FacetGrid. A função melt() converte um dataframe wide em um dataframe long. Veja o exemplo:
# 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()

O gráfico acima mostra a distribuição média das notas do IMDb em cada plataforma.

Tempo de execução por plataforma e faixa etária
Na seção "Trabalhando com valores ausentes", removemos a coluna Age. Para obter os resultados de tempo de execução por plataforma e faixa etária, precisamos recarregar os dados e aplicar 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)
Depois de recarregar o dataset e fazer o melt, é hora de gerar um gráfico do tempo de execução por plataforma considerando as faixas etárias.
# Total of runtime in different platform
ax = sns.barplot(x="platform", y="Runtime",hue="Age", estimator=sum, data=df2)

O gráfico acima mostra que o tempo total de execução no Prime Video para usuários 18+ é bem maior do que em qualquer outra plataforma. Dá para inferir que boa parte do conteúdo do Prime Video foca na faixa etária 18+.
Construindo um sistema de recomendação
Nos últimos anos, com o avanço de YouTube, Walmart, Netflix e muitos outros serviços web, sistemas de recomendação passaram a ter um impacto enorme na indústria. De sugerir produtos/serviços a aumentar o valor das empresas por monetização via anúncios, conectando relevância e preferências do usuário para estimular a compra — os sistemas de recomendação são parte do nosso dia a dia online.
Em geral, são frameworks baseados em matemática voltados a sugerir produtos/serviços relevantes às necessidades e desejos dos usuários finais. Exemplos: filmes para assistir, artigos para ler, produtos para comprar, músicas para ouvir — e por aí vai. Existem três abordagens principais para construir sistemas de recomendação:
- Métodos baseados em conteúdo: definem um modelo para usuários ou itens com base nos atributos dos itens e recomendam itens semelhantes.
- Filtragem colaborativa: definem um modelo com base na similaridade de interação entre usuários e itens, ou na similaridade entre usuários, para recomendar itens.
- Híbridos: combinam conteúdo e colaboração para alcançar resultados melhores.
Vamos primeiro pré-processar o dataset para o sistema de recomendação. Comece verificando os 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")
Você vai construir dois sistemas de recomendação baseados em similaridade do cosseno.
1. Usando apenas variáveis numéricas
2. Usando variáveis numéricas e categóricas
Usando apenas colunas numéricas
Passo 1: selecionar as variáveis numéricas
ndf = df.select_dtypes(include=['float64',"int64"])
Passo 2: aplicar Min-Max Scaler para reduzir a complexidade e o tempo de treino
#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()
Passo 3: calcular a similaridade com cosseno
Agora, vamos calcular a pontuação de similaridade usando cosseno.
# 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()
Passo 4: criar uma função para gerar recomendações com base na similaridade
- A função recebe dois argumentos: o título do filme e a matriz de similaridade. Ela busca o índice do título na nossa Series de índices.
- Obtém as pontuações de similaridade pareadas para todos os filmes.
- Ordena as pontuações em ordem decrescente e converte em lista.
- Pega os 10 filmes mais similares e retorna seus títulos do 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)
Aqui, as recomendações não ficam tão boas. O motivo é usar apenas notas, duração e variáveis de plataforma. Podemos melhorar incluindo informações como gênero, diretores e país.
Usando colunas numéricas e textuais
Como o sistema anterior não trouxe recomendações tão relevantes, vamos tentar uma abordagem melhor para aprimorar os resultados.
df.head()
Passo 1:
Vamos unir as colunas textuais em uma única coluna, depois usar um tokenizador e o TF-IDF Vectorizer para criar uma matriz esparsa com os escores TF-IDF das palavras. Em seguida, selecionamos e escalonamos as variáveis numéricas e as adicionamos à matriz esparsa. As etapas de pré-processamento são:
- Selecionar todas as colunas do tipo object e guardar em uma lista.
- Remover as colunas ID e Title.
- Juntar todas as colunas de texto/objeto em uma única coluna separada por vírgulas.
- Criar um tokenizador para remover elementos indesejados, como símbolos e números.
- Transformar o texto em vetores TF-IDF. Depois, pré-processar as colunas numéricas.
- Selecionar variáveis numéricas em um DataFrame.
- Escalonar variáveis numéricas com Min-Max (0,1).
- Adicionar as variáveis numéricas à matriz esparsa de TF-IDF usando hstack (concatenação horizontal).
#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'])
# Aselecting 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
Passo 2: aplicar a função aos dados e criar a matriz esparsa
# Preprocessing data
mat =preprocess(df)
mat.shape
Passo 3: aplicar novamente a similaridade do cosseno
# 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()
Passo 4: obter recomendações com o sistema aprimorado
give_rec("The Matrix",sig=sig2)
Desta vez o sistema de recomendação funciona bem melhor que o anterior, mostrando que, ao adicionar dados mais relevantes como texto descritivo, um sistema baseado em conteúdo pode melhorar significativamente.
Conclusão
Parabéns! Você chegou ao fim deste tutorial.
Nele, você fez uma análise exploratória do dataset de filmes em plataformas de streaming. Explorou valores ausentes, gráficos de distribuição individuais e a distribuição de filmes por plataforma. Também gerou insights sobre gênero, país, idioma, notas do IMDb e duração. Por fim, viu como construir um sistema de recomendação em Python.
Confira o tutorial da DataCamp Recommender Systems in Python.
