Dérivez et nettoyez les colonnes que vous analysez vraiment — ajoutez-les avec assign, réassociez avec map, construisez des colonnes conditionnelles avec np.where, nettoyez le texte avec .str, et convertissez en category — et apprenez quand préférer une expression vectorisée à apply.
Dérivez et nettoyez des colonnes pandas comme l’analyse se fait vraiment : ajoutez des colonnes calculées avec assign, réassociez des valeurs avec map et replace, construisez des colonnes conditionnelles avec np.where et np.select vectorisés au lieu d’un apply lent, nettoyez le texte en masse avec l’accesseur .str, et donnez à une colonne à faible cardinalité le dtype category pour un gros gain de mémoire et un tri ordonné correct — plus quand recourir à apply (en dernier).
Date de publication
17 juillet 2026
Modifié
18 juillet 2026
AstucePoints clés
assign ajoute des colonnes calculées sans modifier en place.df.assign(mass_kg=df["body_mass_g"]/1000) renvoie une nouvelle frame, ce qui s’enchaîne proprement dans un pipeline — et sa forme lambda (lambda d: d["x"]/d["y"]) peut référencer des colonnes créées plus tôt dans le même appel.
map réassocie une Series élément par élément ; replace corrige des valeurs précises.species.map({"Adelie":"small", ...}) remplace chaque valeur via un dict ou une fonction. map ne travaille que sur une Series — pour exécuter une fonction sur chaque cellule d’une DataFrame entière, utilisez DataFrame.map (l’ancien applymap est déprécié).
Une colonne conditionnelle, c’est np.where / np.select, pas apply.np.where(mass>4000, "heavy", "light") est le if/else vectorisé ; np.select gère trois branches ou plus. Les deux s’exécutent en C sur toute la colonne — bien plus rapides et clairs qu’un apply ligne par ligne.
Ne recourez à apply qu’en dernier.apply(axis=1) exécute une fonction Python par ligne ; ici, lui et l’expression vectorisée donnent la même réponse (20.777), mais la version vectorielle est plus rapide et se lit mieux. N’utilisez apply que lorsqu’il n’existe vraiment aucun équivalent vectorisé, .str ou np.where.
.str nettoie le texte en masse ; astype("category") le compresse et l’ordonne. L’accesseur .str vectorise upper/strip/len/contains/replace/split. Convertir une colonne texte à faible cardinalité en category a réduit sa mémoire d’environ 25× ici (19,256 → 752 octets), et un Categoricalordonné fait suivre au tri, aux comparaisons et aux axes de tracé l’ordre que vous déclarez plutôt que l’ordre alphabétique.
Introduction
Vous n’analysez presque jamais les colonnes qu’on vous donne. La table brute a body_mass_g quand vous voulez des kilogrammes, une chaîne species quand vous voulez une classe de taille, une étiquette texte avec des espaces parasites et une casse incohérente, et une colonne object qui se trierait et se tracerait dans le mauvais ordre. Le vrai travail entre « CSV chargé » et « analyse lancée », c’est dériver et nettoyer des colonnes — et pandas a une trousse à outils réduite et affûtée pour exactement cela : assign, map, apply, np.where/np.select, l’accesseur .str et le dtype category.
Cette leçon parcourt cette trousse dans l’ordre où le travail se fait réellement : ajouter une colonne calculée avec assign, réassocier des valeurs avec map (et corriger des valeurs parasites avec replace), construire une colonne conditionnelle à la façon vectorisée avec np.where/np.select, nettoyer le texte en masse avec .str, et enfin donner à une colonne à faible cardinalité le dtype category pour un gros gain de mémoire et un tri ordonné correct. Au passage, nous réglons l’erreur de performance pandas la plus courante — recourir à apply là où une expression vectorisée fait le même travail plus vite.
Les données sont la table des manchots de Palmer (issue du jeu de données palmerpenguins) — 344 oiseaux avec quelques colonnes texte (species, island, sex) à nettoyer, des catégories à faible cardinalité à compresser et des mesures numériques sur lesquelles calculer. Elle est livrée sous forme d’un petit CSV à côté de cette leçon, donc rien à télécharger et aucun réseau au rendu.
C’est la couche de transformation qui vient au-dessus des DataFrames pandas, qui couvre le chargement, la sélection, le filtrage, groupby et fillna — on s’appuie dessus sans les réexpliquer. C’est aussi l’étape naturelle après les leçons sœurs Combiner : merge & join et Remodeler : pivot & melt : on dérive et nettoie généralement les colonnes juste après une jointure ou un remodelage. Chaque résultat ci-dessous a été produit par le code montré, et les blocs se construisent dans l’ordre. Pour expérimenter, éditez la cellule Essayez en direct à la fin et appuyez sur Run.
La table des manchots
Chargez le fichier et regardez ce que vous avez — les dtypes vous disent quelle colonne appelle quel outil.
import pandas as pdpenguins = pd.read_csv("penguins.csv")print("shape:", penguins.shape)print(penguins.dtypes)print(penguins.head(3))
shape: (344, 7)
species object
island object
bill_length_mm float64
bill_depth_mm float64
flipper_length_mm float64
body_mass_g float64
sex object
dtype: object
species island bill_length_mm bill_depth_mm flipper_length_mm \
0 Adelie Torgersen 39.1 18.7 181.0
1 Adelie Torgersen 39.5 17.4 186.0
2 Adelie Torgersen 40.3 18.0 195.0
body_mass_g sex
0 3750.0 Male
1 3800.0 Female
2 3250.0 Female
344 lignes, 7 colonnes. Trois colonnes sont object — le mot de pandas pour texte (species, island, sex) ; ce sont les candidates pour .str et category. Les quatre mesures sont des nombres float64 sur lesquels calculer. (Quelques mesures sont manquantes — body_mass_g a 2 NaN, sex en a 11 — ce qui comptera quand nous nettoierons le texte et construirons des colonnes conditionnelles.) Maintenant, dérivons et nettoyons, un outil à la fois.
assign : ajouter une colonne calculée
La transformation la plus courante consiste à ajouter une colonne dérivée de colonnes existantes — la masse en kilogrammes, un ratio, un indicateur. Vous pouvez écrire df["mass_kg"] = df["body_mass_g"] / 1000, mais cela modifie la frame en place. assign renvoie plutôt une nouvelle frame avec la colonne ajoutée, ce qui lui permet de s’enchaîner proprement dans un pipeline.
species body_mass_g mass_kg
0 Adelie 3750.0 3.75
1 Adelie 3800.0 3.80
2 Adelie 3250.0 3.25
mean mass_kg: 4.202
mass_kg est maintenant une colonne, et la masse corporelle moyenne sur les 344 oiseaux est de 4.202 kg (la moyenne ignore les 2 masses manquantes). Deux choses font d’assign plus qu’une commodité :
Elle s’enchaîne. Comme elle renvoie une frame, vous pouvez écrire df.merge(...).assign(...).query(...) en un seul pipeline lisible au lieu d’une pile d’affectations en place df["x"] = ....
La forme lambda voit la frame en cours de construction. Passez une fonction et elle reçoit la frame courante, de sorte qu’une colonne ultérieure peut s’appuyer sur une précédente dans le même appel :
Les deux nouvelles colonnes se calculent de façon vectorisée sur toute la frame d’un coup — le bill_ratio dérivé (longueur du bec ÷ profondeur) tourne autour de 2.1–2.3 pour ces premiers oiseaux. Pas de boucle, pas d’apply ; juste de l’arithmétique de colonnes.
map : réassocier des valeurs élément par élément
Pour traduire chaque valeur d’une colonne en une autre valeur — species → une classe de taille grossière, un code → une étiquette — utilisez map sur la Series avec un dict (ou une fonction). Chaque valeur est recherchée puis remplacée.
species
small 220
large 124
Name: count, dtype: int64
Chaque chaîne d’espèce est devenue sa classe de taille : 220 small (Adelie + Chinstrap) et 124 large (Gentoo). map est l’outil quand vous avez une table claire de → vers. Un proche cousin est replace, qui n’échange que les valeurs que vous nommez et laisse le reste intact — mieux pour corriger quelques mauvaises entrées que pour une réassociation complète :
island
Biscoe 168
Dream 124
Torgersen Island 52
Name: count, dtype: int64
replace n’a renommé que Torgersen ; Biscoe et Dream sont passés inchangés. Une réserve : map est une méthode de Series — elle travaille sur une colonne à la fois. Pour appliquer une fonction à chaque cellule d’une DataFrame entière, utilisez DataFrame.map (à noter : l’ancien DataFrame.applymap est déprécié et émet désormais un avertissement) :
# DataFrame.map runs a function element-wise across a whole framenums = penguins[["bill_length_mm", "flipper_length_mm"]].head(3)print(nums.map(lambda x: round(x)))
Chaque cellule numérique a été arrondie indépendamment. Pour une seule colonne, préférez Series.map ; ne recourez à DataFrame.map que lorsque vous avez vraiment besoin de la même fonction élément par élément sur plusieurs colonnes.
apply : une fonction ligne par ligne — à réserver en dernier
apply exécute une fonction Python sur une Series (par élément) ou, avec axis=1, sur une DataFrame par ligne — la fonction voit alors toute la ligne et peut combiner plusieurs colonnes. Cette souplesse est réelle, mais elle a un coût : apply(axis=1) appelle votre fonction Python une fois par ligne, ce qui est lent. La leçon à retenir tôt, c’est que la plupart des cas où l’on écrit apply ont un équivalent vectorisé à la fois plus rapide et plus clair.
Voici le même calcul — un ratio masse/nageoire — fait des deux façons :
Les deux donnent 20.777 — des réponses identiques. Mais ratio_vec exécute la division en C sur toute la colonne d’un coup, tandis que ratio_apply parcourt les 344 lignes en Python. Sur un jeu de données réel (des centaines de milliers de lignes), cette différence, ce sont des secondes contre un instant. Ne recourez à apply qu’en dernier — seulement quand il n’existe vraiment aucune expression vectorisée, .str ou np.where pour ce dont vous avez besoin (par exemple appeler une fonction externe qui attend des scalaires). Si vous pouvez écrire la transformation en arithmétique de colonnes, en appel .str ou en np.where, faites-le à la place.
np.where et np.select : la colonne conditionnelle vectorisée
« Ajouter une colonne dont la valeur dépend d’une condition » est l’une des questions pandas les plus posées — et la réponse idiomatique n’est pas un apply ligne par ligne, c’est np.where : un if/else vectorisé sur toute la colonne.
import numpy as nppenguins["size"] = np.where(penguins["body_mass_g"] >4000, "heavy", "light")print(penguins["size"].value_counts())
size
light 172
heavy 172
Name: count, dtype: int64
np.where(condition, value_if_true, value_if_false) a donné 172 heavy et 172 light — une répartition nette à 4 kg. (Les 2 masses manquantes atterrissent dans light : NaN > 4000 vaut False, elles prennent donc la branche else — bon à retenir quand un seuil rencontre des données manquantes.) Pour trois branches ou plus, empiler des appels np.where devient vite illisible ; utilisez np.select avec une liste de conditions et de choix correspondants, plus un default :
size3
medium 153
heavy 120
light 71
Name: count, dtype: int64
np.select vérifie les conditions dans l’ordre et prend la première qui correspond — ainsi un oiseau sous 3,500 g est light, un entre 3,500 et 4,500 est medium, et tout le reste (y compris les 2 masses NaN, qui ne correspondent à aucune condition) tombe dans le defaultheavy : 153 medium, 120 heavy, 71 light. C’est le motif qui passe à l’échelle pour une colonne en bandes/tranches — bien plus clair qu’un nid d’apply et de if/elif.
L’accesseur .str : nettoyer le texte en masse
Les colonnes texte arrivent en désordre — espaces parasites, casse incohérente, valeurs à tester ou à scinder. L’accesseur .str vectorise les méthodes de chaînes de Python sur toute une colonne : upper/lower, strip, len, contains, replace, split, et davantage. D’abord le geste de nettoyage quotidien — supprimer les espaces superflus et normaliser la casse sur une étiquette volontairement en désordre :
.str.strip() a supprimé les espaces autour et .str.title() a normalisé la casse — enchaînés, tous deux vectorisés, sans boucle. Sur la vraie colonne, .str répond directement à des questions courantes :
print("uppercased islands:", penguins["island"].str.upper().unique().tolist())print("species name lengths (first 3):", penguins["species"].str.len().head(3).tolist())print("rows whose species contains 'oo':", int(penguins["species"].str.contains("oo").sum()))
uppercased islands: ['TORGERSEN', 'BISCOE', 'DREAM']
species name lengths (first 3): [6, 6, 6]
rows whose species contains 'oo': 124
.str.upper() crie les trois noms d’îles (TORGERSEN, BISCOE, DREAM) ; .str.len() indique le nombre de caractères par valeur (Adelie → 6) ; et .str.contains("oo") renvoie une Series booléenne que l’on somme pour compter les correspondances — 124 lignes, exactement les manchots Gentoo (la seule espèce avec oo). Un piège à noter dès maintenant (et à revoir dans Problèmes fréquents) : .str sur une valeur non-chaîne ou manquante donne NaN, pas une erreur — un NaN dans sex reste donc NaN à travers .str.upper(). Passez na=False à .str.contains(..., na=False) quand vous voulez que les lignes manquantes soient traitées comme « pas de correspondance ».
astype("category") : mémoire et ordre
Une colonne comme species n’a que trois valeurs distinctes répétées 344 fois — stocker 344 chaînes séparées est du gaspillage. Le dtype category stocke chaque étiquette une seule fois et garde un code entier compact par ligne. Convertissez avec astype("category") et mesurez la différence :
La même colonne est passée de 19,256 octets à 752 — environ 25× plus petit — sans aucun changement de ses valeurs. Sur une colonne à faible cardinalité avec beaucoup de lignes, cette économie est grande et gratuite. Mais le plus grand gain est souvent l’ordre. Un object ordinaire (ou une catégorie non ordonnée) se trie et se trace alphabétiquement ; un Categoricalordonné suit l’ordre que vous déclarez, de sorte que les comparaisons (<, >), sort_values et les axes de tracé respectent tous une séquence porteuse de sens. Notre bande size3 est le cas parfait : son ordre porteur de sens est light < medium < heavy, mais alphabétiquement ces étiquettes se trient heavy, light, medium — un fouillis. Déclarez l’ordre que vous entendez :
ordered = pd.Categorical( penguins["size3"], categories=["light", "medium", "heavy"], # the order that MEANS something ordered=True,)print("ordered:", ordered.ordered)print("declared order:", ordered.categories.tolist())print("min (first category):", ordered.min())print("alphabetical order would be:", sorted(penguins["size3"].unique()))
ordered: True
declared order: ['light', 'medium', 'heavy']
min (first category): light
alphabetical order would be: ['heavy', 'light', 'medium']
ordered=True rend les catégories comparables, donc .min() renvoie la première catégorie déclarée, light — pas la plus petite alphabétiquement, qui serait heavy. C’est le signe révélateur : light et heavy divergent, donc cet ordre porte réellement une information qu’une simple colonne texte ne peut pas porter. Déclarez ["heavy", "medium", "light"] à la place et .min() bascule sur heavy. Ce contrôle est ce qui règle le classique problème « mes barres sont dans le mauvais ordre » : fixez l’ordre de la catégorie une fois et chaque tri et chaque tracé lui obéit.
Voici cette catégorie ordonnée qui pilote une figure. Nous convertissons size3 au type ordonné, puis le regroupons avec groupby — comme la catégorie est ordonnée, les groupes (et les barres) sortent light → medium → heavy. Un groupby non ordonné sur la même colonne les trierait alphabétiquement en heavy, light, medium — le fouillis dénué de sens que le type ordonné empêche :
import matplotlib.pyplot as pltpenguins["size3"] = pd.Categorical( penguins["size3"], categories=["light", "medium", "heavy"], ordered=True,)mean_flipper = penguins.groupby("size3", observed=True)["flipper_length_mm"].mean()fig, ax = plt.subplots(figsize=(7, 4.5))ax.bar(mean_flipper.index.astype(str), mean_flipper.values, color="#3a86d4", width=0.6)for i, v inenumerate(mean_flipper.values): ax.text(i, v +1.5, f"{v:,.1f}", ha="center", va="bottom", fontsize=9)ax.set_xlabel("Body-mass band (ordered category)")ax.set_ylabel("Mean flipper length (mm)")ax.set_title("Mean flipper length by body-mass band")ax.set_ylim(0, 240)ax.spines[["top", "right"]].set_visible(False)ax.grid(axis="y", alpha=0.3)fig.tight_layout()plt.show()
Figure 1
L’axe des x se lit light → medium → heavy parce que c’est l’ordre que nous avons déclaré — et il n’est visiblement pas alphabétique (ce qui mettrait heavy en premier). Les barres montent régulièrement, de 188.0 mm pour les oiseaux légers à 195.1 pour les moyens et 216.3 pour les lourds : les manchots plus lourds ont des nageoires plus longues. Inversez la liste categories= et les barres s’inversent ; c’est tout l’intérêt d’une catégorie ordonnée — vous décidez la séquence, et le reste de pandas l’honore.
Quel outil pour quelle transformation ?
Choisissez l’outil selon la forme de la transformation, pas par habitude :
La transformation dont vous avez besoin
Recourez à
Exemple
Ajouter une colonne calculée
assign
df.assign(kg=df["g"]/1000)
Réassocier des valeurs connues
map (ou replace pour en corriger quelques-unes)
s.map({"A":"small", ...})
Condition → deux issues
np.where
np.where(m>4000, "heavy", "light")
Condition → trois issues ou plus
np.select
np.select([c1, c2], [v1, v2], default=...)
Nettoyer / tester / scinder du texte
.str
s.str.strip().str.upper()
Étiquettes compactes + ordonnées
astype("category")
pd.Categorical(s, categories=[...], ordered=True)
Une vraie fonction Python ligne par ligne
apply(axis=1)(dernier recours)
df.apply(f, axis=1)
Règle générale : si la transformation peut s’écrire en arithmétique de colonnes, en appel .str, en map ou en np.where/np.select, écrivez-la ainsi — vectorisée, rapide, lisible. Réservez apply au cas rare qui n’a aucune forme vectorisée.
Essayez en direct
Les blocs ci-dessus se sont exécutés au moment du build. Éditez celui-ci et appuyez sur Run pour dériver et convertir des colonnes vous-même — il s’exécute dans votre navigateur via Pyodide (aucune installation). Le premier Run télécharge pandas une fois (~20 Mo), ensuite c’est mis en cache et instantané.
🟢 Avec un agent IA
Vous fixez une colonne à dériver ou à nettoyer ? Collez un df.head() et demandez à Prova« ajoute une classe de taille à partir de la masse corporelle, nettoie cette colonne texte, et fais de species une catégorie ordonnée » — ou « pourquoi mon apply(axis=1) est-il si lent, et quelle est la version vectorisée ? » Elle écrit les appels assign, np.where/np.select, .str et pd.Categorical(..., ordered=True), et — surtout — repère l’apply que vous pouvez remplacer par de la simple arithmétique de colonnes. Parce que le runtime est juste là, vous l’exécutez et lisez vous-même la colonne dérivée, au lieu de faire confiance à une transformation que vous ne voyez pas. The runtime is the judge. Demander à Prova →
La même idée en R
Vous travaillez en R, ou dans les deux langages ? Chaque geste ici a son jumeau tidyverse. assign, c’est le mutate() de dplyr ; np.where, c’est dplyr::if_else() et np.select, c’est case_when() ; map/replace correspondent à dplyr::recode() / case_match() ; l’accesseur .str, c’est stringr (str_to_upper(), str_trim(), str_detect(), str_length()) ; et un Categorical ordonné, c’est le factor de R avec un ordre de niveaux fixé — géré au mieux avec forcats (fct_relevel, fct_reorder). Les concepts — dériver sans modifier en place, préférer une conditionnelle vectorisée à une boucle sur les lignes, nettoyer le texte en masse et contrôler l’ordre des catégories — se transposent directement. Voir Ajouter une colonne en R et la série plus large Manipulation de données en R pour le côté dplyr.
Problèmes fréquents
Un apply(axis=1) lent là où un vecteur suffirait.apply(axis=1) exécute votre fonction Python une fois par ligne — correct pour quelques centaines de lignes, atrocement lent pour des centaines de milliers. Avant de l’écrire, demandez-vous si la transformation est de la simple arithmétique de colonnes (df["a"] / df["b"]), une conditionnelle (np.where / np.select) ou une opération texte (.str). Si c’est le cas, utilisez la forme vectorisée : même résultat, bien plus rapide. Réservez apply à une logique vraiment ligne par ligne sans équivalent vectorisé.
applymap déclenche un FutureWarning.DataFrame.applymap est déprécié — le remplaçant est DataFrame.map, au comportement identique (df.map(func) exécute func sur chaque cellule). Pour une seule colonne, n’utilisez ni l’un ni l’autre : Series.map est l’outil mono-colonne (df["col"].map(func)). Retenez la nomenclature : Series.map réassocie une colonne ; DataFrame.map s’exécute élément par élément sur une frame entière.
.str sur une valeur non-chaîne ou manquante renvoie NaN, silencieusement. L’accesseur .str n’échoue pas sur NaN ou sur des nombres — il propage NaN, donc un sex manquant reste NaN à travers .str.upper(), et .str.contains(...) renvoie NaN (pas False) pour ces lignes. Ce NaN casse ensuite un masque booléen. Corrigez-le en indiquant à la méthode comment traiter les valeurs manquantes (s.str.contains("x", na=False)) ou en nettoyant d’abord (s.fillna("").str.contains("x") / s.astype("string")).
Une colonne catégorie se trie alphabétiquement au lieu de l’ordre voulu. Un simple astype("category") (ou une colonne object) n’a aucun ordre intrinsèque, donc sort_values et les axes de tracé retombent sur l’alphabétique. Pour contrôler l’ordre, vous devez construire une catégorie ordonnée : pd.Categorical(s, categories=[...dans votre ordre...], ordered=True) (ou s.cat.reorder_categories([...], ordered=True)). Seule une catégorie ordonnée prend en charge les comparaisons </> et .min()/.max() selon votre séquence.
Questions fréquentes
NoteQuelle est la différence entre apply et map en pandas ?
map est une méthode de Series qui substitue chaque valeur via un dict ou une fonction à un argument — idéale pour une réassociation directe valeur-à-valeur (species.map({"Adelie":"small", ...})). apply est plus général : sur une Series, il exécute une fonction par élément, et sur une DataFrame avec axis=1, il exécute une fonction par ligne (en voyant toutes les colonnes à la fois). Utilisez map pour une simple recherche valeur par valeur sur une colonne ; utilisez apply quand la fonction a besoin de plusieurs colonnes ou d’une logique sur mesure — mais vérifiez d’abord si une expression vectorisée (np.where, arithmétique de colonnes, .str) fait le travail plus vite. Pour une fonction élément par élément sur une DataFrame entière, utilisez DataFrame.map (pas le déprécié applymap).
NoteComment ajouter une nouvelle colonne pandas selon une condition ?
Utilisez le np.where vectorisé pour un choix à deux issues : df["size"] = np.where(df["body_mass_g"] > 4000, "heavy", "light"). Pour trois bandes ou plus, utilisez np.select avec une liste de conditions et de choix correspondants plus un default : np.select([m<3500, m<4500], ["light","medium"], default="heavy") — il prend la première condition qui correspond. Les deux sont bien plus rapides et clairs qu’un apply ligne par ligne avec if/elif. Attention au comportement de NaN : NaN > 4000 vaut False, donc les valeurs manquantes prennent la branche else (np.where) ou le default (np.select).
NoteComment utiliser apply avec une lambda en pandas ?
Sur une Series, s.apply(lambda x: ...) exécute la lambda par élément ; sur une DataFrame, df.apply(lambda row: ..., axis=1) l’exécute par ligne, où row["col"] lit chaque colonne. C’est souple mais lent, car la lambda s’exécute en Python une fois par ligne. Avant d’y recourir, cherchez une alternative vectorisée : df["a"] / df["b"] pour l’arithmétique, np.where(...) pour une conditionnelle, df["c"].str.... pour le texte. Dans cette leçon, le ratio apply(axis=1) et le simple df["a"]/df["b"] vectorisé ont donné la réponse identique (20.777) — préférez donc le vecteur et gardez apply pour une logique qui n’a vraiment aucune forme vectorisée.
NoteComment vérifier si une colonne pandas contient une sous-chaîne ?
Utilisez le contains de l’accesseur .str : df["species"].str.contains("oo") renvoie une Series booléenne (True là où la valeur contient "oo"), que vous pouvez .sum() pour compter les correspondances ou utiliser comme masque de filtre (df[df["species"].str.contains("oo")]). Par défaut, il traite le motif comme une regex, donc échappez les caractères spéciaux ou passez regex=False pour une correspondance littérale, et ajoutez case=False pour une correspondance insensible à la casse. Comme .str.contains renvoie NaN pour les valeurs manquantes, passez na=False pour que ces lignes comptent comme « pas de correspondance » au lieu de casser votre masque.
NotePourquoi convertir une colonne pandas au dtype category ?
Deux raisons. Mémoire : une colonne texte à faible cardinalité (peu de valeurs distinctes, beaucoup de lignes) stocke chaque étiquette une fois plus un code entier compact, ce qui a ici réduit species de 19,256 à 752 octets (~25×). Ordre : un Categoricalordonné (pd.Categorical(s, categories=[...], ordered=True)) fait suivre au tri, aux comparaisons </> et aux axes de tracé la séquence que vous déclarez au lieu de l’alphabétique — le correctif propre pour « mes barres sont dans le mauvais ordre ». Certaines bibliothèques (et groupby) tournent aussi plus vite sur des catégories. Passez votre chemin pour les colonnes à forte cardinalité (par ex. texte libre ou identifiants), où le surcoût de la catégorie l’emporte sur l’économie.
Testez vos connaissances
ImportantÀ vous — mettez une colonne en bandes et ordonnez une catégorie
Chargez les manchots et dérivez deux colonnes, toutes deux vectorisées (pas d’apply) :
Tâche 1. Ajoutez une colonne flipper_class avec trois bandes issues de flipper_length_mm : "short" pour moins de 190 mm, "medium" pour 190–210 mm (c.-à-d. < 210), et "long" sinon. Affichez son value_counts().
Tâche 2. Convertissez island en catégorie ordonnée avec l’ordre ["Torgersen", "Biscoe", "Dream"], puis confirmez que .min() renvoie Torgersen.
AstuceIndice
Pour la tâche 1, utilisez np.select avec deux conditions dans l’ordre (flipper < 190, puis flipper < 210) et default="long" — np.select prend la première correspondance, ordonnez donc les conditions de la plus stricte à la plus large. Pour la tâche 2, utilisez pd.Categorical(df["island"], categories=[...], ordered=True) et appelez .min() sur le résultat.
AstuceSolution
import pandas as pdimport numpy as nppenguins = pd.read_csv("penguins.csv")# Task 1 — a 3-band column with np.select (vectorized, no apply)flip = penguins["flipper_length_mm"]conditions = [flip <190, flip <210]choices = ["short", "medium"]penguins["flipper_class"] = np.select(conditions, choices, default="long")print(penguins["flipper_class"].value_counts())# Task 2 — an ordered categoricalisland_cat = pd.Categorical( penguins["island"], categories=["Torgersen", "Biscoe", "Dream"], ordered=True,)print("min island:", island_cat.min())
np.select vérifie flipper < 190 d’abord, puis flipper < 210, et envoie tout le reste (y compris les 2 lignes avec une longueur de nageoire manquante) vers default="long". Comme island_cat est ordonnée avec Torgersen en premier, .min() renvoie Torgersen — le plus petit selon l’ordre, pas selon l’alphabet (ce qui serait Biscoe).
Vérification rapide. Vous avez besoin d’une nouvelle colonne qui vaut "heavy" quand body_mass_g > 4000 et "light" sinon, sur une frame de centaines de milliers de lignes. Quel est le bon outil — df.apply(lambda r: "heavy" if r["body_mass_g"] > 4000 else "light", axis=1), ou np.where(df["body_mass_g"] > 4000, "heavy", "light") ?
AstuceAfficher la réponse
np.where. Les deux produisent la colonne identique, mais np.where évalue la condition de façon vectorisée en C sur tout le tableau d’un coup, tandis qu’apply(axis=1) appelle une fonction Python par ligne — des ordres de grandeur plus lent à l’échelle. Une colonne conditionnelle à deux issues est exactement ce à quoi sert np.where ; gardez apply pour une logique sans équivalent vectorisé.
Conclusion
Vous pouvez désormais transformer une table brute en les colonnes que vous analysez réellement : ajouter des colonnes calculées avec assign (enchaînable, sans mutation en place), réassocier des valeurs avec map et corriger les valeurs parasites avec replace, construire des colonnes conditionnelles à la façon vectorisée avec np.where et np.select, nettoyer le texte en masse avec l’accesseur .str, et donner à une colonne à faible cardinalité le dtype category pour un gros gain de mémoire et — avec ordered=True — un tri et un ordre de tracé corrects. Et vous connaissez l’habitude qui sépare le pandas rapide du lent : préférez une expression vectorisée, et ne recourez à apply qu’en dernier. Dérivez délibérément, nettoyez en masse, et l’analyse qui suit travaille sur exactement les colonnes dont elle a besoin.
Cela termine la série Manipulation de données en Python — combiner, remodeler, dates et transformer. À partir d’ici, les mêmes données vont vers une figure ou un modèle.
Leçons connexes
Construisez là-dessus :DataFrames pandas — chargement, sélection, filtrage et groupby, la base sur laquelle repose chaque transformation d’ici · Combiner : merge & join — on dérive généralement des colonnes juste après un merge · Remodeler : pivot & melt — remodelez, puis transformez le résultat mis au propre · Dates & séries temporelles — l’accesseur .dt est le jumeau datetime de .str.
Prouvez que vous savez le faire. Maîtrisez toute la série Manipulation de données en Python — suivez votre parcours, construisez des projets et obtenez un certificat.