De beaux graphiques avec python: mise en pratique

Download nbviewer Onyxia
Binder Open In Colab githubdev

</p>

La pratique de la visualisation se fera, dans ce cours, en répliquant des graphiques qu’on peut trouver sur la page de l’open-data de la ville de Paris ici.

Ce TP vise à initier:

  • Aux packages matplotlib et seaborn pour la construction de graphiques figés
  • Au package plotly pour les graphiques dynamiques, au format HTML

Nous verrons par la suite la manière de construire des cartes facilement avec des formats équivalents.

Un sous-ensemble des données de paris open data a été mis à disposition sur {{< githubrepo >}} pour faciliter l’import (élimination des colonnes qui ne nous serviront pas mais ralentissent l’import).

In [2]:
import matplotlib.pyplot as plt
import seaborn as sns

Premier graphique avec l’API matplotlib de pandas

On peut remarquer plusieurs éléments problématiques (par exemple les labels) mais aussi des éléments ne correspondant pas (les titres des axes, etc.) ou manquants (le nom du graphique…)

Comme les graphiques produits par pandas suivent la logique très flexible de matplotlib, il est possible de les customiser. Cependant, c’est souvent beaucoup de travail et il peut être préférable de directement utiliser seaborn, qui offre quelques arguments prêts à l’emploi.

Utiliser directement seaborn

Vous pouvez repartir des deux dataframes précédents. On va suppose qu’ils se nomment df1 et df2.

Des graphiques dynamiques avec Plotly

Le package Plotly est une surcouche à la librairie Javascript Plotly.js qui permet de créer et manipuler des objets graphiques de manière très flexible afin de produire des objets réactifs sans avoir à recourir à Javascript.

Le point d’entrée recommandé est le module Plotly Express (documentation ici) qui offre une arborescence riche mais néanmoins intuitive pour construire des graphiques (objets plotly.graph_objects.Figure) pouvant être modifiés a posteriori si besoin (par exemple pour customiser les axes).

Comment visualiser un graphique plotly ?

Dans un notebook Jupyter classique, les lignes suivantes de code permettent d’afficher le résultat d’une commande Plotly sous un bloc de code:

from plotly.offline import init_notebook_mode
init_notebook_mode(connected = True)

Pour JupyterLab, l’extension jupyterlab-plotly s’avère nécessaire:

jupyter labextension install jupyterlab-plotly

Pour les utilisateurs de python via l’excellent package R reticulate, il est possible d’écrire le résultats dans un fichier .html et d’utiliser htmltools::includeHTML pour l’afficher via R Markdown (les utilisateurs de R trouveront bien-sûr une technique bien plus simple: utiliser directement le package R plotly…)

Réplication de l’exemple précédent avec plotly

Les modules suivants seront nécessaires pour construire des graphiques avec plotly:

In [25]:
import plotly
import plotly.express as px
from IPython.display import HTML #pour afficher les graphs
# dans une cellule de notebook

La première question permet de construire le graphique suivant:

{{< chart data=“plotly1” >}}

Alors qu’avec le thème sombre (question 2), on obtient :

{{< chart data=“plotly2” >}}

Exercices supplémentaires

Pour ces exercices, il est recommandé de s’inspirer des modèles présents dans la librairie de graphiques Python présentée dans https://www.python-graph-gallery.com/

Les lollipop chart

Cet exercice permet de s’entraîner sur le fichier des naissances et des décès de l’Insee. Il s’inspire d’une excellente visualisation faite par Jean Dupin sur Twitter mettant en avant l’évolution, année par année, des décomptes des personnes nommées “Jean” parmi les personnes nées ou décédées:

{{< tweet 1539567143972487169 >}}

L’animation de Jean Dupin est beaucoup plus raffinée que celle que nous allons mettre en oeuvre.

Récupération des données

La récupération des données étant un peu complexe, le code est donné pour vous permettre de vous concentrer sur l’essentiel (si vous voulez vous exercer avec le package requests, essayez de le faire vous-même).

Les données des décès sont disponibles de manière historique dans des zip pour chaque année.

In [28]:
import shutil
import requests
import zipfile
import os
import glob
import pandas as pd

def import_by_decade(decennie = 1970):

    url = f"https://www.insee.fr/fr/statistiques/fichier/4769950/deces-{decennie}-{decennie+9}-csv.zip"

    req = requests.get(url)

    with open(f"deces_{decennie}.zip",'wb') as f:
        f.write(req.content)

    with zipfile.ZipFile(f"deces_{decennie}.zip", 'r') as zip_ref:
        zip_ref.extractall(f"deces_{decennie}")

    csv_files = glob.glob(os.path.join(f"deces_{decennie}", "*.csv"))

    df = [pd.read_csv(f, sep = ";", encoding="utf-8").assign(annee = f) for f in csv_files]
    df = pd.concat(df)
    df[['nom','prenom']] = df['nomprenom'].str.split("*", expand=True)
    df['prenom'] = df['prenom'].str.replace("/","")
    df['annee'] = df['annee'].str.rsplit("/").str[-1].str.replace("(Deces_|.csv|deces-)","").astype(int)

    shutil.rmtree(f"deces_{decennie}")    
    os.remove(f"deces_{decennie}.zip")

    return df


dfs = [import_by_decade(d) for d in [1970, 1980, 1990, 2000, 2010]]
deces = pd.concat(dfs)

Le fichier des naissances est plus simple à récupérer. Voici le code pour l’obtenir:

In [29]:
year = 2021
url_naissance = f"https://www.insee.fr/fr/statistiques/fichier/2540004/nat{year}_csv.zip"

req = requests.get(url_naissance)

with open(f"naissance_{year}.zip",'wb') as f:
    f.write(req.content)

with zipfile.ZipFile(f"naissance_{year}.zip", 'r') as zip_ref:
    zip_ref.extractall(f"naissance_{year}")

naissance = pd.read_csv(f"naissance_{year}/nat{year}.csv", sep = ";")
naissance = naissance.dropna(subset = ['preusuel'] )

On peut enfin restructurer les DataFrames pour obtenir un seul jeu de données, en se restreignant aux “JEAN”:

In [30]:
jean_naiss = naissance.loc[naissance['preusuel'] == "JEAN"].loc[:, ['annais', 'nombre']]
jean_naiss = jean_naiss.rename({"annais": "annee"}, axis = "columns")
jean_naiss = jean_naiss.groupby('annee').sum().reset_index()
jean_deces = deces.loc[deces["prenom"] == "JEAN"]
jean_deces = jean_deces.groupby('annee').size().reset_index()
jean_deces.columns = ['annee', "nombre"]
jean_naiss.columns = ['annee', "nombre"]
df = pd.concat(
    [
        jean_deces.assign(source = "deces"),
        jean_naiss.assign(source = "naissance")
    ])
df = df.loc[df['annee'] != "XXXX"]
df['annee']=df['annee'].astype(int)
df = df.loc[df['annee'] > 1971]

df.head(3)
In [31]:
 
annee nombre source
0 1972 3017 deces
1 1973 3116 deces
2 1974 3298 deces

Représentation graphique

Vous pouvez vous aider du modèle présent dans https://www.python-graph-gallery.com

A ce stade, vous devriez avoir une version fonctionnelle qui peut servir de base à la généralisation.