Cours
Avec la généralisation du binge-watching de séries et de films, les utilisateurs consomment du contenu à un rythme soutenu grâce aux services disponibles comme Netflix, Prime Video, Hulu et Disney+. Certaines plateformes récentes, telles que Hulu et YouTube TV, proposent aussi des retransmissions en direct d’événements comme le sport, des concerts/tournées ou des chaînes d’info. Le direct n’est toutefois pas encore adopté par toutes les plateformes, comme Netflix.
Les plateformes de streaming offrent davantage de flexibilité : regarder ses séries et films préférés, à tout moment, sur n’importe quel appareil. Grâce à la variété de contenus proposés, elles séduisent particulièrement un public jeune et connecté. Elles permettent aussi de rattraper un programme manqué, selon ses disponibilités. Dans ce tutoriel, vous allez analyser les données de films disponibles sur Netflix, Prime Video, Hulu et Disney+ et tenter de mieux comprendre leurs audiences.

Comprendre le jeu de données
Ce jeu de données ne contient que des films disponibles sur des plateformes de streaming comme Netflix, Prime Video, Hulu et Disney+. Vous pouvez le télécharger sur Kaggle ici.
Voici une description détaillée des attributs :
- ID : identifiant unique de chaque enregistrement
- Title : titre du film
- Year : année de sortie
- Age : tranche d’âge ciblée
- IMDb : note IMDb des films
- Rotten Tomatoes : pourcentage Rotten Tomatoes
- Netflix : indique si le film est disponible sur Netflix
- Hulu : indique si le film est disponible sur Hulu
- Prime Video : indique si le film est disponible sur Prime Video
- Disney+ : indique si le film est disponible sur Disney+
- Type : film ou série
- Directors : nom du ou des réalisateurs
- Genres : genre(s)
- Country : pays d’origine
- Language : langue d’origine
- Runtime : durée du film
Importons les modules nécessaires et chargeons le jeu de données :
# 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])
Gérer les valeurs manquantes
Dans cette section, vous allez traiter les valeurs manquantes avec la fonction isnull(). Voici un exemple :
#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 |
On constate que les variables Age et Rotten Tomatoes présentent plus de 50 % de valeurs manquantes, ce qui est problématique.
Nous allons maintenant traiter les valeurs manquantes en suivant ces étapes :
- Supprimer les colonnes avec plus de 50 % de valeurs manquantes
- Supprimer les NA dans les colonnes IMDb, Directors, Genres, Country, Language et Runtime
- Réinitialiser l’index
- Convertir la colonne Year en type objet
# 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
Graphiques de distribution
Vous pouvez examiner la distribution de la colonne Year avec la fonction distplot() de seaborn. Traçons la distribution des années de sortie.
#checking Distribution of years
plt.figure(figsize=(20,5))
sns.distplot(df['Year'])
plt.show()

Ce graphique montre la distribution des années d’origine des films. On peut en déduire que la majorité a été produite entre 2000 et 2020. Traçons maintenant la distribution des notes IMDb.
# Distribution of IMDb Rating
plt.figure(figsize=(20,5))
sns.distplot(df['IMDb'])
plt.show()

La distribution ci-dessus est légèrement asymétrique. On observe que la note IMDb moyenne de la plupart des films tourne autour de 6,5.
Traçons enfin la distribution des durées (Runtime).
# Distribution of runtime
sns.distplot(df['Runtime'])
plt.show()

D’après ce graphique, la durée moyenne des films se situe entre 80 et 120 minutes.
Répartition des films par plateforme de streaming
Dans cette section, vous allez visualiser la répartition des films par plateforme. Commencez par créer une fonction m_cnt() qui compte les films pour une plateforme donnée. Ensuite, affichez un camembert pour comparer les parts de chaque plateforme.
# 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()

