De beaux graphiques avec python : mise en pratique

Une partie essentielle du travail du data scientist est d’être en mesure de synthétiser une information dans des représentations graphiques percutantes. Ce chapitre permet de découvrir les fonctionalités graphiques de matplotlib, seaborn et plotly pour représenter des statistiques sur les décomptes de vélo à Paris.

Visualisation
Exercice
Author

Lino Galiana

Published

2024-05-08

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 :

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

Si vous êtes intéressés par R , une version très proche de ce TP est disponible dans ce cours d’introduction à R pour l’ENS.

1 Données

Un sous-ensemble des données de Paris Open Data a été mis à disposition pour faciliter l’import. Il s’agit d’une extraction, qui commence à dater, des données disponibles sur le site où seules les colonnes qui servent à cet exercice ont été conservées.

2 Premières productions graphiques avec l’API Matplotlib de Pandas

Chercher à produire une visualisation parfaite du premier coup est illusoire. Il est beaucoup plus réaliste d’améliorer graduellement une représentation graphique afin, petit à petit, de mettre en avant les effets de structure dans un jeu de données.

Nous allons donc commencer par nous représenter la distribution des passages aux principales stations de mesure. Pour cela nous allons produire rapidement un barplot puis l’améliorer graduellement.

Dans cette partie, nous allons ainsi reproduire les deux premiers graphiques de la page d’analyse des données : Les 10 compteurs avec la moyenne horaire la plus élevée et Les 10 compteurs ayant comptabilisé le plus de vélos. Les valeurs chiffrées des graphiques seront différentes de celles de la page en ligne, c’est normal, nous travaillons sur des données plus anciennes.

Premières lignes nécessaires pour cet exercice :
Identifiant du compteur Nom du compteur Identifiant du site de comptage Nom du site de comptage Comptage horaire Date et heure de comptage Date d'installation du site de comptage
0 100003096-SC 97 avenue Denfert Rochereau SO-NE 100003096 97 avenue Denfert Rochereau SO-NE 1 2019-08-01T02:00:00Z 2012-02-22
1 100003096-SC 97 avenue Denfert Rochereau SO-NE 100003096 97 avenue Denfert Rochereau SO-NE 0 2019-08-01T01:00:00Z 2012-02-22
2 100003096-SC 97 avenue Denfert Rochereau SO-NE 100003096 97 avenue Denfert Rochereau SO-NE 0 2019-08-01T04:00:00Z 2012-02-22
Les 10 principales stations à l'issue de la question 2
Comptage horaire
Nom du compteur
26 boulevard de Ménilmontant SE-NO 109.462847
35 boulevard de Menilmontant NO-SE 117.180643
21 boulevard Saint Michel S-N 117.730884
67 boulevard Voltaire SE-NO 119.208018
72 boulevard Voltaire NO-SE 124.391365
Figure 1 sans travail sur le style:

Figure 2 sans travail sur le style:

On commence à avoir quelque chose qui commence à transmettre un message synthétique sur la nature des données. On peut néanmoins 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 la grammaire matplotlib n’est pas aussi normalisée que celle de ggplot en R. Il peut être préférable de directement utiliser seaborn, qui offre quelques arguments prêts à l’emploi.

3 Utiliser directement seaborn

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

La figure comporte maintenant un message mais il est encore peu lisible. Il y a plusieurs manières de faire un barplot en seaborn. Les deux principales sont :

  • sns.catplot ;
  • sns.barplot.

On propose d’utiliser sns.catplot pour cet exercice.

A l’issue de la question 2, c’est-à-dire en utilisant seaborn pour reproduire de manière minimale un barplot, on obtient :

Après quelques réglages esthétiques, à l’issue des questions 3 et 4, on obtient une figure proche de celle de l’open data parisien.

On comprend ainsi que le boulevard de Sébastopol est le plus emprunté, ce qui ne vous suprendra pas si vous faites du vélo à Paris. Néanmoins, si vous n’êtes pas familiers avec la géographie parisienne, cela sera peu informatif pour vous, vous allez avoir besoin d’une représentation graphique supplémentaire: une carte ! Nous verrons ceci lors d’un prochain chapitre.

