Seaborn en Python : nuage de points, histogramme, KDE, jointplot & pairplot
Explorez une vraie table comme le fait un analyste — des tracés statistiques en une ligne directement depuis un DataFrame, séparés par groupe, jusqu’à une figure digne d’être montrée.
Apprenez seaborn en Python en explorant une vraie table : nuage de points, histogramme, KDE et ECDF en une ligne directement depuis un DataFrame, séparation des groupes avec hue, ajout d’un ajustement avec regplot et lmplot, combinaison des vues avec jointplot, et balayage de toutes les paires avec pairplot.
Date de publication
17 juillet 2026
Modifié
18 juillet 2026
AstucePoints clés
Seaborn, c’est matplotlib avec un accent statistique. Il lit votre DataFrame par nom de colonne, donc sns.scatterplot(data=df, x="a", y="b", hue="g") remplace une boucle groupby — et il vous rend un Axes matplotlib, donc chaque ax.set_* que vous connaissez déjà fonctionne encore.
hue= est tout l’intérêt. Nommer une colonne de regroupement colore, sépare et légende le tracé en un seul argument. size= et style= ajoutent deux variables de plus au même panneau.
Axes-level vs figure-level est LA distinction à apprendre.scatterplot/histplot dessinent sur un seul Axes que vous contrôlez ; relplot/displot/jointplot/pairplot construisent leur propre figure — c’est pourquoi leur passer figsize=lève une erreur (dimensionnez-les avec height=/aspect=).
Trois vues d’une même variable :histplot (des comptages par bins), kdeplot (une courbe lisse), ecdfplot (chaque point, sans bins — le tracé honnête quand les groupes diffèrent en taille).
pairplot est le tout premier coup d’œil le plus rapide qui soit — chaque paire numérique en nuage de points, la densité de chaque variable sur la diagonale, colorée par groupe, en une ligne.
Fixez le style maison une seule fois avec sns.set_theme(). Les valeurs par défaut de seaborn sont reconnaissables entre toutes comme seaborn ; un set_theme + une palette épinglée font de chaque figure qui suit la vôtre.
Introduction
Vous avez la table des manchots ouverte et deux questions qu’un analyste se pose toujours en premier : comment ces mesures sont-elles distribuées, et comment se relient-elles entre elles ? Dans Les bases de matplotlib vous y répondiez à la main — une boucle groupby pour colorer un nuage de points par espèce, un .dropna() avant chaque histogramme. Seaborn fait les deux en une ligne, parce qu’il lit lui-même le DataFrame.
C’est le compromis que cette leçon rend concret. Nous explorons les mêmes manchots avec les tracés statistiques de seaborn — nuage de points, ajustement de régression, histogramme, KDE, ECDF —, les séparons par espèce avec un seul argument hue=, puis terminons par les deux figures qui méritent leur réputation : jointplot (une relation et ses distributions dans un seul cadre) et pairplot (toutes les paires d’un coup). À la fin, vous aurez une figure digne de figurer dans le rapport.
Seaborn n’est pas un remplaçant de matplotlib. Il est matplotlib en dessous — chaque fonction seaborn dessine avec matplotlib et la plupart vous rendent un Axes matplotlib. Donc tout ce qui vient de la leçon 1 — ax.set_xlabel, ax.set_title, fig.savefig — s’applique encore, et nous nous appuyons dessus plutôt que de le répéter.
Les données sont le même CSV de manchots livré à côté de cette leçon (des mesures physiques pour trois espèces), lu avec pd.read_csv — sans téléchargement et sans seaborn.load_dataset qui accède au réseau. Chaque figure ci-dessous a été produite par le code montré. Les blocs s’enchaînent dans l’ordre, comme lors d’une vraie session : chargez les données et fixez le style une fois, puis tracez. Pour expérimenter vous-même, modifiez la cellule autonome Essayez en direct à la fin et appuyez sur Run ; elle s’exécute dans votre navigateur via Pyodide.
Seaborn repose sur matplotlib
Deux faits expliquent presque toutes les surprises de seaborn.
Premièrement : les fonctions seaborn connaissent les DataFrame. Vous passez data= plus les noms de colonnes sous forme de chaînes. Seaborn gère le regroupement, les couleurs, la légende et les valeurs manquantes — la comptabilité que vous écriviez à la main dans la leçon 1.
Deuxièmement : seaborn a deux sortes de fonctions, et savoir laquelle vous appelez évite bien des confusions :
un objet grille seaborn (FacetGrid, JointGrid, PairGrid)
height= + aspect=
Les fonctions Axes-level sont des invités polis : elles dessinent là où vous leur dites, et comme elles renvoient un Axes vous gardez tous les réglages de matplotlib. Les fonctions Figure-level sont autonomes — elles construisent leur propre figure pour pouvoir ajouter des facettes, des panneaux marginaux ou une légende hors du tracé. Cette puissance est exactement pourquoi elles rejettentfigsize= — ce n’est pas à vous de le fixer sur une figure qui leur appartient, alors vous passez height= et aspect= à la place. Recourez à l’attribut .figure de la grille quand vous devez redescendre à matplotlib.
Fixez le style visuel maison, une seule fois
Le rendu par défaut de seaborn — une palette bleu profond vers orange sur une grille grise — est reconnaissable comme celui de seaborn, pas le vôtre. sns.set_theme() fixe le style globalement, et il accepte un dictionnaire rc= pour superposer vos propres surcharges matplotlib par-dessus. Fixez-le une fois et chaque figure ci-dessous en hérite.
Nous gardons le même langage visuel que la leçon 1 : azur #3a86d4 pour une seule série, et un échantillon viridis adapté au daltonisme épinglé par espèce pour qu’une espèce ait la même couleur dans chaque figure.
import matplotlib.pyplot as pltimport numpy as npimport pandas as pdimport seaborn as sns# House style — one call, applied to every figure that followssns.set_theme( style="whitegrid", # white panel + a light reference grid context="notebook", # sensible label/marker scale for a report rc={"figure.figsize": (7, 4.5),"figure.dpi": 110,"axes.spines.top": False, # drop the top/right box edges"axes.spines.right": False,"grid.color": "#e6e6e6","grid.linewidth": 0.8,"axes.titlesize": 13,"axes.titleweight": "bold","legend.frameon": False, },)AZURE ="#3a86d4"# brand colour for a single series# Load the data once; later blocks reuse itpenguins = pd.read_csv("penguins.csv")# A colourblind-safe colour per species, sampled from viridis — the SAME three# colours lesson 1 used, pinned so a species keeps its colour across the seriesSPECIES = ["Adelie", "Chinstrap", "Gentoo"]PALETTE =dict(zip(SPECIES, plt.get_cmap("viridis")(np.linspace(0.1, 0.85, 3))))sns.set_palette([PALETTE[s] for s in SPECIES]) # the default cycle, tooprint(penguins.shape, "rows x cols")print(penguins["species"].value_counts().to_dict())
Trois choix délibérés. style="whitegrid" donne un panneau blanc avec une grille légère ; les surcharges rc= retirent ensuite les bordures du haut et de droite et adoucissent la grille en #e6e6e6 — le même rendu que la leçon 1 construisait avec rcParams, parce que set_themeest une mise à jour de rcParams avec de meilleures valeurs par défaut. context="notebook" met les polices et les marqueurs à l’échelle d’un rapport ("talk" ou "poster" les agrandissent pour des diapositives).
La ligne de palette est celle de la leçon 1, inchangée — et c’est là tout le propos : seaborn prend des couleurs matplotlib ordinaires, il n’y a donc pas de dialecte de couleurs propre à seaborn à apprendre. Épingler PALETTE comme un dictionnaire plutôt que de se fier au cycle de couleurs signifie qu’Adelie est le même violet ici que dans la leçon 1, que Chinstrap soit ou non présent dans le tracé. (Le propre sns.color_palette("viridis", 3) de seaborn échantillonnerait des teintes légèrement différentes — très bien en soi, mais il recolorerait discrètement les espèces entre deux leçons portant sur les mêmes manchots.)
Relations : un nuage de points en une ligne
Commencez là où est la question. Un manchot plus lourd a-t-il des nageoires plus longues, et cela dépend-il de l’espèce ? Dans la leçon 1, cela demandait une boucle groupby ; ici, c’est un seul appel avec hue="species".
import matplotlib.pyplot as pltimport seaborn as snsfig, ax = plt.subplots()sns.scatterplot( data=penguins, x="flipper_length_mm", y="body_mass_g", hue="species", palette=PALETTE, s=45, alpha=0.8, ax=ax,)ax.set_xlabel("Flipper length (mm)")ax.set_ylabel("Body mass (g)")ax.set_title("Body mass rises with flipper length")ax.legend(title="Species")fig.tight_layout()plt.show()
Figure 1
Un seul argument — hue="species" — a fait le regroupement, la coloration et la légende. Et la figure paie immédiatement : la masse grimpe avec la longueur des nageoires (Pearson global r = 0.87), mais le nuage est en réalité trois regroupements. Adelie et Chinstrap se superposent en bas à gauche (nageoires ~190 et ~196 mm, masse ~3700 g pour les deux), tandis que Gentoo se sépare nettement en haut à droite (~217 mm, ~5076 g). C’est l’espèce, et non un continuum lisse, qui structure ces données.
Remarquez ce que vous n’avez pas écrit : aucun .dropna(). Seaborn écarte silencieusement les valeurs manquantes tracé par tracé — les deux manchots sans masse enregistrée ne sont tout simplement pas dessinés. Pratique, et bon à retenir quand un décompte paraît faible.
Parce que scatterplot est axes-level, il a accepté notre ax= et l’a renvoyé, donc les appels ax.set_* et fig.tight_layout() de la leçon 1 fonctionnent inchangés. C’est le motif de chaque fonction axes-level sur cette page.
Plus de variables : hue, size et style
Un nuage de points a deux positions à dépenser, mais seaborn vous donne trois canaux de plus. hue= associe une colonne à la couleur, size= à l’aire du marqueur, style= à la forme du marqueur. Ici : l’espèce par la couleur, la masse corporelle par la taille, le sexe par la forme — cinq variables sur un seul panneau.
import matplotlib.pyplot as pltimport seaborn as snsfig, ax = plt.subplots(figsize=(7.5, 5))sns.scatterplot( data=penguins, x="bill_length_mm", y="bill_depth_mm", hue="species", palette=PALETTE, size="body_mass_g", sizes=(15, 160), style="sex", alpha=0.8, ax=ax,)ax.set_xlabel("Bill length (mm)")ax.set_ylabel("Bill depth (mm)")ax.set_title("Bill dimensions by species, mass and sex")# Seaborn labels each legend section with the raw column name — rename themhandles, labels = ax.get_legend_handles_labels()pretty = {"species": "Species", "body_mass_g": "Body mass (g)", "sex": "Sex"}ax.legend(handles, [pretty.get(l, l) for l in labels], bbox_to_anchor=(1.02, 1), loc="upper left", fontsize=8)fig.tight_layout()plt.show()
Figure 2
Les trois espèces se répartissent en territoires distincts — Adelie court-et-profond (38.8 × 18.3 mm), Chinstrap long-et-profond (48.8 × 18.4 mm), Gentoo long-et-peu-profond (47.5 × 15.0 mm). Regardez au sein d’un même regroupement et les cercles (mâles) sont visiblement plus gros que les croix (femelles) : les mâles sont plus lourds dans les trois espèces, d’environ 400 à 800 g. C’est un vrai résultat que les canaux size et style ont fait apparaître gratuitement.
La retenue compte ici. Cinq variables est proche du plafond de ce qu’un lecteur peut décoder, et la légende est maintenant plus haute que le tracé — c’est pourquoi nous l’avons fait flotter à l’extérieur avec bbox_to_anchor. sizes=(15, 160) fixe la plage d’aire des marqueurs ; sans elle, seaborn choisit une plage souvent trop étroite pour être lisible. Et notez la correction de légende : seaborn intitule chaque section avec le nom brut de la colonne (body_mass_g), donc réétiqueter les poignées fait la différence entre un brouillon et une figure que vous publieriez.
relplot : le même tracé, en facettes
relplot est la version figure-level de scatterplot (et de lineplot, via kind="line"). Mêmes arguments, un ajout qui le justifie : col= et row= répartissent les données en panneaux. C’est le gain d’avoir laissé seaborn posséder la figure.
import seaborn as snsg = sns.relplot( data=penguins, x="flipper_length_mm", y="body_mass_g", hue="species", palette=PALETTE, col="island", col_order=["Biscoe", "Dream", "Torgersen"], # pin the panel order kind="scatter", height=3.6, aspect=0.95, s=40, alpha=0.8,)g.set_axis_labels("Flipper length (mm)", "Body mass (g)")g.set_titles("{col_name}")g.legend.set_title("Species")g.figure.suptitle("Mass vs. flipper length, by island", y=1.03, fontweight="bold")plt.show()
Figure 3
Le facettage par île expose quelque chose qu’aucun panneau unique ne montrerait : les espèces ne sont pas du tout réparties entre les îles. Les Gentoo ne vivent que sur Biscoe, les Chinstrap seulement sur Dream, et seuls les Adelie se trouvent sur les trois. Donc « les manchots de Biscoe sont plus lourds » n’est pas un effet d’île — ce sont les Gentoo. Séparer par une variable, c’est ainsi qu’on repère un facteur de confusion comme celui-là avant qu’il n’atteigne vos conclusions.
Quatre habitudes relplot à copier. height= et aspect= dimensionnent les panneaux (pasfigsize= — c’est une fonction figure-level ; la largeur par panneau vaut height × aspect). col_order= fixe la séquence des panneaux ; omettez-le et vous obtenez l’ordre dans lequel les valeurs apparaissent dans le fichier, qui change si les données changent. g.set_axis_labels(...) étiquette tous les panneaux d’un coup, et g.set_titles("{col_name}") remplace le verbeux island = Biscoe par défaut de seaborn. Quand vous avez besoin de matplotlib lui-même, g.figure est la Figure — c’est ainsi que suptitle est arrivé là, et c’est ce que vous passeriez à fig.savefig de la leçon 1.
Note
Le facettage aura sa propre leçon plus loin dans cette série, aux côtés des heatmaps et des palettes. Ici, c’est simplement la raison d’être des fonctions figure-level.
Ajouter un ajustement : regplot et lmplot
« La masse augmente avec la longueur des nageoires » est une affirmation à l’œil nu. regplot dessine la droite des moindres carrés et sa bande de confiance à 95 %, pour que la tendance et son incertitude soient sur la figure.
import matplotlib.pyplot as pltimport seaborn as snsfig, ax = plt.subplots()sns.regplot( data=penguins, x="flipper_length_mm", y="body_mass_g", color=AZURE, scatter_kws={"s": 25, "alpha": 0.5}, seed=42, # fix the CI bootstrap so the band is reproducible ax=ax,)ax.set_xlabel("Flipper length (mm)")ax.set_ylabel("Body mass (g)")ax.set_title("One line for all penguins — 49.7 g per mm")fig.tight_layout()plt.show()
Figure 4
L’ajustement est raide et la bande étroite : regroupé sur l’ensemble des manchots, chaque millimètre de nageoire supplémentaire achète environ 49.7 g de masse corporelle. scatter_kws transmet des options au nuage de points sous-jacent (il existe un line_kws correspondant pour la droite) — une convention seaborn que vous rencontrerez souvent.
Mais nous savons déjà que la vue regroupée cache trois espèces. lmplot est le pendant figure-level de regplot, et il accepte hue= — un ajustement par groupe :
import seaborn as snsg = sns.lmplot( data=penguins, x="flipper_length_mm", y="body_mass_g", hue="species", palette=PALETTE, height=4.5, aspect=1.5, scatter_kws={"s": 25, "alpha": 0.6}, seed=42, # reproducible CI bands)g.set_axis_labels("Flipper length (mm)", "Body mass (g)")g.legend.set_title("Species")g.figure.suptitle("One fit per species — Gentoo gain mass fastest", y=1.02, fontweight="bold")plt.show()
Figure 5
Trois ajustements, trois pentes différentes : Adelie +32.8 g/mm, Chinstrap +34.6 g/mm, Gentoo +54.6 g/mm. La droite des Gentoo est nettement plus raide — ils n’ont pas seulement de plus grandes nageoires, ils prennent de la masse plus vite par millimètre de celles-ci. Et le 49.7 g/mm regroupé de la figure précédente apparaît maintenant pour ce qu’il est : un nombre qui ne décrit aucune espèce réelle, gonflé par l’écart entre le regroupement Gentoo et les deux autres. La leçon 1 a rencontré ce piège sur les mesures du bec, où la pente regroupée a même changé de signe — le paradoxe de Simpson. Même leçon, forme plus douce : ajustez au sein des groupes que vous avez.
Lequel des deux ?regplot quand vous voulez un ajustement sur un Axes que vous construisez déjà (il se compose avec d’autres couches) ; lmplot quand vous voulez des séparations hue=/col= et acceptez de laisser seaborn posséder la figure.
Distributions : la forme d’une variable
Les relations réglées, passez aux marginales. Seaborn offre trois vues axes-level d’une variable — et elles répondent à des questions subtilement différentes.
histplot : des comptages par bins
L’histogramme nu de la leçon 1 montrait que la masse corporelle des manchots est bimodale. histplot ajoute l’argument qui explique pourquoi :
import matplotlib.pyplot as pltimport seaborn as snsfig, ax = plt.subplots()sns.histplot( data=penguins, x="body_mass_g", hue="species", palette=PALETTE, bins=25, multiple="stack", edgecolor="white", ax=ax,)ax.set_xlabel("Body mass (g)")ax.set_ylabel("Number of penguins")ax.set_title("Body mass is bimodal — because of the species")ax.get_legend().set_title("Species")fig.tight_layout()plt.show()
Figure 6
Voilà la réponse. Les deux bosses trouvées par la leçon 1 sont les espèces : Adelie et Chinstrap s’empilent dans le mode inférieur (toutes deux avec une moyenne ~3700 g) et Gentoo forment le supérieur (~5076 g). Une moyenne unique de 4202 g ne décrit presque aucun manchot de la table.
Soyez précis sur ce que « séparé » veut dire, cependant — les modes sont distincts, mais les distributions ne sont pas disjointes. Elles se chevauchent sur une vraie bande 3950–4800 g : 34 % des Gentoo ne pèsent pas plus que le plus lourd des Adelie ou Chinstrap (4800 g), et 30 % des Adelie et Chinstrap sont au moins aussi lourds que le plus léger des Gentoo (3950 g). La masse corporelle seule ne vous dirait pas de quelle espèce est un manchot.
multiple= contrôle comment les groupes se partagent l’espace et le choix change le message : "stack" (ici) lit le total honnêtement ; "dodge" place les barres côte à côte ; "layer" les superpose en transparence — bien pour comparer des formes, trompeur pour lire des totaux. Ajoutez kde=True pour superposer une courbe lisse sur les barres.
kdeplot : une courbe lisse, sans bins
La forme d’un histogramme dépend de l’endroit où tombent les bins. kdeplot estime plutôt une densité lisse — meilleur pour comparer les formes des groupes, car des courbes qui se chevauchent restent lisibles là où des barres qui se chevauchent ne le sont plus.
import matplotlib.pyplot as pltimport seaborn as snsfig, ax = plt.subplots()sns.kdeplot( data=penguins, x="body_mass_g", hue="species", palette=PALETTE, fill=True, alpha=0.4, linewidth=1.5, common_norm=False, # each curve integrates to 1 — compare SHAPES ax=ax,)ax.set_xlabel("Body mass (g)")ax.set_ylabel("Density")ax.set_title("Adelie and Chinstrap are the same weight; Gentoo aren't")ax.get_legend().set_title("Species")fig.tight_layout()plt.show()
Figure 7
Maintenant la comparaison est sans équivoque : les courbes Adelie et Chinstrap sont centrées au même endroit (moyennes 3701 g et 3733 g — une différence de 32 g sur une dispersion de ~450 g), tandis que Gentoo se situe environ 1.4 kg à droite. Trois jeux de barres se seraient disputé les mêmes pixels ; trois courbes non.
Lisez les hauteurs avec soin, cependant — elles concernent la dispersion, pas le poids. Chinstrap culmine plus haut qu’Adelie uniquement parce que la masse des Chinstrap est plus resserrée (sd 384 g contre 459 g), et une courbe d’aire unitaire comprimée dans une plage plus étroite doit monter plus haut. Même centre, même histoire : ces deux espèces pèsent la même chose.
common_norm=False fait un vrai travail ici, et c’est le réglage par défaut de seaborn qu’il vous faut le plus connaître. Laissé à sa valeur par défaut True, seaborn normalise tous les groupes ensemble, donc l’aire de chaque courbe — et par conséquent sa hauteur — est mise à l’échelle par la part de ce groupe dans les données. Avec 151 Adelie contre 68 Chinstrap, la courbe Adelie dominerait celle des Chinstrap et vous liriez une différence de taille d’échantillon comme une différence de poids. common_norm=False donne à chaque groupe sa propre aire unitaire, ce que vous voulez dès l’instant où la question est « ces groupes ont-ils la même forme ? »
L’autre compromis est honnête : une KDE lisse, elle invente donc un peu de densité là où vous n’avez aucune donnée — remarquez les courbes qui dérivent sous le manchot le plus léger (2700 g). Réglez-la avec bw_adjust= (<1 suit les données de plus près, >1 lisse davantage) et cut=0 pour empêcher la courbe de s’étendre au-delà de la plage observée.
ecdfplot : chaque point, aucun choix
Les bins et la largeur de bande sont deux réglages que vous choisissez — et les choisir mal change l’histoire. ecdfplot n’a ni l’un ni l’autre : il trace chaque observation comme une marche, c’est donc le même tracé à chaque fois.
import matplotlib.pyplot as pltimport seaborn as snsfig, ax = plt.subplots()sns.ecdfplot( data=penguins, x="body_mass_g", hue="species", palette=PALETTE, linewidth=2, ax=ax,)ax.axhline(0.5, color="grey", linestyle="--", linewidth=1) # the median lineax.set_xlabel("Body mass (g)")ax.set_ylabel("Proportion of penguins at or below")ax.set_title("Cumulative body mass by species")ax.get_legend().set_title("Species")fig.tight_layout()plt.show()
Figure 8
Lisez-le en choisissant une valeur et en regardant vers le haut. À 4000 g : environ 77 % des Adelie et 78 % des Chinstrap sont à ce niveau ou en dessous — et seulement 1 % des Gentoo. Lisez à l’horizontale à 0.5 (la ligne pointillée) pour les médianes : 3700 g, 3700 g, 5000 g. C’est un énoncé précis, non lissé et sans bins de la séparation, et c’est pourquoi une ECDF est le bon choix quand vos groupes ont des tailles très différentes (151 Adelie contre 68 Chinstrap ici) — un histogramme de comptages ferait juste paraître Chinstrap petit.
displot : la version figure-level
displot est à histplot ce que relplot est à scatterplot : mêmes tracés (kind="hist", "kde" ou "ecdf"), plus le facettage col=/row=.
import seaborn as snsg = sns.displot( data=penguins, x="body_mass_g", col="species", hue="species", palette=PALETTE, kind="hist", kde=True, bins=15, legend=False, height=3.2, aspect=0.95, edgecolor="white",)g.set_axis_labels("Body mass (g)", "Count")g.set_titles("{col_name}")g.figure.suptitle("Body mass distribution per species", y=1.04, fontweight="bold")plt.show()
Figure 9
Des panneaux séparés, des axes partagés — c’est cette échelle x partagée qui rend le décalage Gentoo mesurable plutôt que simplement visible. Chaque espèce est unimodale prise à part : la bimodalité n’a jamais existé que dans les données regroupées. kde=True superpose la courbe lisse sur les barres, vous donnant les deux lectures à la fois — et ici les courbes justifient leur présence, en révélant un léger épaulement à droite chez les Gentoo que les barres seules n’annoncent pas.
Parce qu’il s’agit de comptages sur une échelle partagée, les panneaux montrent aussi honnêtement les tailles de groupes inégales — le panneau Chinstrap est réellement plus court parce qu’il n’y en a que 68. C’est le choix inverse de la KDE ci-dessus, et c’est le bon quand vous voulez que le lecteur voie combien de données chaque groupe apporte.
jointplot : la relation et ses distributions
Tout jusqu’ici a été soit une relation, soit une distribution. jointplot met les deux dans un seul cadre : le tracé bivarié au centre, la marginale de chaque variable le long de son propre bord.
import seaborn as snsg = sns.jointplot( data=penguins, x="flipper_length_mm", y="body_mass_g", hue="species", palette=PALETTE, kind="scatter", height=6, s=40, alpha=0.75, marginal_kws={"multiple": "stack", "linewidth": 0.5},)g.set_axis_labels("Flipper length (mm)", "Body mass (g)")g.ax_joint.legend(title="Species")g.figure.suptitle("Flipper length vs. body mass, with marginals", y=1.02, fontweight="bold")plt.show()
Figure 10
Le centre est le nuage de points que vous connaissez déjà ; les bords sont le gain. Les deux marginales sont bimodales — et les bosses du haut et de droite s’alignent sur le même regroupement en haut à droite au centre, si bien que vous pouvez voir qu’un seul groupe entraîne à la fois le mode des nageoires et le mode de la masse. C’est une affirmation qui a demandé trois figures séparées plus haut ; ici, c’en est une seule.
Regardez de près ces bords, cependant, car seaborn a discrètement changé de type de tracé. Un jointplot en kind="scatter" dessine des marginales en histogramme — sauf si vous passez hue=, auquel cas il les bascule en KDE remplies. Ce sont donc des courbes de densité empilées, pas des barres, et marginal_kws est allé à kdeplot. C’est le genre de valeur par défaut à l’air non documenté qui rend seaborn surprenant : vérifiez toujours ce qui a réellement été dessiné plutôt que ce que vous avez supposé.
kind= change le panneau central : "scatter" (par défaut), "kde" pour des contours, "hex" pour un hexbin quand les points se superposent, "reg" pour un ajustement avec marginales. C’est figure-level, donc height= le dimensionne et g.figure vous ramène à matplotlib — et la légende réside sur l’Axes central, g.ax_joint.
pairplot : toutes les paires d’un coup
Quand vous découvrez une table, la réponse honnête à « quelles deux colonnes devrais-je tracer ? » est vous ne le savez pas encore. pairplot y répond en les traçant toutes : chaque paire numérique en nuage de points hors de la diagonale, et la distribution de chaque variable — séparée par groupe, si vous passez hue= — le long de celle-ci.
import seaborn as sns# Rename first, so the grid is labelled in plain English rather than column namesLABELS = {"bill_length_mm": "Bill length (mm)","bill_depth_mm": "Bill depth (mm)","flipper_length_mm": "Flipper length (mm)","body_mass_g": "Body mass (g)",}g = sns.pairplot( penguins.rename(columns=LABELS),vars=list(LABELS.values()), hue="species", palette=PALETTE, diag_kind="kde", height=1.9, plot_kws={"s": 18, "alpha": 0.7},)g.legend.set_title("Species")g.figure.suptitle("Penguin measurements — every pair at once", y=1.02, fontsize=14, fontweight="bold")plt.show()
Figure 11
Une ligne, et toute la table est sur la page.
Lisez d’abord la diagonale — mais lisez-la pour ce qu’elle montre réellement. Parce que nous avons passé hue=, chaque panneau diagonal contient une courbe de densité par espèce, non la distribution regroupée : il vous dit à quel point cette seule mesure sépare les groupes. La profondeur du bec (deuxième en descendant) détache nettement les Gentoo des deux autres ; la longueur des nageoires (troisième) fait de même ; mais aucune mesure isolée ne sépare les trois espèces — Adelie et Chinstrap se superposent partout sauf sur la longueur du bec. C’est là l’argument pour regarder les paires.
Alors parcourez les hors-diagonales pour ce qu’une seule variable ne peut pas faire seule. Longueur du bec × profondeur du bec est celle qui ressort : trois nuages quasi disjoints, les trois espèces écartées. Longueur des nageoires × masse corporelle, en revanche, fusionne Adelie et Chinstrap en une seule tache. Si vous construisiez un classifieur, cette comparaison vient de vous dire que les deux mesures du bec sont vos meilleures variables explicatives — et il aura suffi d’un seul appel pour le découvrir.
Deux arguments rendent cela pratique. vars= restreint la grille aux colonnes qui vous intéressent (omettez-le et seaborn trace chaque colonne numérique — y compris les colonnes d’identifiant, ce qui transforme un pairplot en un illisible 12×12). diag_kind="kde" place des densités lisses sur la diagonale au lieu des histogrammes par défaut ; corner=True supprime le triangle supérieur redondant quand la grille devient encombrée. Renommer les colonnes d’abord est la façon la moins coûteuse d’obtenir une grille de qualité publication — seaborn étiquette les axes avec le nom qu’a la colonne, quel qu’il soit.
C’est la figure digne d’être montrée. C’est aussi celle à exécuter en premier, avant même d’avoir décidé quelle est la question.
Quel tracé pour quelle question ?
La leçon 1 choisissait un tracé à partir de la question ; seaborn ajoute la dimension des groupes. Lisez ceci comme : qu’est-ce que je demande, et à propos de combien de groupes ?
Votre question
Le tracé
L’appel
Comment deux variables numériques se relient-elles, séparées par groupe ?
Nuage de points
sns.scatterplot(data=df, x=, y=, hue=)
…et dans des panneaux séparés par groupe ?
Nuage de points en facettes
sns.relplot(..., col=)
Y a-t-il une tendance, et quelle en est la certitude ?
Ajustement de régression + IC
sns.regplot(...) / sns.lmplot(..., hue=)
Quelle est la forme d’une variable ?
Histogramme
sns.histplot(data=df, x=, hue=, multiple="stack")
Comment les formes des groupes se comparent-elles ?
KDE
sns.kdeplot(..., hue=, fill=True)
Quelle proportion est en dessous d’une valeur (groupes de tailles différentes) ?
ECDF
sns.ecdfplot(..., hue=)
Une relation et les deux distributions ?
Joint plot
sns.jointplot(data=df, x=, y=, hue=)
Je ne sais pas encore quoi tracer
Pair plot
sns.pairplot(df, vars=, hue=)
En cas de doute : commencez par pairplot pour trouver la structure, puis scatterplot ou lmplot pour cerner la relation, et histplot/kdeplot/ecdfplot pour décrire les variables qui ont fini par vous importer.
Les mêmes tracés en R
Vous travaillez en R, ou dans les deux langages ? Les équivalents en grammaire des graphiques se trouvent dans la série ggplot2 et les concepts se transposent un pour un — hue= est aes(colour = species), pairplot est une matrice de nuages de points :
Les blocs ci-dessus ont été exécutés au moment de la compilation. Modifiez celui-ci et appuyez sur Run pour explorer les manchots vous-même — il s’exécute dans votre navigateur via Pyodide (aucune installation). Le premier Run télécharge seaborn et ses dépendances de la pile scientifique une fois (pandas, matplotlib et SciPy — quelques dizaines de Mo), puis c’est mis en cache et instantané.
🟢 Avec un agent IA
Apportez votre propre table — collez un df.head() et demandez à Prova« quel tracé seaborn répond à ma question, et devrait-il être axes-level ou figure-level ? » Elle choisit entre scatterplot, histplot et pairplot à partir de ce que vous demandez réellement, écrit l’appel hue=/col=, et vous dit s’il faut le dimensionner avec figsize ou height. Parce que le runtime est juste là, vous l’exécutez et voyez la figure réelle — puis changez les bins, la palette, la facette — au lieu de faire confiance à un extrait à l’air plausible. The runtime is the judge. Demander à Prova →
Problèmes fréquents
figsize= déclenche une erreur cryptique sur relplot, lmplot, jointplot ou pairplot. Ce sont des fonctions figure-level : elles construisent leur propre figure, donc figsize= n’est pas un paramètre qu’elles acceptent — et l’erreur ne le dit presque jamais. relplot/jointplot/displot la font remonter sous la forme AttributeError: PathCollection.set() got an unexpected keyword argument 'figsize' ; lmplot/pairplot sous la forme TypeError: ... got an unexpected keyword argument 'figsize'. Ni l’une ni l’autre ne désigne figsize comme le coupable, alors cela ressemble à un bug dans vos données. Dimensionnez-les plutôt avec height= (par panneau, en pouces) et aspect= (largeur ÷ hauteur) — relplot(..., height=4, aspect=1.5) donne des panneaux de 6×4 pouces. L’habitude figsize= ne fonctionne qu’avec les fonctions axes-level, où vous le fixez sur votre propre plt.subplots(figsize=...).
sns.relplot(..., ax=ax) ignore votre Axes — et se contente d’un avertissement. Il ne lève pas d’erreur, ce qui est pire : seaborn affiche UserWarning: relplot is a figure-level function and does not accept the 'ax' parameter, puis dessine dans une toute nouvelle figure à lui. Votre Axes soigneusement construit reste vide, une figure parasite apparaît, et rien n’arrête le script. Si vous avez besoin que le tracé se pose sur un Axes que vous contrôlez — dans une grille plt.subplots(), par exemple — utilisez le jumeau axes-level : scatterplot au lieu de relplot, histplot au lieu de displot, regplot au lieu de lmplot. Règle générale : ax= signifie axes-level ; col=/row= signifie figure-level. Vous ne pouvez pas avoir les deux.
Votre ax.set_title() ou ax.set_xlabel() disparaît silencieusement. Vous l’avez appelé sur le mauvais objet. Après un appel figure-level, g est une grille, pas un Axes — utilisez g.set_axis_labels(x, y), g.set_titles(...) et g.figure.suptitle(...). (g.axes vous donne le tableau d’Axes sous-jacent si vous avez vraiment besoin d’un panneau.)
Le tracé a perdu des lignes et vous ne l’avez pas remarqué. Seaborn écarte silencieusement les valeurs manquantes — et matplotlib aussi, qui saute discrètement les points NaN au lieu de lever une erreur — donc ni l’un ni l’autre ne vous avertit quand une séparation hue trace discrètement 333 manchots sur 344. Vérifiez avec df[["x_col", "y_col"]].isna().sum() avant de faire confiance à un décompte ou à un ajustement.
Vos groupes kdeplot ont des hauteurs radicalement différentes. C’est common_norm=True, la valeur par défaut : seaborn normalise tous les groupes ensemble, donc la hauteur de chaque courbe reflète sa part des données, pas sa forme. Un groupe qui a deux fois plus de lignes obtient à peu près deux fois la courbe. Si vous comparez des formes, passez common_norm=False et chaque groupe obtient sa propre aire unitaire ; si vous voulez vraiment la taille des groupes dans l’image, gardez la valeur par défaut — ou utilisez des comptages (histplot) et dites-le.
Questions fréquentes
NoteSeaborn vs matplotlib — lequel devrais-je utiliser ?
Les deux, ensemble. Seaborn est construit sur matplotlib, ce n’est donc pas un remplaçant : seaborn vous donne des tracés statistiques qui lisent un DataFrame par nom de colonne et séparent les groupes avec un seul argument hue=, tandis que matplotlib est la couche en dessous qui contrôle chaque détail. Le workflow normal est seaborn pour le tracé, matplotlib pour la finition — parce que les fonctions axes-level comme scatterplot renvoient un Axes matplotlib, ax.set_title(...) et fig.savefig(...) fonctionnent exactement comme dans Les bases de matplotlib. Apprenez d’abord le modèle Figure/Axes ; seaborn prend alors tout son sens comme un raccourci par-dessus lui.
NoteQuelle est la différence entre figure-level et axes-level dans seaborn ?
Les fonctions axes-level (scatterplot, histplot, kdeplot, ecdfplot, regplot, lineplot) dessinent sur un Axes existant — passez ax= — et renvoient cet Axes, donc elles se composent avec matplotlib et avec les grilles de sous-graphiques. Les fonctions figure-level (relplot, displot, lmplot, jointplot, pairplot) créent leur propre figure et renvoient un objet grille seaborn (FacetGrid, JointGrid, PairGrid) ; c’est ce qui leur permet de faire des facettes avec col=/row= et de placer la légende à l’extérieur. Test pratique : si vous avez besoin de ax=, utilisez axes-level ; si vous avez besoin de col=, utilisez figure-level.
NoteComment changer la taille de la figure dans seaborn ?
Cela dépend du type de fonction que vous avez appelé. Axes-level : créez la figure vous-même et passez l’Axes — fig, ax = plt.subplots(figsize=(8, 5)) puis sns.histplot(..., ax=ax). Figure-level :figsize= n’est pas accepté — le passer lève une erreur — alors utilisez plutôt height= (hauteur par panneau en pouces) et aspect= (largeur ÷ hauteur), par exemple sns.relplot(..., height=4, aspect=1.5) pour des panneaux de 6×4 pouces. Pour changer la valeur par défaut partout, fixez-la une fois dans le thème : sns.set_theme(rc={"figure.figsize": (8, 5)}).
NoteComment changer les couleurs ou la palette dans seaborn ?
Par tracé, passez palette= — une palette nommée (palette="viridis"), une liste de couleurs, ou un dictionnaire associant chaque valeur de hue à une couleur (palette={"Adelie": "#440154", ...}), qui est l’option fiable car elle épingle un groupe à une couleur, que les autres groupes soient présents ou non. Pour un tracé à une seule couleur, utilisez plutôt color="#3a86d4". Pour définir la valeur par défaut de chaque figure, appelez sns.set_palette([...]) une fois, ou sns.set_theme(palette="viridis"). Préférez une carte perceptuellement uniforme et adaptée au daltonisme comme viridis au cycle par défaut.
NoteQue fait hue dans seaborn ?
hue="column" associe une colonne à la couleur : seaborn sépare les données selon les valeurs de cette colonne, dessine chaque groupe dans sa propre couleur, et construit la légende — remplaçant la boucle groupby que vous écririez dans matplotlib. Il fonctionne sur presque toutes les fonctions seaborn, et son comportement s’adapte au tracé : sur scatterplot il colore les points, sur histplot il sépare les barres (contrôlez l’empilement avec multiple="stack"/"dodge"/"layer"), sur lmplot il ajuste une régression distincte par groupe. size= et style= associent d’autres colonnes à l’aire et à la forme du marqueur.
Testez vos connaissances
ImportantÀ vous — explorez les manchots
Utilisez les données penguins (penguins = pd.read_csv("penguins.csv")) dans la cellule Essayez en direct ci-dessus.
Tâche 1. Tracez un kdeplot de flipper_length_mm, séparé par species, rempli et semi-transparent. Étiquetez les axes et donnez-lui un titre.
AstuceIndice
sns.kdeplot est axes-level, alors construisez d’abord fig, ax = plt.subplots() et passez ax=ax. L’argument de regroupement est hue= ; le remplissage est fill=True plus alpha=.
AstuceSolution
import matplotlib.pyplot as pltimport pandas as pdimport seaborn as snspenguins = pd.read_csv("penguins.csv")sns.set_theme(style="whitegrid")fig, ax = plt.subplots(figsize=(7, 4.5))sns.kdeplot(data=penguins, x="flipper_length_mm", hue="species", palette="viridis", fill=True, alpha=0.4, ax=ax)ax.set_xlabel("Flipper length (mm)")ax.set_ylabel("Density")ax.set_title("Flipper length by species")fig.tight_layout()plt.show()
La longueur des nageoires sépare mieux les espèces que la masse corporelle : Adelie (~190 mm) et Chinstrap (~196 mm) se chevauchent encore, mais Gentoo (~217 mm) se tient presque entièrement à l’écart des deux.
Tâche 2. Tracez un jointplot de bill_length_mm contre bill_depth_mm, coloré par species, à height=5. Pourquoi ne pouvez-vous pas passer figsize ici ?
jointplot est figure-level — il construit sa propre figure (panneau central plus deux marginales), donc il prend height= plutôt que figsize=. Les marginales montrent pourquoi ces deux colonnes séparent si bien les espèces : les deux sont bimodales, et chaque espèce occupe son propre coin.
Vérification rapide. Vous voulez un nuage de points de deux colonnes dans le panneau en bas à droite d’une grille plt.subplots(2, 2). Appelez-vous sns.relplot ou sns.scatterplot ?
AstuceAfficher la réponse
sns.scatterplot(..., ax=axes[1, 1]). C’est axes-level, donc il dessine sur un Axes que vous avez déjà créé. relplot est figure-level — il créerait sa propre figure séparée et n’a aucun paramètre ax=.
Conclusion
Vous avez exploré une vraie table comme le fait un analyste : un scatterplot en une ligne avec hue= pour trouver la structure, des facettes relplot pour repérer le facteur de confusion des îles, lmplot pour ajuster au sein des groupes plutôt qu’à travers eux, histplot/kdeplot/ecdfplot pour décrire les variables de trois façons, et jointplot et pairplot pour tout mettre dans un seul cadre. Les résultats se tenaient — trois regroupements d’espèces, portés par les becs, avec Gentoo comme la lourde valeur aberrante qui prend de la masse le plus vite.
Deux choses se transposent à vos propres données. hue= est l’argument qui paie : nommez la colonne de regroupement et seaborn fait la séparation, la coloration et la mise en légende pour lesquelles vous boucleriez autrement. Et axes-level vs figure-level est la distinction qui transforme les surprises de seaborn en prédictions — ax= et figsize= d’un côté, col= et height= de l’autre. Fixez le thème une fois, épinglez votre palette, et tout le reste consiste à choisir le bon tracé pour la question.
Prouvez que vous savez le faire. Maîtrisez toute la série Visualisation de données en Python — suivez votre parcours, construisez des projets et obtenez un certificat.