Ce graphique montre que Prime Video héberge le plus grand nombre de titres, avec 71 % de part, et Netflix environ 20 %. Disney+ et Hulu ferment la marche, avec respectivement 5,4 % et 3,4 % des titres.
Répartition des films par genre
Ici, vous allez visualiser la répartition par genre. Il faut d’abord préparer les données : gérer les multiples genres listés dans une même cellule du DataFrame. Pour cela, utilisez les fonctions split(), apply() et stack(). split() sépare les valeurs par virgule et crée une liste, apply(pd.Series,1) crée plusieurs colonnes (une par genre) et stack() les empile dans une seule colonne.
Après ces étapes, une nouvelle colonne Genres est jointe au DataFrame, et vous pouvez tracer les graphiques. Affichez les 10 genres principaux avec leur nombre de films via value_counts(), puis utilisez la méthode 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()

On observe que les genres les plus fréquents sont le drame et la comédie.
Répartition des films par pays
Dans cette section, vous allez visualiser la répartition par pays. Là encore, il faut gérer les multiples pays mentionnés dans une seule cellule. Utilisez split(), apply() et stack(). La fonction split() sépare les valeurs par des virgules, apply(pd.Series,1) crée une colonne par pays et stack() empile le tout dans une seule colonne.
Après ces opérations, une nouvelle colonne Country est jointe au DataFrame et vous pouvez tracer le graphique. Affichez le top 10 des pays par nombre de films avec value_counts() et la méthode 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()

La majorité des films ont été produits aux États‑Unis.
Répartition des films par langue
Dans cette section, vous allez visualiser la répartition par langue. Comme précédemment, il faut gérer les langues multiples présentes dans une même cellule du DataFrame. Utilisez split(), apply() et stack(). La fonction split() sépare les valeurs par des virgules, apply(pd.Series,1) crée une colonne par langue et stack() les regroupe dans une seule colonne.
Après ces étapes, une nouvelle colonne Language est jointe au DataFrame. Vous pouvez afficher le top 10 des langues avec leur nombre de films via value_counts() et la méthode 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()

On en conclut que la majorité des films sont en anglais.
Distribution des notes IMDb selon chaque plateforme
Dans cette section, vous allez tracer la distribution des notes IMDb par plateforme. Pour obtenir ces résultats, appliquez la fonction melt() puis utilisez un FacetGrid. melt() convertit un DataFrame large en format long. Exemple :
# 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()

On visualise ici la distribution moyenne des notes IMDb sur chaque plateforme.

Durée totale par plateforme et par tranche d’âge
Dans la section « Gérer les valeurs manquantes », nous avons supprimé la colonne Age. Pour analyser la durée totale par plateforme et par tranche d’âge, rechargeons les données et appliquons 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)
Après avoir rechargé le jeu de données et effectué le melt, générons un graphique croisant la durée et la plateforme selon les tranches d’âge.
# Total of runtime in different platform
ax = sns.barplot(x="platform", y="Runtime",hue="Age", estimator=sum, data=df2)