Les diagrammes en batons (barplot) sont extrêmement communs mais qu’ils transmettent. Sur le plan sémiologique, les lollipop charts sont préférables : ils transmettent la même information mais avec moins de bruit (la largeur des barres du barplot noie un peu l’information).

Text(0, 0.5, 'La somme des vélos comptabilisés sur la période sélectionnée')

4 Premières agrégations temporelles

On va maintenant se concentrer sur la dimension spatiale de notre jeu de données à travers deux approches :

  • Un diagramme en barre synthétisant l’information de notre jeu de données de manière mensuelle ;
  • Des séries instructives sur la dynamique temporelle. Cela sera l’objet de la prochaine partie.

Pour commencer, reproduisons la troisième figure qui est, encore une fois, un barplot. La première question implique une première rencontre avec une donnée temporelle à travers une opération assez classique en séries temporelles : changer le format d’une date pour pouvoir faire une agrégation à un pas de temps plus large.

month value
0 2019-08 33.637536
1 2019-09 55.831038

Si vous préférez représenter cela sous forme de lollipop1:

5 Première série temporelle

Il est plus commun de représenter sous forme de série les données ayant une dimension temporelle.

6 Des graphiques dynamiques avec Plotly

6.1 Contexte

L’inconvénient des figures avec ggplot est que celles-ci ne permettent pas d’interaction avec le lecteur. Toute l’information doit donc être contenue dans la figure ce qui peut la rendre difficile à lire. Si la figure est bien faite, avec différents niveaux d’information, cela peut bien fonctionner.

Il est néanmoins plus simple, grâce aux technologies web, de proposer des visualisations à plusieurs niveaux. Un premier niveau d’information, celui du coup d’oeil, peut suffire à assimiler les principaux messages de la visualisation. Ensuite, un comportement plus volontaire de recherche d’information secondaire peut permettre d’en savoir plus. Les visualisations réactives, qui sont maintenant la norme dans le monde de la dataviz, permettent ce type d’approche : le lecteur d’une visualisation peut passer sa souris à la recherche d’information complémentaire (par exemple les valeurs exactes) ou cliquer pour faire apparaître des informations complémentaires sur la visualisation ou autour.

Ces visualisations reposent sur le même triptyque que l’ensemble de l’écosystème web : HTML, CSS et JavaScript. Les utilisateurs de Python ne vont jamais manipuler directement ces langages, qui demandent une certaine expertise, mais vont utiliser des librairies au niveau de R qui génèreront automatiquement tout le code HTML, CSS et JavaScript permettant de créer la figure.

6.2 La librairie 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).

6.3 Réplication de l’exemple précédent avec Plotly

Les représentations figées comme celles ci-dessus sont approriées pour des rapports ou articles. Néanmoins

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

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

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

Cette représentation montre bien le caractère spécial de l’année 2020. Pour rappeller au lecteur distrait la nature particulière de la période, marquée par un premier confinement qu’on voit bien dans les données, on pourrait, avec l’aide de la documentation, ajouter deux barres verticales pour marquer les dates de début et de fin de cette période.

7 Bonus

En bonus, l’état d’esprit des habitués de ggplot2 quand ils découvrent plotnine:

Informations additionnelles

environment files have been tested on.

Latest built version: 2024-05-08

Python version used:

'3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:40:35) [GCC 12.3.0]'
Package Version
affine 2.4.0
aiobotocore 2.12.2
aiohttp 3.9.3
aioitertools 0.11.0
aiosignal 1.3.1
alembic 1.13.1
aniso8601 9.0.1
annotated-types 0.6.0
appdirs 1.4.4
archspec 0.2.3
astroid 3.1.0
asttokens 2.4.1
attrs 23.2.0
Babel 2.15.0
bcrypt 4.1.2
beautifulsoup4 4.12.3
black 24.4.2
blinker 1.7.0
blis 0.7.11
bokeh 3.4.0
boltons 23.1.1
boto3 1.34.51
botocore 1.34.51
branca 0.7.1
Brotli 1.1.0
cachetools 5.3.3
cartiflette 0.0.2
Cartopy 0.23.0
catalogue 2.0.10
cattrs 23.2.3
certifi 2024.2.2
cffi 1.16.0
charset-normalizer 3.3.2
chromedriver-autoinstaller 0.6.4
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.16.0
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
commonmark 0.9.1
conda 24.3.0
conda-libmamba-solver 24.1.0
conda-package-handling 2.2.0
conda_package_streaming 0.9.0
confection 0.1.4
contextily 1.6.0
contourpy 1.2.1
cryptography 42.0.5
cycler 0.12.1
cymem 2.0.8
cytoolz 0.12.3
dask 2024.4.1
dask-expr 1.0.10
debugpy 1.8.1
decorator 5.1.1
dill 0.3.8
distributed 2024.4.1
distro 1.9.0
docker 7.0.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et-xmlfile 1.1.0
exceptiongroup 1.2.0
executing 2.0.1
fastjsonschema 2.19.1
fiona 1.9.6
flake8 7.0.0
Flask 3.0.2
folium 0.16.0
fontawesomefree 6.5.1
fonttools 4.51.0
frozenlist 1.4.1
fsspec 2023.12.2
GDAL 3.8.4
gensim 4.3.2
geographiclib 2.0
geopandas 0.12.2
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.29.0
graphene 3.3
graphql-core 3.2.3
graphql-relay 3.2.0
graphviz 0.20.3
great-tables 0.5.1
greenlet 3.0.3
gunicorn 21.2.0
h11 0.14.0
htmltools 0.5.1
hvac 2.1.0
idna 3.6
imageio 2.34.1
importlib_metadata 7.1.0
importlib_resources 6.4.0
inflate64 1.0.0
ipykernel 6.29.3
ipython 8.22.2
ipywidgets 8.1.2
isort 5.13.2
itsdangerous 2.1.2
jedi 0.19.1
Jinja2 3.1.3
jmespath 1.0.1
joblib 1.3.2
jsonpatch 1.33
jsonpointer 2.4
jsonschema 4.21.1
jsonschema-specifications 2023.12.1
jupyter-cache 1.0.0
jupyter_client 8.6.1
jupyter_core 5.7.2
jupyterlab_widgets 3.0.10
kaleido 0.2.1
kiwisolver 1.4.5
kubernetes 29.0.0
langcodes 3.4.0
language_data 1.2.0
lazy_loader 0.4
libmambapy 1.5.7
llvmlite 0.42.0
locket 1.0.0
lxml 5.2.1
lz4 4.3.3
Mako 1.3.2
mamba 1.5.7
mapclassify 2.6.1
marisa-trie 1.1.1
Markdown 3.6
MarkupSafe 2.1.5
matplotlib 3.8.3
matplotlib-inline 0.1.6
mccabe 0.7.0
menuinst 2.0.2
mercantile 1.2.1
mizani 0.11.2
mlflow 2.11.3
mlflow-skinny 2.11.3
msgpack 1.0.7
multidict 6.0.5
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.10
mypy 1.9.0
mypy-extensions 1.0.0
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.8.1
numba 0.59.1
numpy 1.26.4
oauthlib 3.2.2
opencv-python-headless 4.9.0.80
openpyxl 3.1.2
outcome 1.3.0.post0
OWSLib 0.28.1
packaging 23.2
pandas 2.2.1
paramiko 3.4.0
parso 0.8.4
partd 1.4.1
pathspec 0.12.1
patsy 0.5.6
Pebble 5.0.7
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.3.0
pip 24.0
pkgutil_resolve_name 1.3.10
platformdirs 4.2.0
plotly 5.19.0
plotnine 0.13.5
pluggy 1.4.0
polars 0.20.18
preshed 3.0.9
prometheus_client 0.20.0
prometheus-flask-exporter 0.23.0
prompt-toolkit 3.0.42
protobuf 4.25.3
psutil 5.9.8
ptyprocess 0.7.0
pure-eval 0.2.2
py7zr 0.20.8
pyarrow 15.0.0
pyarrow-hotfix 0.6
pyasn1 0.5.1
pyasn1-modules 0.3.0
pybcj 1.0.2
pycodestyle 2.11.1
pycosat 0.6.6
pycparser 2.21
pycryptodomex 3.20.0
pydantic 2.7.1
pydantic_core 2.18.2
pyflakes 3.2.0
Pygments 2.17.2
PyJWT 2.8.0
pylint 3.1.0
PyNaCl 1.5.0
pynsee 0.1.7
pyOpenSSL 24.0.0
pyparsing 3.1.2
pyppmd 1.1.0
pyproj 3.6.1
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.0
PyYAML 6.0.1
pyzmq 25.1.2
pyzstd 0.15.10
QtPy 2.4.1
querystring-parser 1.2.4
rasterio 1.3.10
referencing 0.34.0
regex 2023.12.25
requests 2.31.0
requests-cache 1.2.0
requests-oauthlib 2.0.0
rpds-py 0.18.0
rsa 4.9
Rtree 1.2.0
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.1
scikit-image 0.23.2
scikit-learn 1.4.1.post1
scipy 1.13.0
seaborn 0.13.2
selenium 4.20.0
setuptools 69.2.0
shapely 2.0.3
six 1.16.0
smart-open 6.4.0
smmap 5.0.0
sniffio 1.3.1
snuggs 1.4.7
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.4
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.29
sqlparse 0.4.4
srsly 2.4.8
stack-data 0.6.2
statsmodels 0.14.1
tabulate 0.9.0
tblib 3.0.0
tenacity 8.2.3
texttable 1.7.0
thinc 8.2.3
threadpoolctl 3.4.0
tifffile 2024.5.3
tomli 2.0.1
tomlkit 0.12.4
toolz 0.12.1
topojson 1.8
tornado 6.4
tqdm 4.66.2
traitlets 5.14.2
trio 0.25.0
trio-websocket 0.11.1
truststore 0.8.0
typer 0.9.4
typing_extensions 4.11.0
tzdata 2024.1
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.18
wasabi 1.1.2
wcwidth 0.2.13
weasel 0.3.4
webcolors 1.13
webdriver-manager 4.0.1
websocket-client 1.7.0
Werkzeug 3.0.2
wheel 0.43.0
widgetsnbextension 4.0.10
wordcloud 1.9.3
wrapt 1.16.0
wsproto 1.2.0
xgboost 2.0.3
xlrd 2.0.1
xyzservices 2024.4.0
yarl 1.9.4
yellowbrick 1.5
zict 3.0.0
zipp 3.17.0
zstandard 0.22.0

