import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re
import seaborn as sns
import networkx as nx
Allocine - Data Visualization Project
Dans ce notebook nous tenterons d’analyser le jeu de données Allocine et nous présenterons 4 graphiques clés et un réseau pour comprendre les différentes relations dans ce dataframe.
Lien du Dépot GitHub : https://github.com/aurvl/DA_projects/blob/main/Allocine/Allocine.md
Chargement des librairies
Les librairies que nous utiliserons dans ce notebook sont :
Ensuite on importe notre dataframe (df) et on jette petit aperçu sur ses 5 premières lignes
= pd.read_excel("Base_allocine.xlsx")
df df.head()
Titre | Nationality | Channel | Actor | Genre | Duration | Date | |
---|---|---|---|---|---|---|---|
0 | Bloodline (2015) | U.S.A. | Netflix | Kyle Chandler;Linda Cardellini;Ben Mendelsohn | Drame , Thriller | 42min | 2015 - 2017 |
1 | The Fosters | U.S.A. | Freeform | Teri Polo;Sherri Saum;Jake T. Austin | Drame , Famille | 42min | 2013 - 2018 |
2 | Marvel's Luke Cage | U.S.A. | Netflix | Mike Colter;Simone Missick;Alfre Woodard | Fantastique , Action | 52min | 2016 - 2018 |
3 | The Hollow Crown | Grande-Bretagne | BBC Two | Jamie Ballard;James Fleet;Judi Dench | Drame , Historique | 130min | 2012 - 2016 |
4 | Future Man | U.S.A. | Hulu | Josh Hutcherson;Eliza Coupe;Derek Wilson | Comédie , Science fiction | 25min | 2017 - 2020 |
Partie 1 : Représentations graphiques
Dans cette partie nous présenterons 4 graphiques : - Un graphique pour représenter le nombre d’apparition des acteurs présent dans plus de 2 films
Le second graphique présenterons le Nombre de saisons d’un film en fonction de la chaine de diffusion
Le 3e graphique va s’articluer autour de la durée des films en fonction de leur genre
Et le dernier permettra de mieux appréhender le nombre de séries diffusées par année
Nom des acteurs et leur nombre d’apparitions
D’abord on crée une list dans laquelle on va stocker les noms des acteurs séparément
= []
lit for i in df['Actor']:
if isinstance(i, str):
= i.split(";")
separate for a in separate:
lit.append(a)else:
lit.append(i)
print(lit[:30])
['Kyle Chandler', 'Linda Cardellini', 'Ben Mendelsohn', 'Teri Polo', 'Sherri Saum', 'Jake T. Austin', 'Mike Colter', 'Simone Missick', 'Alfre Woodard', 'Jamie Ballard', 'James Fleet', 'Judi Dench', 'Josh Hutcherson', 'Eliza Coupe', 'Derek Wilson', 'Maimie McCoy', 'Luke Pasqualino', 'Tom Burke', 'Nathan Fillion', 'Stana Katic', 'Molly C. Quinn', 'Kathy Bates', 'Nate Corddry', 'Christopher McDonald', 'Jonathan Groff', 'Frankie J. Alvarez', 'Murray Bartlett', 'Danny McBride', 'John Hawkes', 'Katy Mixon']
On convertis la lite en dataframe pour lui appliquer la fonction value_counts() qui compte le nombre de fois qu’une modalité est retrouvée dans la colonne spécifiée (en l’occurence ActorName)
= pd.DataFrame(lit, columns=["ActorName"])
df_list = df_list['ActorName'].value_counts()
counts = counts[counts >= 2] # Ici seul ceux qui ont une apparition > 2 sont sélectionnés counts
Enfin on plot le résultat obtenue à l’aide d’un barplot
='bar', colormap='viridis', figsize=(12, 6))
counts.plot(kind="dark")
sns.set_theme(style'ActorName', fontsize = 10)
plt.xlabel('Nomb', fontsize = 10)
plt.ylabel(=60, fontsize=8, ha= 'right')
plt.xticks(rotation plt.show
Nombre de saisons par chaine
Dans un premier temps les date (en format debut - fin) sont séparés et stockées dans deux lists Pour ce faire, deux patern sont recherchés Dans la colonne ‘Date’
= "(.*?) -" # Celui avant le tiret (date de debut)
pat1 = "- (.*?) " # ET celui après (date de fin)
pat2
= [] # Nos deux listes pour stocker les dates
L1 = [] L2
On boucle ensuite sur chaque element de la colonne ‘Date’ pour récupérer chaque élément
for i in df['Date']:
= re.findall(pat1, i)
sy = re.findall(pat2, i)
ey 0])
L1.append(sy[0])
L2.append(ey[
# chaque valeur de L1 & L2 sont ensuite convertis en integer et ajoutées au df (a travers deux nouvelles colonnes)
= [int(x) for x in L1]
L1 = [int(x) for x in L2]
L2 'StartY']=L1
df['EndY']=L2 df[
Le nombre de saisons est ainsi calculé en soustrayant la date de fin à celle du debut
'Saison'] = df['EndY'] - df['StartY']
df[ df.head()
Titre | Nationality | Channel | Actor | Genre | Duration | Date | StartY | EndY | Saison | |
---|---|---|---|---|---|---|---|---|---|---|
0 | Bloodline (2015) | U.S.A. | Netflix | Kyle Chandler;Linda Cardellini;Ben Mendelsohn | Drame , Thriller | 42min | 2015 - 2017 | 2015 | 2017 | 2 |
1 | The Fosters | U.S.A. | Freeform | Teri Polo;Sherri Saum;Jake T. Austin | Drame , Famille | 42min | 2013 - 2018 | 2013 | 2018 | 5 |
2 | Marvel's Luke Cage | U.S.A. | Netflix | Mike Colter;Simone Missick;Alfre Woodard | Fantastique , Action | 52min | 2016 - 2018 | 2016 | 2018 | 2 |
3 | The Hollow Crown | Grande-Bretagne | BBC Two | Jamie Ballard;James Fleet;Judi Dench | Drame , Historique | 130min | 2012 - 2016 | 2012 | 2016 | 4 |
4 | Future Man | U.S.A. | Hulu | Josh Hutcherson;Eliza Coupe;Derek Wilson | Comédie , Science fiction | 25min | 2017 - 2020 | 2017 | 2020 | 3 |
On sort le df pour que les barres de notre graphe soit dans l’ordre décroissant
= df.sort_values(by='Saison', ascending=False)
df ="dark")
sns.set_theme(style=(14, 8))
plt.figure(figsize=df, x='Channel', y='Saison', palette='viridis', ci=None)
sns.barplot(data'Seasons vs Channel', fontsize=14)
plt.title('Chaine', fontsize=10)
plt.xlabel('Saisons', fontsize=10)
plt.ylabel(=60, fontsize=10, ha='right')
plt.xticks(rotation
plt.tight_layout() plt.show()
C:\Users\aurel\AppData\Local\Temp\ipykernel_10180\4050999946.py:4: FutureWarning:
The `ci` parameter is deprecated. Use `errorbar=None` for the same effect.
sns.barplot(data=df, x='Channel', y='Saison', palette='viridis', ci=None)
C:\Users\aurel\AppData\Local\Temp\ipykernel_10180\4050999946.py:4: FutureWarning:
Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.
sns.barplot(data=df, x='Channel', y='Saison', palette='viridis', ci=None)
Durée en fonction du genre
D’abord, dans une liste, on recupère chaque genre par individu :
On les séparent sur la base de la virgule à l’aide de la fonction split(” , “)
Ensuite on supprime les espaces qu’il reste dans chaque élément (on les néttoie)
Le résultat obtenu pour chaque observation est une liste*
Et chque liste obtenue est rangée dans une grande liste
= []
grand_list
for ele in df['Genre']:
if isinstance(ele, str):
= ele.split(" , ")
each = [g.strip() for g in each]
each
grand_list.append(each)else:
grand_list.append(ele)
print(grand_list[:30])
[['Comédie', 'Famille'], ['Drame', 'Policier'], ['Drame', 'Thriller'], ['Comédie'], ['Drame', 'Policier'], ['Drame', 'Fantastique', 'Action'], ['Drame', 'Fantastique'], ['Drame', 'Fantastique', 'Romance'], ['Comédie', 'Drame', 'Judiciaire'], ['Drame', 'Policier'], ['Drame'], ['Drame', 'Judiciaire'], ['Policier', 'Thriller'], ['Drame', 'Fantastique'], ['Drame', 'Guerre'], ['Comédie'], ['Drame', 'Policier', 'Thriller'], ['Aventure', 'Drame', 'Policier'], ['Aventure', 'Comédie', 'Epouvante-horreur', 'Action', 'Animation'], ['Drame', 'Fantastique', 'Espionnage', 'Action'], ['Comédie'], ['Comédie'], ['Comédie', 'Drame', 'Policier'], ['Comédie', 'Drame', 'Animation'], ['Comédie'], ['Comédie', 'Comédie musicale'], ['Drame', 'Thriller', 'Judiciaire'], ['Drame', 'Policier'], ['Drame'], ['Aventure', 'Drame', 'Science fiction']]
Ensuite ce qu’on va chercher a faire cest de créer un dataframe dans lequel on va avoir : - Pour une obervation, une colonne pour chaque genre avec la durée
Pour connaitre alors le nombre de colonnes à créer pour les genre, on boucle sur chaque liste de grand_list:
= []
ls for nb_genr in grand_list:
len(nb_genr))
ls.append(
max(ls) # Colonnes max a créer = 5
5
# On crée alors un df avec 5 colonnes Genre 1, 2, ..., 5
= pd.DataFrame(grand_list, columns=[f'Genre{i+1}' for i in range(5)])
genre_df genre_df.head()
Genre1 | Genre2 | Genre3 | Genre4 | Genre5 | |
---|---|---|---|---|---|
0 | Comédie | Famille | None | None | None |
1 | Drame | Policier | None | None | None |
2 | Drame | Thriller | None | None | None |
3 | Comédie | None | None | None | None |
4 | Drame | Policier | None | None | None |
Les durées sont ensuite nettoyées : - On supprime les ‘min’ dans chaque valeur - On remplace les valeurs manquantes par des 0 - On convertit les valeurs en int
Enfin on join les deux df (horizontalement)
'Duration'] = df['Duration'].str.replace('min', '').fillna('0').astype(int)
df[= pd.concat([df, genre_df], axis=1)
df df.head()
Titre | Nationality | Channel | Actor | Genre | Duration | Date | StartY | EndY | Saison | Genre1 | Genre2 | Genre3 | Genre4 | Genre5 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
234 | Modern Family | U.S.A. | ABC | Ed O'Neill;Julie Bowen;Ty Burrell | Comédie , Famille | 22 | 2009 - 2020 | 2009 | 2020 | 11 | Comédie dramatique | Policier | None | None | None |
28 | Hawaii Five-0 (2010) | U.S.A. | CBS | Alex O'Loughlin;Scott Caan;Chi McBride | Drame , Policier | 42 | 2010 - 2020 | 2010 | 2020 | 10 | Drame | None | None | None | None |
167 | Homeland | U.S.A. | Showtime | Claire Danes;Mandy Patinkin;Maury Sterling | Drame , Thriller | 42 | 2011 - 2020 | 2011 | 2020 | 9 | Drame | Epouvante-horreur | Fantastique | None | None |
58 | The Middle | U.S.A. | ABC | Patricia Heaton;Neil Flynn;Eden Sher | Comédie | 22 | 2009 - 2018 | 2009 | 2018 | 9 | Aventure | Action | Animation | None | None |
77 | Caïn | France | France 2 | Julien Baumgartner;Julie Delarme;David Baiot | Drame , Policier | 52 | 2012 - 2020 | 2012 | 2020 | 8 | Aventure | Science fiction | Action | None | None |
On va ensuite melt (renverser en qlq sorte le dataframe) pour avoir tous les genres dans une même colonne avec la durée en face
= df.melt(id_vars=['Duration'], value_vars=['Genre1', 'Genre2', 'Genre3', 'Genre4', 'Genre5'], var_name='Genre')
df2 =['Genre'], inplace=True) # la colonne 'Genre' (qui contient finalement (après le melt) les colonnes Genre1, 2, ..., 5) est ensuite supprimée
df2.drop(columns
# On aggrège ensuite les durées par genre en les moyennant pour chaque genre
= df2.groupby('value')['Duration'].mean()
pargenre pargenre.head()
value
Action 42.545455
Animation 39.923077
Arts Martiaux 37.000000
Aventure 38.080000
Biopic 52.000000
Name: Duration, dtype: float64
On peut ensuite afficher le résultat sous forme de graphique à bar (barplot)
='bar', colormap='viridis', figsize=(12, 6))
pargenre.plot(kind'Genre', fontsize = 10)
plt.xlabel('Durée', fontsize = 10)
plt.ylabel(=60, fontsize=10, ha= 'right')
plt.xticks(rotation plt.show
Nombre de séries par année
= df[['Titre', 'StartY', 'EndY']] # On récupère les 3 colonnes qui nous interresse ici df3
Dans un premier temps, Pour savoir la période sur laquelle on va travailler on determine les années minimales et maximales
= df3['StartY'].min()
mn = df3['EndY'].max() mx
On crée une list contenant toute les années de l’année minimal à l’année max
Pourquoi on crée cette liste car elle va nous servir a nommer les colonnes de notre df
Dans ce df, on va avoir pour chaque année (en colonne et les films en ligne) si le film a été diffusé sur cette période la valeur de 1 à l’intersection
= list(range(mn, mx + 1))
years = pd.DataFrame(0, index=df3.index, columns=years) # On met 0 comme valeur par defaut pour chaque obervation
df_anne
for index, row in df3.iterrows(): # ici on va boucler sur chque ligne (df.interrows())
= int(row['StartY']) # on récupère la date de debut
start = int(row['EndY']) # et celle de fin
end += 1 #et on ajoute 1 si pour ce film l'année (en colonne) se situe entre sa date de debut et de fin
df_anne.loc[index, start:end] # En gros si pour cette année la (en colonne) le film a été diffusé on va avoir comme valeur 1
= df_anne.sum(axis=0) # Puis on somme toutes les lignes ce qui va donner pour chaque colonne (année) le nombre de film diffusé
yearly_counts print(yearly_counts)
2009 34
2010 70
2011 100
2012 128
2013 157
2014 163
2015 176
2016 180
2017 168
2018 131
2019 87
2020 44
dtype: int64
On peut maintenant présenter les résultats obtenus
=(12, 6))
plt.figure(figsize= plt.bar(yearly_counts.index, yearly_counts.values, color='green')
bars 'Time', fontsize=10)
plt.xlabel('Numb', fontsize=10)
plt.ylabel('Numb de films par an', fontsize=12)
plt.title(
# Annotations
for bar in bars:
= bar.get_height()
yval + bar.get_width()/2, yval, int(yval), ha='center', va='bottom')
plt.text(bar.get_x()
plt.show()
Partie 2 : Réseau
df.head()
Titre | Nationality | Channel | Actor | Genre | Duration | Date | StartY | EndY | Saison | Genre1 | Genre2 | Genre3 | Genre4 | Genre5 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
234 | Modern Family | U.S.A. | ABC | Ed O'Neill;Julie Bowen;Ty Burrell | Comédie , Famille | 22 | 2009 - 2020 | 2009 | 2020 | 11 | Comédie dramatique | Policier | None | None | None |
28 | Hawaii Five-0 (2010) | U.S.A. | CBS | Alex O'Loughlin;Scott Caan;Chi McBride | Drame , Policier | 42 | 2010 - 2020 | 2010 | 2020 | 10 | Drame | None | None | None | None |
167 | Homeland | U.S.A. | Showtime | Claire Danes;Mandy Patinkin;Maury Sterling | Drame , Thriller | 42 | 2011 - 2020 | 2011 | 2020 | 9 | Drame | Epouvante-horreur | Fantastique | None | None |
58 | The Middle | U.S.A. | ABC | Patricia Heaton;Neil Flynn;Eden Sher | Comédie | 22 | 2009 - 2018 | 2009 | 2018 | 9 | Aventure | Action | Animation | None | None |
77 | Caïn | France | France 2 | Julien Baumgartner;Julie Delarme;David Baiot | Drame , Policier | 52 | 2012 - 2020 | 2012 | 2020 | 8 | Aventure | Science fiction | Action | None | None |
Dans un premier on va récupérer tous les genres de notre jeu de données :
= [pd.Series(grand_list).explode().values]
all_genres = pd.Series(all_genres[0]) all_genres
Ensuite on peut voir la distribution des différents genres
= all_genres.unique()
genres_uniques = all_genres.value_counts()
genre_counts
print(f"""Liste des genres :
{genres_uniques}
=====================================================
Distribution des genres :
{genre_counts.head(15)}""") # 15 genres les plus représentés
Liste des genres :
['Comédie' 'Famille' 'Drame' 'Policier' 'Thriller' 'Fantastique' 'Action'
'Romance' 'Judiciaire' 'Guerre' 'Aventure' 'Epouvante-horreur'
'Animation' 'Espionnage' 'Comédie musicale' 'Science fiction'
'Arts Martiaux' 'Western' 'Médical' 'Historique' 'Soap' 'Musical'
'Comédie dramatique' 'Biopic' 'Websérie' 'Sport event']
=====================================================
Distribution des genres :
Drame 213
Comédie 114
Policier 71
Thriller 63
Fantastique 53
Action 44
Science fiction 38
Animation 26
Aventure 25
Comédie dramatique 23
Romance 17
Epouvante-horreur 16
Historique 16
Judiciaire 14
Espionnage 10
Name: count, dtype: int64
= pd.DataFrame(0, index=genres_uniques, columns=genres_uniques)
cooccurrence_matrix for i in range(len(all_genres)):
for j in range(i, len(all_genres)):
= all_genres[i]
genre_i = all_genres[j]
genre_j += 1
cooccurrence_matrix.at[genre_i, genre_j] if genre_i != genre_j:
+= 1
cooccurrence_matrix.at[genre_j, genre_i]
# print(cooccurrence_matrix) # pour afficher la matrice mais vu quelle est trop grande on la laisse en commentaire si vous voulez la voir
Maintenant on va représenter notre réseau sur un graphe où les noeuds auront une taille proportionnelle à la fréquence dans la matrice de cooccurence et où les arêtes (liens) auront proportionnelles à la co-occurrence
= nx.Graph()
R
# Graphe G auquel on ajoute des noeuds
for genre in genres_uniques:
=genre_counts[genre]) R.add_node(genre, size
Puis on ajoute les arêtes
for i in cooccurrence_matrix.index:
for j in cooccurrence_matrix.columns:
if cooccurrence_matrix.at[i, j] > 0:
=cooccurrence_matrix.at[i, j])
R.add_edge(i, j, weight
# supprimer les boucles (les neouds sont liés à eux même donc on retire cette liaison) R.remove_edges_from(nx.selfloop_edges(R))
Maitenant que les noeuds et les arrêtes ont étés ajoutées au graphe, on va maitenant définir leur taille, leur poids et leur position en fonction de la matrice de cooccurence
# La taille qu'on prend en racine carré sinon les noeuds sonr trop grands
= [np.sqrt(R.nodes[genre]['size'] + 1) * 100 for genre in R.nodes]
node_sizes
# Le poids
= [R.edges[edge]['weight'] * 0.05 for edge in R.edges]
edge_weights
# La position
= nx.spring_layout(R, k=1, iterations=100, seed=42)
pos
# Couleurs des nœuds
= sns.color_palette("husl", len(R.nodes)) node_colors
On obtient le graphique ci-dessous qui
=(15, 15))
plt.figure(figsize=node_sizes, node_color=node_colors, alpha=0.7)
nx.draw_networkx_nodes(R, pos, node_size=edge_weights, alpha=0.3)
nx.draw_networkx_edges(R, pos, width=11, font_weight=550, font_color=dict(zip(R.nodes, node_colors)), font_family='sans-serif')
nx.draw_networkx_labels(R, pos, font_size'off')
plt.axis(
plt.tight_layout()"Réseau des Genres", fontsize=15, y=0.05)
plt.suptitle( plt.show()