(18 min de lecture)

Je vous propose un guide complet étape par étape pour créer des visualisations de données Python avancées avec Seaborn et Matplotlib.
Bien qu’il existe des tonnes d’outils de visualisation en Python, Matplotlib + Seaborn se distingue toujours par sa capacité à créer et à personnaliser toutes sortes de tracés.

Dans cet article, je vais d’abord passer en revue quelques sections afin de vous préparer aux connaissances de base pour les lecteurs qui seraient débutants Matplotlib :

  • Comprendre les deux différentes interfaces de Matplotlib (Cela porte souvent à confusion) .
  • Comprendre les éléments d’une figure, afin de pouvoir facilement consulter les API pour résoudre votre problème.
  • Jeter un coup d’œil sur quelques types de tracés courants afin d’avoir une meilleure idée de quand et comment les utiliser au mieux.
  • Apprendre comment augmenter la “dimension” de vos graphiques.
  • Apprendre comment partitionner la figure en utilisant GridSpec.

Je parlerai ensuite du processus de création de visualisations de données Python (avancées) à l’aide d’un exemple sur les données du Black Friday :

  1. Fixer un objectif.
  2. Préparer les variables.
  3. Préparer la visualisation de données.

C’est parti !

Deux interfaces Matplotlib différentes

Importation Matplotlib :

import matplotlib.pyplot as plt
%matplotlib inline # pour Jupyter Notebook

Il y a deux façons de coder dans Matplotlib :

Méthode Fonctionnelle

La première est la méthode Fonctionnelle :

