Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
PE – Python numérique – S5
S5 · mardi 13 octobre 2026
Raphael Prasquier · cours d'Aurélien Noce – UE12 Python numérique
PE – Python numérique – S5
Au programme aujourd'hui
Quiz 4 (10 min, papier) sur la séance 4 et le devoir D4, puis correction flash.
Notebooks :
2-06 df.plot() : tracer directement depuis pandas
2-11 séries temporelles : to_datetime, .dt, DatetimeIndex, resample, rolling
3-01 matplotlib : figure, courbes, habillage, savefig
3-02 grilles de sous-figures : subplots
Montrer des données : une question, une forme ; et 7 façons dont une figure peut mentir
TP : Covid par pays — multi-index, dates, unstack, figures comparatives
Devoir : D5 « mini-projet série temporelle », pour le dimanche 18 octobre , 23 h 59
PE – Python numérique – S5
Retour sur le quiz 4
Après groupby, la clé est dans l'index. Pour la retrouver en colonne : as_index=False
ou reset_index(). Beaucoup de KeyError évitables.
concat empile, merge aligne sur une clé. Et merge est inner par défaut :
les lignes orphelines disparaissent — on compare les shape.
pivot_table(index=, columns=, values=, aggfunc=) : quatre arguments, quatre rôles.
values est ce qu'on agrège, pas ce qu'on met en ligne.
Pour D4 : une production « par habitant » se calcule après le merge, colonne sur colonne — pas dans une boucle.
PE – Python numérique – S5
df.plot() : une courbe par colonne
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("data/Amazon.csv" , comment="#" ,
parse_dates=["Date" ], index_col="Date" )
df[["High" , "Low" ]].tail(60 ).plot()
pandas enveloppe matplotlib : l'index en abscisse , une courbe par colonne, la légende en prime.
subplots=True : une sous-figure par colonne.
Les variantes (plot.hist, plot.bar, plot.scatter, boxplot…), une par question : c'est le guide Quel graphique pour quelles données , plus loin.
PE – Python numérique – S5
Les dates arrivent en texte
df = pd.read_csv("data/Amazon.csv" , comment="#" )
df["Date" ].dtype
df["Date" ] = pd.to_datetime(df["Date" ],
format ="%Y-%m-%d" )
%d jour · %m mois · %Y année · %H:%M:%S heure
trié comme du texte
trié comme des dates
02/03/2020
31/12/2019
12/01/2020
12/01/2020
31/12/2019
02/03/2020
pd.to_datetime("03/04/2020") donne le 4 mars (mois d'abord, à l'américaine) : passez toujours format=.
PE – Python numérique – S5
df = df.dropna(subset=["Date" ])
d = df["Date" ]
d.dt.year, d.dt.month, d.dt.dayofweek
d.dt.day_name()
df.groupby(d.dt.year)["Close" ].mean().plot.bar()
Une fois en datetime64, chaque composante devient une clé de groupby.
PE – Python numérique – S5
Les dates en index : couper par périodes
df = df.set_index("Date" ).sort_index()
df.loc["2019" ]
df.loc["2020-04" :"2020-06" , "Close" ].plot()
Une chaîne partielle désigne toute la période, bornes incluses (juin jusqu'au 30).
sort_index() d'abord : sur un index en désordre, le slicing échoue ou ment.
PE – Python numérique – S5
resample : une valeur par période
s = df["Close" ].loc["2019" :]
s.plot(alpha=0.4 , label="chaque jour" )
s.resample("ME" ).mean().plot(marker="o" ,
label="moyenne du mois" )
plt.legend()
Moins de points, à dates régulières ; il faut dire comment agréger (.mean(), .sum()…).
D jour · W semaine · ME mois · YE année
PE – Python numérique – S5
rolling : lisser sans perdre de points
s.plot(alpha=0.4 , label="chaque jour" )
s.rolling(28 ).mean().plot(label="moyenne sur 28 jours" )
plt.legend()
Chaque point devient la moyenne de la fenêtre qui le précède ; les 27 premiers sont NaN.
resample réduit , rolling lisse : ça tombe souvent en quiz !
PE – Python numérique – S5
long : une ligne par (date, pays)
Date
Pays
Deces
15/03
France
127
15/03
Italie
1 809
16/03
France
148
16/03
Italie
2 158
large : une colonne par pays
Date
France
Italie
15/03
127
1 809
16/03
148
2 158
df.plot() trace une courbe par colonne : c'est le format qu'il veut.
cv = pd.read_csv("data/corona-par-pays.csv" , sep=";" , comment="#" , parse_dates=["Date" ])
s = cv[cv["Pays" ].isin(["France" , "Italie" ])].groupby(["Date" , "Pays" ])["Deces" ].sum ()
s.unstack().plot()
PE – Python numérique – S5
import numpy as np
x = np.linspace(0 , 2 * np.pi, 100 )
fig, ax = plt.subplots(figsize=(8 , 4 ))
ax.plot(x, np.sin(x), label="sin" )
ax.plot(x, np.cos(x), "--" , label="cos" )
ax.set_title("deux fonctions" )
ax.set_xlabel("x" ); ax.set_ylabel("y" )
ax.legend()
fig : la page ; ax : un repère dessus. df.plot(ax=ax) dessine dans un repère existant.
PE – Python numérique – S5
subplots : une grille de repères
fig, axes = plt.subplots(2 , 2 , figsize=(10 , 6 ))
axes[0 , 0 ].plot(x, np.sin(x))
axes[0 , 0 ].set_title("sin" )
axes[0 , 1 ].plot(x, np.cos(x))
axes[0 , 1 ].set_title("cos" )
axes[1 , 0 ].hist(np.random.randn(1000 ), bins=30 )
s.unstack().plot(ax=axes[1 , 1 ])
fig.tight_layout()
axes est un tableau numpy : axes.shape == (2, 2).
PE – Python numérique – S5
Sauvegarder : savefig avant show
fig.savefig("figure.png" , dpi=150 , bbox_inches="tight" )
fig.savefig("figure.pdf" )
plt.show()
dans un notebook, la figure s'affiche toute seule ; plt.show() n'est utile qu'en script ;
savefig après show produit une image vide : show a vidé la figure ;
un ; en fin de cellule (df.plot();) masque le <Axes: ...> inutile.
Le PNG est ce qu'on rend dans les devoirs et ce qu'on colle dans un rapport : pensez à sauvegarder chaque figure dans un fichier.
PE – Python numérique – S5
🎨 Montrer des données · 1/3
Quatre jeux de 11 points (Anscombe, 1973) : même moyenne, même variance, même corrélation, même droite de régression. Seule la figure révèle quatre histoires différentes.
Le réflexe : tracer avant de résumer . Un describe() ne remplace pas un plot.scatter() ou un hist().
PE – Python numérique – S5
🎨 Montrer des données · 2/3
Explorer pour soi, présenter pour un lecteur
Explorer : on ne connaît pas encore les données, on trace vite, on jette.
Présenter : une question, un titre qui donne la réponse, des unités, une source.
PE – Python numérique – S5
🎨 Montrer des données · 3/3
évolution → courbe
évolution d'un total → aires
comparer → barres
croiser → barres groupées
répartition → histogramme
comparer des groupes → boîtes
lien → nuage de points
deux entrées → carte de chaleur
Le détail de chacun, avec son code, dans les neuf slides suivantes.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 1/9 · une évolution
Courbe (line plot)
Pour : une tendance, des pics, une saisonnalité.
Données : une ou quelques colonnes numériques, des dates en index.
df = pd.read_csv("data/Amazon.csv" , comment="#" ,
parse_dates=["Date" ], index_col="Date" )
df["Close" ].plot(ylabel="cours de clôture ($)" )
au-delà de 5 ou 6 courbes, c'est un plat de spaghettis : subplots=True.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 2/9 · une évolution
Aires empilées (area plot)
Pour : un total et la part de chacun dans ce total, au fil du temps.
Données : plusieurs colonnes positives qui s'additionnent.
cv = pd.read_csv("data/corona-par-pays.csv" , sep=";" , comment="#" ,
parse_dates=["Date" ])
deces = cv.pivot_table(index="Date" , columns="Pays" , values="Deces" )
deces[["Italie" , "Espagne" , "France" , "Belgique" ]].plot.area()
seule la couche du bas se lit bien ; pour comparer les pays entre eux, des courbes.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 3/9 · des catégories
Barres (bar chart)
Pour : classer et comparer une quantité entre catégories.
Données : une colonne catégorielle et un nombre par catégorie.
cv = pd.read_csv("data/corona-par-pays.csv" , sep=";" , comment="#" )
dernier = cv[cv["Date" ] == cv["Date" ].max ()].set_index("Pays" )
(dernier["Deces" ].nlargest(8 ) / 1000 ).sort_values().plot.barh()
plt.xlabel("décès (milliers)" )
les barres partent de zéro ; on trie, sauf si l'ordre a un sens (mois, classes d'âge).
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 4/9 · des catégories
Pour : croiser deux catégories, c-à-d. lire un tableau croisé.
Données : un pivot_table : une catégorie en ligne, l'autre en colonne.
titanic = pd.read_csv("data/titanic.csv" )
taux = titanic.pivot_table(index="Pclass" , columns="Sex" , values="Survived" )
taux.plot.bar(rot=0 , ylabel="taux de survie" )
groupées pour des taux ; empilées (stacked=True) seulement pour des effectifs qui s'additionnent.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 5/9 · des catégories
Pour : des effectifs par catégorie, et leur composition.
Données : un tableau de comptages (pd.crosstab), dont les colonnes s'additionnent.
titanic = pd.read_csv("data/titanic.csv" )
effectifs = pd.crosstab(titanic["Pclass" ], titanic["Sex" ])
effectifs.plot.bar(stacked=True , rot=0 , ylabel="passagers" )
seul le bas de la pile se compare facilement ; pour comparer des proportions, pd.crosstab(..., normalize="index").
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 6/9 · une distribution
Histogramme (histogram)
Pour : où se trouvent la plupart des valeurs, leur étalement, une asymétrie.
Données : une colonne numérique, une valeur par individu.
titanic = pd.read_csv("data/titanic.csv" )
titanic["Age" ].plot.hist(bins=30 , xlabel="âge" )
le nombre de classes (bins) change la forme : essayez-en plusieurs.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 7/9 · une distribution
Boîte à moustaches (box plot)
Pour : comparer des distributions entre groupes : médiane, quartiles, valeurs atypiques.
Données : une colonne numérique et une colonne de groupe.
titanic = pd.read_csv("data/titanic.csv" )
titanic.boxplot(column="Age" , by="Pclass" )
une boîte cache l'effectif : 30 ou 500 passagers ont la même allure.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 8/9 · une relation
Nuage de points (scatter plot)
Pour : une tendance, des groupes, des points isolés ; une 3e variable en couleur (c=).
Données : deux colonnes numériques, une ligne par individu.
iris = pd.read_csv("data/iris.csv" )
iris.plot.scatter(x="PetalLength" , y="PetalWidth" ,
c="SepalLength" , colormap="viridis" )
beaucoup de points se recouvrent : alpha=0.3 ; et un lien n'est pas une cause.
PE – Python numérique – S5
📊 Quel graphique pour quelles données · 9/9 · une relation
Carte de chaleur (heatmap)
Pour : une valeur selon deux variables : jour × heure, matrice de corrélations.
Données : un tableau à deux entrées (groupby + unstack).
fr = pd.read_csv("data/fremont.csv" , index_col="Date" , parse_dates=["Date" ],
date_format="%m/%d/%Y %I:%M:%S %p" )
v = fr["Fremont Bridge Total" ]
tab = v.groupby([v.index.dayofweek, v.index.hour]).mean().unstack()
plt.imshow(tab, aspect="auto" ); plt.colorbar(label="vélos / heure" )
plt.yticks(range (7 ), ["lun" , "mar" , "mer" , "jeu" , "ven" , "sam" , "dim" ])
une palette du clair au foncé, et toujours la barre de couleurs.
PE – Python numérique – S5
Sept graphiques trompeurs, en plein écran : où est le problème ?
Puis, pour chacun, la version honnête et la règle à retenir.
⚠️ Une figure peut mentir · 1/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 1/7 · l'axe tronqué
Six fois moins de vélos le vendredi ? Non : 14 % de moins
Une barre se lit par sa longueur : elle part de zéro (ax.set_ylim(0, None)).
Fremont Bridge, Seattle : moyenne des vélos par jour ouvré, octobre 2012 – septembre 2022 (fremont.csv).
⚠️ Une figure peut mentir · 2/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 2/7 · la fenêtre choisie
Amazon perd 34 % en quatre mois… au milieu de cinq ans de hausse
Qui choisit la période choisit la conclusion : montrez la série entière avant de zoomer.
⚠️ Une figure peut mentir · 3/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 3/7 · cumul ou quotidien
Un cumul ne baisse jamais : les vagues sont dans les écarts
Pour voir les vagues, tracez les nouveaux cas : s.diff().rolling(7).mean().
⚠️ Une figure peut mentir · 4/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 4/7 · linéaire ou log
Linéaire ou log : deux lectures de la même épidémie
En log, une droite = une croissance exponentielle ; annoncez-le (logy=True).
⚠️ Une figure peut mentir · 5/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 5/7 · compter ou rapporter
Les États-Unis en tête… ou cinquièmes : tout dépend du dénominateur
Pour comparer des pays, rapportez à la population (le merge de D4).
⚠️ Une figure peut mentir · 6/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 6/7 · le double axe
Deux axes y : c'est l'auteur qui choisit où les courbes se croisent
Évitez twinx : deux sous-figures , ou une base 100 commune.
⚠️ Une figure peut mentir · 7/7 · où est le problème ?
PE – Python numérique – S5
⚠️ Une figure peut mentir · 7/7 · angles et aires
L'œil compare des longueurs, pas des angles
Préférez des barres triées ; le camembert, à deux ou trois parts au plus.
Production éolienne par région, données du devoir D4.
PE – Python numérique – S5
Questions pour un champion : où est le problème ?
Dix graphiques, dont six vus dans la presse française, en plein écran.
Trente secondes chacun : qu'est-ce qui cloche ?
🏆 Où est le problème ? · 1/10
PE – Python numérique – S5
🏆 Où est le problème ? · 1/10 · réponse
22,5 % → 19,84 % : la barre divisée par deux pour − 2,7 points
Pas d'axe, pas de zéro : vérifiez que la hauteur suit le chiffre.
Recréé d'après le communiqué de presse de la SNCF, 18 avril 2018 (analyse : Les Décodeurs, Le Monde ).
🏆 Où est le problème ? · 2/10
PE – Python numérique – S5
🏆 Où est le problème ? · 2/10 · réponse
49 + 40 = 89 % : le disque est plein quand même
Les parts d'un camembert font 100 % : où sont passés les 11 % restants ?
Recréé d'après CNews, mai 2018 (sondage Viavoice pour Le Figaro ) ; angles relevés sur l'image diffusée.
🏆 Où est le problème ? · 3/10
PE – Python numérique – S5
🏆 Où est le problème ? · 3/10 · réponse
48 % dessiné comme plus de la moitié du disque
L'angle doit suivre le chiffre : 48 %, c'est moins d'un demi-disque.
Recréé d'après BFMTV, 25 avril 2018 (sondage Elabe) ; angles relevés sur l'image diffusée.
🏆 Où est le problème ? · 4/10
PE – Python numérique – S5
🏆 Où est le problème ? · 4/10 · réponse
6 % dessiné comme 15 % : une part deux fois et demie trop grosse
Vérifiez une part facile : 25 % = un quart de disque.
Recréé d'après BFMTV, 22 août 2018 (sondage Elabe) ; angles relevés sur l'image diffusée.
🏆 Où est le problème ? · 5/10
PE – Python numérique – S5
🏆 Où est le problème ? · 5/10 · réponse
+ 6 % par an pendant 5 ans, c'est + 33,8 %, pas + 30 %
Les hausses successives se multiplient : 1,06⁵ = 1,338.
Recréé d'après France 2, JT de 13 h, février 2013 (« Facture d'électricité »).
🏆 Où est le problème ? · 6/10
PE – Python numérique – S5
🏆 Où est le problème ? · 6/10 · réponse
Hollande (6,5 %) dessiné aussi haut que Mélenchon (12 %)
Mesurez : une barre deux fois plus haute doit valoir deux fois plus.
Recréé d'après franceinfo, « Tout est politique », 10 novembre 2025 (sondage Elabe) ; hauteurs relevées sur l'image diffusée. La chaîne s'est excusée.
🏆 Où est le problème ? · 7/10
PE – Python numérique – S5
🏆 Où est le problème ? · 7/10 · réponse
Le rayon suit la valeur : l'aire est × 24 pour × 4,9
Une surface grandit comme le carré : comparez des longueurs (des barres).
Cours moyen de l'action Amazon, 2015 et 2020 (Amazon.csv).
🏆 Où est le problème ? · 8/10
PE – Python numérique – S5
🏆 Où est le problème ? · 8/10 · réponse
+ 1 300 % au Panama… de 1 à 14 décès
Un pourcentage sans sa base ne dit rien : donnez aussi les nombres.
Décès Covid du 21 au 28 mars 2020 (corona-par-pays.csv).
🏆 Où est le problème ? · 9/10
PE – Python numérique – S5
🏆 Où est le problème ? · 9/10 · réponse
Les années sautent : 1998, 2008, 2016, 2018…
Un axe du temps doit être régulier : toutes les années, ou une courbe datée.
Cours moyen de l'action Amazon (Amazon.csv).
🏆 Où est le problème ? · 10/10
PE – Python numérique – S5
🏆 Où est le problème ? · 10/10 · réponse
Une moyenne sur 180 jours efface les trois vagues
Lisser, oui, mais à l'échelle du phénomène : 7 jours pour une épidémie.
Nouveaux cas en France (covid-frozen.json), moyenne glissante sur 180 jours.
PE – Python numérique – S5
un titre qui dit ce qu'on voit, c-à-d. la réponse à la question ;
des axes nommés, avec leur unité (GWh, cas par jour, $) ;
des barres qui partent de zéro , une échelle log annoncée ;
une période montrée en entier , ou un zoom justifié ;
des grandeurs comparables : par habitant, par jour, base 100 ;
un seul axe y par repère, une légende dès qu'il y a deux courbes ;
la source des données, en petit.
Et le piège de S4 vaut aussi pour les figures : avant de conclure à partir d'un écart, croisez avec la variable qui pourrait l'expliquer (Simpson !).
PE – Python numérique – S5
Les 3 pièges du jour
Une date en texte n'est pas une date. Tri alphabétique, pas de slicing, pas de .dt.
pd.to_datetime(..., format=...) dès le chargement — et %d/%m vs %m/%d se vérifie sur une ligne.
resample réduit, rolling lisse. L'un change le nombre de points, l'autre non.
Et les deux veulent une agrégation derrière (.mean(), .sum()…).
savefig avant show , sinon l'image est blanche. Et fig.savefig, pas ax.savefig.
Bonus : le slicing par dates exige un index trié — sort_index() juste après set_index().
PE – Python numérique – S5
TP du jour : Covid par pays
Données corona-par-pays.csv (sujet et fichier dans la fiche de séance ; colonnes Date, Pays, Infections, Deces…).
charger avec les bons sep= / comment=, convertir Date, mettre un index (Date, Pays) ;
extraire 4 ou 5 pays, unstack pour avoir une colonne par pays, tracer ;
passer des cumuls aux nouveaux cas par jour (diff()), lisser par rolling(7) ;
une grille subplots : cumul / quotidien lissé, mêmes échelles (sharex) ;
savefig d'une figure propre, relue avec la liste : titre, axes, unités, échelle.
À rendre : rien d'obligatoire — le PNG final, si vous en êtes fiers.
Pour les rapides : 3-03 / 3-04 figures interactives (ipympl) · fremont.csv (comptage vélos horaire :
resample("D"), dt.dayofweek) — c'est aussi l'échauffement de D5.
PE – Python numérique – S5
Devoir pour la prochaine séance
D5 — Mini-projet : une série temporelle
Pour le dimanche 18 octobre, 23 h 59 . Un comptage de vélos jour après jour sur un an
(ou une série de votre choix, validée par mail).
charger et nettoyer : dates converties avec format=, index trié, manquants traités ;
analyse 1 : tendance — resample("W").mean(), rolling(28), une figure ;
analyse 2 : saisonnalité — par mois (.dt.month) ou jour de semaine, groupby + plot.bar ;
question ouverte : vous choisissez l'angle (météo, jours fériés…), vous y répondez par une figure.
Ce qu'on rend : le notebook exécuté + les PNG, un court texte sous chaque figure.
Ce qui est noté : dates réellement typées, resample/rolling à bon escient, figures lisibles sans le code et honnêtes (la liste ).
fig: img-S5/t-resample.svg
fig: img-S5/t-rolling.svg
fig: img-S5/t-unstack.svg
fig: img-S5/t-subplots.svg
fig: img-S5/g-groupees.svg
fig: img-S5/g-empilees.svg
fig: img-S5/g-histogramme.svg
fig: img-S5/g-chaleur.svg
Une barre se lit par sa **longueur** : elle doit partir de zéro. À gauche, l'axe commence à 5 215, et la barre du vendredi paraît six fois plus courte que celle du mardi.
Une courbe, elle, peut zoomer (on y lit des variations), pourvu que l'axe soit gradué. `ax.set_ylim(0, None)` remet le zéro.
Qui choisit la période choisit la conclusion : à gauche, un krach ; à droite, un accident de parcours.
Montrez d'abord la série entière, puis zoomez en disant pourquoi : `s.plot()`, puis `s.loc["2018-09":"2018-12"].plot()`.
Le nombre total de cas en Allemagne (à gauche) ne fait que monter, même quand l'épidémie recule. Les vagues n'apparaissent qu'en traçant les **nouveaux cas** : `s.diff()`, lissés par `.rolling(7).mean()`.
C'est exactement le passage que vous ferez dans le TP Covid !
En linéaire, les États-Unis écrasent tout et le mois de mars semble calme. En log, chaque graduation multiplie par 10 : une droite signifie une croissance exponentielle, et la **pente** donne le rythme, comparable d'un pays à l'autre.
Le log convient aux croissances multiplicatives, à condition de l'annoncer : `logy=True`, et « échelle log » dans le titre ou l'axe.
Comparer des pays (ou des régions) de tailles différentes en valeur absolue, c'est surtout comparer leurs populations. Rapporté à 100 000 habitants, le classement change du tout au tout.
C'est le `merge` avec la population de D4 : on divise **après** l'avoir fait. Notez enfin que ces chiffres dépendent aussi de ce que chaque pays compte : l'Inde a très peu testé.
Mêmes données, seule l'échelle de droite change : à gauche, les deux courbes semblent liées ; à droite, plus du tout. Et même liées, deux courbes qui montent ensemble ne prouvent rien : corrélation n'est pas causalité.
Évitez `ax.twinx()` : préférez deux sous-figures (`plt.subplots(2, 1, sharex=True)`) ou une base 100 commune (`s / s.iloc[0] * 100`).
Sur le camembert, qui de l'Occitanie, du Centre-Val de Loire ou de la Nouvelle-Aquitaine produit le plus ? Sur les barres triées, la réponse est immédiate.
Le camembert ne se défend qu'avec deux ou trois parts ; la 3D et les pictogrammes dont l'aire grandit avec la valeur sont à bannir.
classe: presse-franceinfo-2025-bis