On voit que la durée totale sur Prime Video pour les utilisateurs 18+ dépasse largement celle des autres plateformes. On peut en déduire que l’essentiel du contenu de Prime Video cible la tranche 18+.
Construire un système de recommandation
Ces dernières années, avec l’essor de YouTube, Walmart, Netflix et d’autres services web, les systèmes de recommandation ont eu un impact considérable. Ils vont du conseil de produits/services à la monétisation publicitaire, en passant par la mise en correspondance des préférences des utilisateurs pour favoriser l’achat. Les systèmes de recommandation sont devenus incontournables dans nos usages en ligne.
Il s’agit généralement d’approches fondées sur les mathématiques, visant à proposer aux utilisateurs finaux des produits/services pertinents selon leurs besoins et envies : films à regarder, articles à lire, produits à acheter, musique à écouter, etc. On distingue trois grandes familles pour construire un système de recommandation :
- Méthodes basées sur le contenu : modéliser les utilisateurs ou les éléments avec lesquels ils ont interagi à partir des caractéristiques des éléments, puis recommander des éléments similaires.
- Filtrage collaboratif : modéliser les similarités d’interactions entre utilisateurs et éléments, ou entre utilisateurs eux‑mêmes, pour recommander des éléments.
- Approches hybrides : combiner contenu et collaboratif pour de meilleurs résultats.
Commençons par prétraiter le jeu de données pour le système de recommandation. D’abord, vérifions les valeurs manquantes :
# 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")
Vous allez construire deux systèmes de recommandation basés sur la similarité cosinus.
1. En utilisant uniquement les variables numériques
2. En combinant variables numériques et catégorielles
Utiliser uniquement les colonnes numériques
Étape 1 : sélectionner les variables numériques
ndf = df.select_dtypes(include=['float64',"int64"])
Étape 2 : appliquer un min‑max scaler aux variables numériques pour réduire la complexité et le temps d’entraînement
#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()
Étape 3 : calculer le score de similarité avec la similarité cosinus
Calculons maintenant la similarité cosinus.
# 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()
Étape 4 : écrire une fonction qui retourne des recommandations selon le score de similarité
- La fonction prend deux arguments : le titre du film et la matrice de similarité. Elle recherche l’index du titre dans la série indices.
- Récupérer les similarités par paire pour tous les films.
- Trier ces scores par ordre décroissant et les convertir en liste.
- Récupérer les 10 meilleurs scores/indices et retourner les titres correspondants.
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)
Ici, les recommandations ne sont pas très satisfaisantes. La raison : nous n’utilisons que les notes, les durées et des variables de plateforme. On peut améliorer en intégrant d’autres informations, comme les genres, les réalisateurs et les pays.
Combiner colonnes numériques et textuelles
Le premier système fonctionne, mais la qualité des résultats reste perfectible. Essayons une approche plus riche pour améliorer les recommandations.
df.head()
Étape 1 :
Regroupez les colonnes textuelles en une seule, puis utilisez un tokenizer et un TF‑IDF Vectorizer pour créer une matrice creuse des scores TF‑IDF. Ensuite, sélectionnez et mettez à l’échelle les variables numériques, puis ajoutez-les à la matrice creuse. Étapes de prétraitement :
- Sélectionner toutes les colonnes de type objet et les stocker dans une liste
- Retirer les colonnes ID et Title
- Concaténer toutes les colonnes texte/objet en une seule colonne séparée par des virgules
- Créer un tokenizer pour retirer les éléments indésirables (symboles, chiffres)
- Convertir le texte en vecteurs TF‑IDF, puis prétraiter les colonnes numériques
- Sélectionner les variables numériques dans un DataFrame
- Appliquer un min‑max scaler (0,1) aux variables numériques
- Ajouter les variables numériques à la matrice TF‑IDF au moyen de
hstack(concaténation horizontale de matrices creuses)
#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
Étape 2 : appliquer la fonction et créer la matrice creuse
# Preprocessing data
mat =preprocess(df)
mat.shape
Étape 3 : recalculer la similarité cosinus
# 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()
Étape 4 : obtenir des recommandations avec notre système amélioré
give_rec("The Matrix",sig=sig2)
Cette fois, le système fournit des recommandations nettement meilleures, ce qui montre qu’en ajoutant des données plus pertinentes (comme du texte descriptif), un système de recommandation basé sur le contenu peut être considérablement amélioré.
Conclusion
Félicitations, vous êtes arrivé au bout de ce tutoriel !
Vous avez mené une analyse exploratoire d’un jeu de données de films issus de plateformes de streaming. Vous avez exploré les valeurs manquantes, les distributions individuelles et la répartition des films par plateforme. Vous avez aussi dégagé des insights par genre, pays, langue, notes IMDb et durée des films. Enfin, vous avez vu comment construire un système de recommandation en Python.
Consultez le tutoriel de DataCamp : Recommender Systems in Python.