plt.figure()
plt.plot([0, 1], [0, 1],'r--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.title('Une figure Test')        
plt.show()

Ce qui est bien pour créer des tracés faciles (vous appelez des plt.xxx pour tracer chaque composante du graphique), mais vous n’avez pas trop de contrôle sur le graphique.

Méthode Orientée Objet

L’autre méthode est appelée la méthode Orienté Objet (OO) :

fig, ax = plt.subplots(figsize=(3,3))
ax.bar(x=['A','B','C'], height=[3.1,7,4.2], color='r')
ax.set_xlabel(xlabel='X title', size=20)
ax.set_ylabel(ylabel='Y title' , color='b', size=20)
plt.show()

Il faudra plus de temps pour coder, mais vous aurez le contrôle total de l’allure de votre graphique, ça vaut le coup, non?
L’idée est de créer un objet “figure”, que vous pouvez considérer comme une boîte qui englobe toute la visualisation que vous allez construire, et un ou plusieurs objets “axes”, qui sont des sous-images ou sous-parcelles de la visualisation (on appelle ces sous-parcelles “axes”, mais il n’y a pas vraiment de raison à cela…) et les sous-parcelles peuvent être manipulées par les méthodes applicables à ces objets “axes”.

(Pour des explications détaillées de ces deux interfaces, vous pouvez vous référer à
https://matplotlib.org/tutorials/introductory/lifecycle.html
ou
https://pbpython.com/effective-matplotlib.html)

On continuera avec l’approche Orientée Objet dans ce tutoriel.

Éléments d’une figure dans une interface orientée objet

La figure suivante, tirée du site ci-dessus, explique assez bien les composantes d’une figure :

Visualisations de données Python

Voyons un exemple simple de création d’un graphique linéaire avec une interface orientée objet.

fig, ax = plt.subplots(figsize=(3,3))
ax.plot(['Camille','Thomas','Laura'], [5,2,3], color='r')
ax.set_xlabel('TITRE 1')
for tick in ax.get_xticklabels():
    tick.set_rotation(45)
plt.show()

Dans les codes ci-dessus, nous avons créé un objet axes, créé un tracé de ligne par-dessus, ajouté un titre et fait pivoter toutes les étiquettes x-tick de 45 degrés dans le sens inverse des aiguilles d’une montre.

Consultez l’API officielle pour savoir comment manipuler les objets axes : https://matplotlib.org/api/axes_api.html

Visualisations de données Python – Quelques types de graphiques courants

Maintenant que vous avez une idée générale du fonctionnement de Matplotlib, il est temps d’examiner quelques tracés courants.

  • Diagrammes de dispersion – Nuage de points (x : Numérique #1, y : Numérique #2)
    –> Scatter plots
  • Tracés linéaires (x : Catégorie- ordinal #1, y : Numérique #1)
    –> Line plots
  • Diagrammes à barres (x : Catégorie #1, y : Numérique #1). Numérique #1 est souvent le comptage de Catégorie #1.
    –> Bar plots
  • Histogrammes (x : Numérique #1, y : Numérique #2). Numérique #1 est combiné en groupes (converti en une variable catégorielle), et Numérique #2 est généralement le comptage de cette variable catégorielle.
    –> Histogram
  • Graphiques de densité du noyau (x : Numérique #1, y : Numérique #2). Numérique #2 est la fréquence de Numérique #1.
    –> Kernel density plot
  • Graphiques de la densité des noyaux en 2D (x : Numérique #1, y : Numérique #2, couleur : Numérique #3). Numérique #3 est la fréquence commune de Numérique #1 et Numérique #2.
    –> 2-D kernel density plot
  • Boîtes à moustaches (x : Catégorie #1, y : Numérique #1, marques : Numérique #2). La boîte à moustaches montre les statistiques de chaque valeur de Catégorie #1, ce qui nous donne une idée de la répartition dans l’autre variable.
    Valeur y : la valeur de l’autre variable ; marques : montrant comment ces valeurs sont réparties (plage, Q1, médiane, Q3).
    –> Box plots
  • Diagrammes en violon (x : Catégorie #1, y : Numérique #1, largeur/marque : Numérique #2). Le violon est en quelque sorte similaire à la boîte à moustaches mais il montre encore mieux la répartition.
    –> Violin plots
  • Cartes de chaleur (x : Catégorie #1, y : Catégorie #2, couleur : Numérique #1). Numérique #1 peut être le nombre de valeurs de Catégorielle #1 et de Catégorielle #2 conjointement, ou d’autres attributs numériques pour chaque valeur de la paire (Catégorie #1, Catégorie #2).
    –> Heat maps

Pour apprendre à tracer ces figures, vous pouvez consulter les APIs de Seaborn en cherchant les termes suivants sur Google :

sns.barplot / sns.distplot / sns.lineplot / sns.kdeplot / sns.violinplot
sns.scatterplot / sns.boxplot / sns.heatmap

Je vais vous donner deux exemples de codes montrant comment des tracés de kde 2D (densité de noyaux) / carte de chaleur sont générés dans une interface orientée objet.

# 2D kde plots
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

np.random.seed(1)
numerical_1 = np.random.randn(100)
np.random.seed(2)
numerical_2 = np.random.randn(100)

fig, ax = plt.subplots(figsize=(3,3))
sns.kdeplot(data=numerical_1,
            data2= numerical_2,
            ax=ax,
            shade=True, 
            color="blue",  
            bw=1)

plt.show()

La clé est l’argument ax=ax. En exécutant la méthode .kdeplot(), seaborn applique les changements à ax, un objet “axes”.

# heat map
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.DataFrame(dict(categorical_1=['apple', 'banana', 'grapes',
                                      'apple', 'banana', 'grapes',
                                      'apple', 'banana', 'grapes'], 
                  categorical_2=['A','A','A','B','B','B','C','C','C'], 
                  value=[10,2,5,7,3,15,1,6,8]))

pivot_table = df.pivot("categorical_1", "categorical_2", "value")
# Essayez d'imprimer pivot_table pour voir à quoi cela ressemble !

fig, ax = plt.subplots(figsize=(5,5))
sns.heatmap(data=pivot_table, 
            cmap=sns.color_palette("Blues"),
            ax=ax)

plt.show()

Il s’agit du code de la carte de chaleur un peu plus haut.

Augmenter la dimension de vos graphiques

Pour ces graphiques de base, seule une quantité limitée d’informations peut être affichée (2 à 3 variables). Et si nous souhaitons afficher plus d’informations sur ces graphiques ? Voici quelques possibilités :

Superposition de tracés

Si plusieurs graphiques linéaires partagent les mêmes variables x et y, vous pouvez appeler les graphiques Seaborn plusieurs fois et les tracer tous sur la même figure. Dans l’exemple ci-dessous, nous avons ajouté une variable catégorielle supplémentaire [value = alpha, beta] dans le graphique avec des tracés superposés.

fig, ax = plt.subplots(figsize=(4,4))
sns.lineplot(x=['A','B','C','D'], 
             y=[4,2,5,3],
             color='r',
             ax=ax)
sns.lineplot(x=['A','B','C','D'], 
             y=[1,6,2,4], 
             color='b',
             ax=ax)    
ax.legend(['alpha', 'beta'], facecolor='w')
plt.show()

On peut aussi combiner un graphique en barres et un graphique linéaire avec le même axe x mais un axe y différent :

sns.set(style="white", rc={"lines.linewidth": 3})

fig, ax1 = plt.subplots(figsize=(4,4))
ax2 = ax1.twinx()

sns.barplot(x=['A','B','C','D'],
            y=[100,200,135,98], 
            color='#004488',
            ax=ax1)

sns.lineplot(x=['A','B','C','D'], 
             y=[4,2,5,3],
             color='r',
             marker="o",
             ax=ax2)
plt.show()
sns.set()

Quelques commentaires ici. Comme les deux tracés ont des axes y différents, nous devons créer un autre objet “axes” avec le même axe x (en utilisant .twinx()) et ensuite tracer sur des “axes” différents.
sns.set(…) sert à définir une esthétique spécifique pour le tracé actuel, et nous lançons sns.set() à la fin pour rétablir les paramètres par défaut.

La combinaison de différents diagrammes à barres en un diagramme à barres groupé ajoute également une dimension catégorielle au diagramme (une variable catégorielle de plus).

categorical_1 = ['A', 'B', 'C', 'D']
colors        = ['green', 'red', 'blue', 'orange']
numerical = [[6, 9, 2, 7],
             [6, 7, 3, 8],
             [9, 11, 13, 15],
             [3, 5, 9, 6]]

number_groups = len(categorical_1) 
bin_width = 1.0/(number_groups+1)

fig, ax = plt.subplots(figsize=(6,6))

for i in range(number_groups):
    ax.bar(x=np.arange(len(categorical_1)) + i*bin_width, 
           height=numerical[i],
           width=bin_width,
           color=colors[i],
           align='center')

ax.set_xticks(np.arange(len(categorical_1)) + number_groups/(2*(number_groups+1)))

# number_groups/(2*(number_groups+1)): décalage du xticklabel

ax.set_xticklabels(categorical_1)
ax.legend(categorical_1, facecolor='w')

plt.show()

Dans l’exemple de code ci-dessus, vous pouvez personnaliser les noms de variables, les couleurs et la taille des chiffres.
number_groups et bin_width sont calculés sur la base des données d’entrée. J’ai ensuite écrit une boucle pour tracer les barres, une couleur à la fois, et mettre les tics et les légendes à la toute fin.

Facet

Cartographie d’un ensemble de données selon plusieurs axes, et elles diffèrent par une ou deux variable(s) catégorielle(s). Vous pouvez trouver un tas d’exemples sur https://seaborn.pydata.org/generated/seaborn.FacetGrid.html

Couleur / Forme / Taille des nœuds dans un diagramme de dispersion

L’exemple de code suivant, tiré de l’API Seaborn Scatter plot, montre comment cela fonctionne (https://seaborn.pydata.org/generated/seaborn.scatterplot.html)

import seaborn as sns

tips = sns.load_dataset("tips")
ax = sns.scatterplot(x="total_bill", y="tip",                      
                     hue="size", size="size",
                     sizes=(20, 200), hue_norm=(0, 7),
                     legend="full", data=tips)
plt.show()

Partitionner la figure avec GridSpec

L’un des avantages de l’interface orientée objet, c’est que nous pouvons facilement diviser notre figure en plusieurs sous-parcelles et manipuler chaque sous-parcelle avec l’API “axes”.

import matplotlib.gridspec as gridspec

fig = plt.figure(figsize=(7,7))

gs = gridspec.GridSpec(nrows=3, 
                       ncols=3, 
                       figure=fig, 
                       width_ratios= [1, 1, 1],
                       height_ratios=[1, 1, 1],
                       wspace=0.3,
                       hspace=0.3)

ax1 = fig.add_subplot(gs[0, 0])
ax1.text(0.5, 0.5, 'ax1: gs[0, 0]', fontsize=12, fontweight="bold", va="center", ha="center")  # adding text to ax1

ax2 = fig.add_subplot(gs[0, 1:3])
ax2.text(0.5, 0.5, 'ax2: gs[0, 1:3]', fontsize=12, fontweight="bold", va="center", ha="center")

ax3 = fig.add_subplot(gs[1:3, 0:2])
ax3.text(0.5, 0.5, 'ax3: gs[1:3, 0:2]', fontsize=12, fontweight="bold", va="center", ha="center")

ax4 = fig.add_subplot(gs[1:3, 2])
ax4.text(0.5, 0.5, 'ax4: gs[1:3, 2]', fontsize=12, fontweight="bold", va="center", ha="center")

plt.show()

Dans l’exemple, nous divisons d’abord la figure en 3*3 = 9 petites cases avec gridspec.GridSpec(), puis nous définissons quelques objets axes. Chaque objet d’axe peut contenir une ou plusieurs cases.
Disons que dans le code ci-dessus, gs[0, 1:3] = gs[0, 1] + gs[0, 2] est attribué à l’objet axes ax2.
wspace et hspace sont des paramètres contrôlant l’espace entre les tracés.

Créer des visualisations de données Python avancées

Avec les tutoriels des sections précédentes, il est temps de produire des choses intéressantes.
Télécharger les données sur les ventes du Black Friday d’Analytics Vidhya en cliquant ici et faire un pré-traitement facile des données :

df = pd.read_csv('BlackFriday.csv', usecols = ['User_ID', 'Gender', 'Age', 'Purchase'])

df_gp_1 = df[['User_ID', 'Purchase']].groupby('User_ID').agg(np.mean).reset_index()

df_gp_2 = df[['User_ID', 'Gender', 'Age']].groupby('User_ID').agg(max).reset_index()

df_gp = pd.merge(df_gp_1, df_gp_2, on = ['User_ID'])

Vous obtenez alors un tableau indiquant l’identifiant de l’utilisateur, son sexe, son âge et le prix moyen des articles achetés par chaque client.

Etape 1 : Objectif

Nous sommes curieux de savoir comment l’âge et le sexe influent sur le prix moyen des articles achetés pendant le Black Friday, et nous espérons voir la répartition des prix également. Nous voulons également connaître les pourcentages pour chaque groupe d’âge.

Etape 2 : Variables

Nous aimerions inclure le groupe d’âge (catégorique), le sexe (catégorique), le prix moyen de l’article (numérique) et la distribution du prix moyen de l’article (numérique) dans le graphique. Nous devons inclure une autre parcelle avec le pourcentage pour chaque groupe d’âge (groupe d’âge + nombre/fréquence).

Pour montrer le prix moyen de l’article + ses distributions, nous pouvons utiliser le graphique de densité des noyaux (kde), des boîtes à moustaches ou diagramme du violon violon.
Parmi celles-ci, le kde montre la meilleure distribution. Nous traçons ensuite deux ou plusieurs graphiques kde dans la même figure, puis nous faisons des graphiques facet, de sorte que les informations sur le groupe d’âge et le sexe puissent être incluses. Pour l’autre graphique, un diagramme à barres peut faire l’affaire.

Visualisation de données

Une fois que nous avons un plan sur les variables, nous pourrions alors réfléchir à la façon de le visualiser. Nous devons d’abord faire des partitions de figures, cacher certaines limites, des xticks et des yticks, puis ajouter un diagramme à barres sur la droite.

Le graphique ci-dessous est ce que nous allons créer. À partir de la figure, nous pouvons clairement voir que les hommes ont tendance à acheter des articles plus chers que les femmes, d’après les données, et que les personnes âgées ont tendance à acheter des articles plus chers (la tendance est plus claire pour les 4 groupes d’âge supérieurs). Nous avons également constaté que les personnes âgées de 18 à 45 ans sont les principaux acheteurs dans les ventes du Black Friday.

Les codes ci-dessous génèrent ce graphique (les explications sont incluses dans les commentaires) :

freq = ((df_gp.Age.value_counts(normalize = True).reset_index().sort_values(by = 'index').Age)*100).tolist()

number_gp = 7

# freq = le pourcentage pour chaque groupe d'âge (et il y a 7 groupes d'âge).

def ax_settings(ax, var_name, x_min, x_max):
    ax.set_xlim(x_min,x_max)
    ax.set_yticks([])
    
    ax.spines['left'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)
    
    ax.spines['bottom'].set_edgecolor('#444444')
    ax.spines['bottom'].set_linewidth(2)
    
    ax.text(0.02, 0.05, var_name, fontsize=17, fontweight="bold", transform = ax.transAxes) 
    return None

# Manipulez chaque objet de l'axe à gauche. Essayez de régler certains paramètres et vous saurez comment fonctionne chaque commande.

fig = plt.figure(figsize=(12,7))
gs = gridspec.GridSpec(nrows=number_gp, 
                       ncols=2, 
                       figure=fig, 
                       width_ratios= [3, 1],
                       height_ratios= [1]*number_gp,
                       wspace=0.2, hspace=0.05
                      )

ax = [None]*(number_gp + 1)
features = ['0-17', '18-25', '26-35', '36-45', '46-50', '51-55', '55+']

# Créez une figure, divisez la figure en boîtes de 7*2, mettez en place un tableau d'axes pour stocker les objets des axes, et créez une liste de noms de groupes d'âge.  

for i in range(number_gp):
    ax[i] = fig.add_subplot(gs[i, 0])
    
    ax_settings(ax[i], 'Age: ' + str(features[i]), -1000, 20000)    
    
    sns.kdeplot(data=df_gp[(df_gp.Gender == 'M') & (df_gp.Age == features[i])].Purchase, 
            ax=ax[i], shade=True, color="blue",  bw=300, legend=False)
    sns.kdeplot(data=df_gp[(df_gp.Gender == 'F') & (df_gp.Age == features[i])].Purchase, 
            ax=ax[i], shade=True, color="red",  bw=300, legend=False)
    
    if i < (number_gp - 1): 
        ax[i].set_xticks([])

# cette boucle for consiste à créer un ensemble d'objets axes et à les relier à des boîtes GridSpec. Ensuite, nous les manipulons avec sns.kdeplot() et ax_settings() que nous venons de définir.

ax[0].legend(['Hommes', 'Femmes'], facecolor='w')

# ajout de légendes sur l'objet axes     

ax[number_gp] = fig.add_subplot(gs[:, 1])
ax[number_gp].spines['right'].set_visible(False)
ax[number_gp].spines['top'].set_visible(False)

ax[number_gp].barh(features, freq, color='#004c99', height=0.4)
ax[number_gp].set_xlim(0,100)
ax[number_gp].invert_yaxis()
ax[number_gp].text(1.09, -0.04, '(%)', fontsize=10, transform = ax[number_gp].transAxes)   
ax[number_gp].tick_params(axis='y', labelsize = 14)

# Manipulez le diagramme à droite. Essayez de commenter certaines des commandes pour voir ce qu'elles font réellement au diagramme.

plt.show()

J’espère que ce guide vous a plu, si c’est le cas, partagez sur vos réseaux sociaux (Facebook, Twitter, LinkedIn) ou par mail, c’est ce qui m’aide le plus. Vous pouvez aussi consulter ces 2 articles : Visualisations de données Python: comparaison d’outils et Visualisations de données Python : Nuage de points.

Guide des visualisations de données Python avec Seaborn et Matplotlib