View file history

SHA Date Author Description
06d003a 2024-04-23 10:09:22 Lino Galiana Continue la restructuration des sous-parties (#492)
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3fba612 2023-12-17 18:16:42 Lino Galiana Remove some badges from python (#476)
cf91965 2023-12-02 13:15:18 linogaliana href in dataviz chapter
1f23de2 2023-12-01 17:25:36 Lino Galiana Stockage des images sur S3 (#466)
09654c7 2023-11-14 15:16:44 Antoine Palazzolo Suggestions Git & Visualisation (#449)
889a71b 2023-11-10 11:40:51 Antoine Palazzolo Modification TP 3 (#443)
df01f01 2023-10-10 15:55:04 Lino Galiana Menus automatisés (#432)
a771183 2023-10-09 11:27:45 Antoine Palazzolo Relecture TD2 par Antoine (#418)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
057dae1 2023-09-20 16:28:46 Lino Galiana Chapitre visualisation (#406)
1d0780c 2023-09-18 14:49:59 Lino Galiana Problème rendu chapitre matplotlib (#405)
a8f90c2 2023-08-28 09:26:12 Lino Galiana Update featured paths (#396)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
78ea2cb 2023-07-20 20:27:31 Lino Galiana Change titles levels (#381)
8df7cb2 2023-07-20 17:16:03 linogaliana Change link
f0c583c 2023-07-07 14:12:22 Lino Galiana Images viz (#371)
f21a24d 2023-07-02 10:58:15 Lino Galiana Pipeline Quarto & Pages 🚀 (#365)
f2e8922 2023-06-12 14:54:20 Lino Galiana Remove spoiler shortcode (#364)
2dc82e7 2022-10-18 22:46:47 Lino Galiana Relec Kim (visualisation + API) (#302)
03babc6 2022-10-03 16:53:47 Lino Galiana Parler des règles de la dataviz (#291)
89c10c3 2022-08-25 08:30:22 Lino Galiana Adaptation du shortcode spoiler en notebook (#257)
494a85a 2022-08-05 14:49:56 Lino Galiana Images featured ✨ (#252)
d201e3c 2022-08-03 15:50:34 Lino Galiana Pimp la homepage ✨ (#249)
2812ef4 2022-07-07 15:58:58 Lino Galiana Petite viz sympa des prenoms (#242)
a4e2426 2022-06-16 19:34:18 Lino Galiana Improve style (#238)
02ed1e2 2022-06-09 19:06:05 Lino Galiana Règle problème plotly (#235)
299cff3 2022-06-08 13:19:03 Lino Galiana Problème code JS suite (#233)
5698e30 2022-06-03 18:28:37 Lino Galiana Finalise widget (#232)
7b9f27b 2022-06-03 17:05:15 Lino Galiana Essaie régler les problèmes widgets JS (#231)
12965ba 2022-05-25 15:53:27 Lino Galiana :launch: Bascule vers quarto (#226)
9c71d6e 2022-03-08 10:34:26 Lino Galiana Plus d’éléments sur S3 (#218)
4f67528 2021-12-12 08:37:21 Lino Galiana Improve website appareance (#194)
66a5276 2021-11-23 16:13:20 Lino Galiana Relecture partie visualisation (#181)
2a8809f 2021-10-27 12:05:34 Lino Galiana Simplification des hooks pour gagner en flexibilité et clarté (#166)
2f4d390 2021-09-02 15:12:29 Lino Galiana Utilise un shortcode github (#131)
2e4d586 2021-09-02 12:03:39 Lino Galiana Simplify badges generation (#130)
80877d2 2021-06-28 11:34:24 Lino Galiana Ajout d’un exercice de NLP à partir openfood database (#98)
6729a72 2021-06-22 18:07:05 Lino Galiana Mise à jour badge onyxia (#115)
4cdb759 2021-05-12 10:37:23 Lino Galiana :sparkles: :star2: Nouveau thème hugo :snake: :fire: (#105)
7f9f97b 2021-04-30 21:44:04 Lino Galiana 🐳 + 🐍 New workflow (docker 🐳) and new dataset for modelization (2020 🇺🇸 elections) (#99)
0a0d034 2021-03-26 20:16:22 Lino Galiana Ajout d’une section sur S3 (#97)
a5b7c99 2020-10-05 15:07:09 Lino Galiana Donne lien vers données compteurs
18be8f4 2020-10-01 17:08:53 Lino Galiana Intégration de box inspirées du thème pydata sphinx (#58)
5ac3cbe 2020-09-28 18:59:24 Lino Galiana Continue la partie graphiques (#54)
94f39ec 2020-09-24 21:25:32 Lino Galiana quelques mots sur vizu
Back to top

Footnotes

  1. J’ai retiré la couleur sur l’axe des ordonnées qui, je trouve, apporte peu à la figure voire dégrade la compréhension du message.↩︎

Citation

BibTeX citation:
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python Pour La Data Science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {en}
}
For attribution, please cite this work as:
Galiana, Lino. 2023. Python Pour La Data Science. https://doi.org/10.5281/zenodo.8229676.