Pular para o conteúdo principal

Tutorial de sistema de recomendação para plataformas de streaming

Neste tutorial em Python, explore dados de filmes de plataformas de streaming populares e construa um sistema de recomendação.
· 10 min leer

Explore com IA

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.

career-building python skills with data camp banner

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: 0IDTitleYearAgeIMDbRotten TomatoesNetflixHuluPrime VideoDisney+TypeDirectorsGenresCountryLanguageRuntime
001Inception201013+8.887%10000Christopher NolanAction,Adventure,Sci-Fi,ThrillerUnited States,United KingdomEnglish,Japanese,French148.0
112The Matrix199918+8.787%10000Lana Wachowski,Lilly WachowskiAction,Sci-FiUnited StatesEnglish136.0
223Avengers: Infinity War201813+8.584%10000Anthony Russo,Joe RussoAction,Adventure,Sci-FiUnited StatesEnglish149.0
334Back to the Future19857+8.596%10000Robert ZemeckisAdventure,Comedy,Sci-FiUnited StatesEnglish116.0
445The Good, the Bad and the Ugly196618+8.897%10100Sergio LeoneWesternItaly,Spain,West GermanyItalian161.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
 misscountmiss%
ID00.000000
Title00.000000
Year00.000000
Age939056.079790
IMDb5713.410177
Rotten Tomatoes1158669.194935
Netflix00.000000
Hulu00.000000
Prime Video00.000000
Disney+00.000000
Type00.000000
Directors7264.335882
Genres2751.642379
Country4352.597946
Language5993.577401
Runtime5923.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()
movie year distribution plot

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()
imdb rating distribution plot

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()
movie Runtime distribution plot

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()
streaming platforms

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()
movie genres

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()
countries movies were made

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()
movie languages plot

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()
IMDB rating Distribution According to each Platform

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

explore datacamp's python course library banner

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)
Runtime Per Platform Along with Age Group

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:

  1. Métodos baseados em conteúdo: definem um modelo para usuários ou itens com base nos atributos dos itens e recomendam itens semelhantes.
  2. 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.
  3. 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.

datacamp's python course library banner

Temas

Relacionado

Tutorial

Tutorial de Python: Streamlit