Pandas est la bibliothèque Python la plus utilisée pour les données structurées. Il existe de nombreux tutoriels, mais j’aimerais présenter ici des astuces Pandas que tu ne connais peut-être pas encore, ou que tu connais mais sans savoir qu’elles s’utilisent de cette manière.
En bref : ces 22 astuces Pandas couvrent la lecture de fichiers, la manipulation de colonnes, le filtrage, le groupby, les dates et la performance. Trois d’entre elles changent vraiment le temps de calcul : remplacer iterrows() par itertuples() (ou mieux, par une opération vectorisée), utiliser nlargest() plutôt que sort_values().head(), et typer les colonnes à la lecture (usecols, dtype, category).
Tout le code a été testé en octobre 2026 avec Pandas 2.3.3 et Pandas 3.0.5 : les résultats sont identiques, sauf mention contraire. Les temps d’exécution donnés sont mesurés sur ma machine : les tiens seront différents, mais les ordres de grandeur resteront les mêmes.
Tu peux aussi exécuter toutes les astuces dans ce notebook Google Colab : chaque extrait est dans sa propre cellule, prêt à être modifié.

Le jeu de données utilisé dans l’article
Un DataFrame est un tableau de données à deux dimensions (des lignes et des colonnes), l’objet central de Pandas. La plupart des exemples utilisent ce petit DataFrame de dix élèves, avec leur ville, leur niveau (1, 2 ou 3) et leurs notes en maths et en sciences. Quand une astuce a besoin d’autres données (un gros fichier, des dates, des valeurs manquantes), je les crée dans l’extrait lui-même.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'ID': ['A001', 'B014', 'C022', 'D031', 'E047', 'F058', 'G063', 'H070', 'I082', 'J095'],
'ville': ['Lisbonne', 'Paris', 'Zurich', 'Lisbonne', 'Paris', 'Lisbonne', 'Zurich', 'Paris', 'Lisbonne', 'Zurich'],
'niveau': [1, 2, 3, 1, 2, 3, 1, 2, 3, 1],
'maths': [35, 72, 58, 35, 90, 64, 47, 81, 35, 66],
'sciences': [40, 65, 70, 30, 88, 55, 52, 79, 45, 71],
})
print(df)Python ID ville niveau maths sciences
0 A001 Lisbonne 1 35 40
1 B014 Paris 2 72 65
2 C022 Zurich 3 58 70
3 D031 Lisbonne 1 35 30
4 E047 Paris 2 90 88
5 F058 Lisbonne 3 64 55
6 G063 Zurich 1 47 52
7 H070 Paris 2 81 79
8 I082 Lisbonne 3 35 45
9 J095 Zurich 1 66 71Lecture et chargement de données
1. Comment lire rapidement un gros fichier CSV avec read_csv ?
Pour lire vite un gros fichier CSV, lis d’abord quelques lignes avec nrows=5, puis charge seulement les colonnes utiles (usecols) avec des types légers (dtype). Tout le monde connaît read_csv, qui lit un fichier CSV dans un DataFrame. Mais si le fichier est volumineux, commence par ne lire que quelques lignes avec l’argument nrows=5 : tu vérifies que le séparateur et les colonnes sont les bons avant de charger toute la table.
Pour la démonstration, je crée d’abord un fichier de 200 000 lignes dont le séparateur est un point-virgule (très courant dans les fichiers exportés depuis Excel en français) :
import pandas as pd
import numpy as np
rng = np.random.default_rng(0)
n = 200_000
pd.DataFrame({
'id': np.arange(n),
'ville': rng.choice(['Lisbonne', 'Paris', 'Zurich'], n),
'montant': rng.normal(100, 20, n).round(2),
'quantite': rng.integers(1, 10, n),
'remise': rng.random(n).round(3),
}).to_csv('ventes.csv', sep=';', index=False)PythonSi tu oublies de préciser le séparateur, tu le vois tout de suite : tout se retrouve dans une seule colonne.
import pandas as pd
apercu = pd.read_csv('ventes.csv', nrows=5)
print(apercu)Python id;ville;montant;quantite;remise
0 0;Zurich;83.1;8;0.249
1 1;Paris;115.04;1;0.023
2 2;Paris;105.41;7;0.02
3 3;Lisbonne;99.03;7;0.604
4 4;Lisbonne;116.77;6;0.997Avec le bon séparateur (sep=';'), les colonnes apparaissent correctement. columns.tolist() te donne la liste des noms de colonnes à copier.
import pandas as pd
apercu = pd.read_csv('ventes.csv', sep=';', nrows=5)
print(apercu)
print(apercu.columns.tolist())Python id ville montant quantite remise
0 0 Zurich 83.10 8 0.249
1 1 Paris 115.04 1 0.023
2 2 Paris 105.41 7 0.020
3 3 Lisbonne 99.03 7 0.604
4 4 Lisbonne 116.77 6 0.997
['id', 'ville', 'montant', 'quantite', 'remise'](Tu peux aussi regarder les premières lignes de n’importe quel fichier texte depuis le terminal, avec head -n 5 data.txt sous Linux et macOS.)
Ensuite, charge uniquement ce dont tu as besoin. usecols limite les colonnes lues. dtype évite à Pandas de deviner le type de chaque colonne, et te permet de choisir un type plus léger (ici int32, float32 et category). Autre avantage : si une colonne mélange du texte et des chiffres (des identifiants comme « 007 » ou « A12 »), déclare-la en str pour éviter les erreurs quand tu fusionneras des tableaux avec cette colonne comme clé.
import pandas as pd
df_ventes = pd.read_csv(
'ventes.csv',
sep=';',
usecols=['id', 'ville', 'montant'],
dtype={'id': 'int32', 'ville': 'category', 'montant': 'float32'},
)
print(df_ventes.dtypes)
print(df_ventes.shape)Pythonid int32
ville category
montant float32
dtype: object
(200000, 3)L’effet sur la mémoire est considérable :
import pandas as pd
complet = pd.read_csv('ventes.csv', sep=';')
cible = pd.read_csv('ventes.csv', sep=';', usecols=['id', 'ville', 'montant'],
dtype={'id': 'int32', 'ville': 'category', 'montant': 'float32'})
print(f"Lecture complète : {complet.memory_usage(deep=True).sum() / 1e6:.1f} Mo")
print(f"Lecture ciblée : {cible.memory_usage(deep=True).sum() / 1e6:.1f} Mo")PythonLecture complète : 17.5 Mo
Lecture ciblée : 1.8 Mo2. Comment sélectionner les colonnes selon leur type avec select_dtypes ?
La méthode select_dtypes sélectionne les colonnes d’un DataFrame selon leur type (nombres, texte, dates…). Pour le pré-traitement, select_dtypes te fait gagner du temps. Commence par vérifier la répartition des types de colonnes :
print(df.dtypes.value_counts())Pythonint64 3
str 2
Name: count, dtype: int64Dans Pandas 3, les colonnes de texte ont le type str ; dans Pandas 2 et avant, elles ont le type object (c’est la seule différence visible dans cet article).
Puis sélectionne les colonnes numériques (entiers et décimaux) avec include='number', ou à l’inverse toutes les autres avec exclude='number'. C’est plus court que de lister ['float64', 'int64'], et ça couvre aussi int32, float32, etc.
print(df.select_dtypes(include='number').head(3))
print()
print(df.select_dtypes(exclude='number').head(3))Python niveau maths sciences
0 1 35 40
1 2 72 65
2 3 58 70
ID ville
0 A001 Lisbonne
1 B014 Paris
2 C022 ZurichAttention : dans Pandas 3, include='object' ne reste compatible avec les colonnes de texte qu’au prix d’un avertissement. Pour les colonnes de texte, préfère exclude='number' ou include=['object', 'string'].
Manipulation de colonnes
3. Comment copier un DataFrame sans modifier l’original ?
Pour copier un DataFrame sans modifier l’original, utilise df.copy() : l’affectation df2 = df1 ne copie rien. C’est une méthode importante si tu n’en as pas encore entendu parler. Regarde ce code :
import pandas as pd
df1 = pd.DataFrame({'a': [0, 0, 0], 'b': [1, 1, 1]})
df2 = df1
df2['a'] = df2['a'] + 1
print(df1)
print(df1 is df2)Python a b
0 1 1
1 1 1
2 1 1
TrueLe DataFrame df1 a été modifié alors qu’on n’a touché qu’à df2. En effet, df2 = df1 ne crée pas de copie : c’est un deuxième nom pour le même objet (la dernière ligne l’affiche : df1 is df2 vaut True). Toute modification via l’un des deux noms se voit donc dans l’autre.
Pour obtenir un DataFrame indépendant, utilise copy() :
import pandas as pd
df1 = pd.DataFrame({'a': [0, 0, 0], 'b': [1, 1, 1]})
df2 = df1.copy()
df2['a'] = df2['a'] + 1
print(df1)
print(df2)Python a b
0 0 1
1 0 1
2 0 1
a b
0 1 1
1 1 1
2 1 1(La fonction deepcopy du module copy donne le même résultat, mais df.copy() est plus court.) Avec Pandas 3, les sous-tableaux issus d’un filtre ou d’un découpage se comportent toujours comme des copies (c’est le mécanisme « Copy-on-Write »), mais une simple affectation df2 = df1 reste un second nom pour le même objet.
4. Comment remplacer des valeurs avec map ?
La méthode map() remplace chaque valeur d’une série à l’aide d’un dictionnaire. Il s’agit d’une commande simple pour transformer des valeurs. Tu définis un dictionnaire dont les clés sont les anciennes valeurs et les valeurs sont les nouvelles :
niveau_map = {1: 'élevé', 2: 'moyen', 3: 'faible'}
df['niveau_txt'] = df['niveau'].map(niveau_map)
print(df[['ID', 'niveau', 'niveau_txt']].head(4))Python ID niveau niveau_txt
0 A001 1 élevé
1 B014 2 moyen
2 C022 3 faible
3 D031 1 élevéLes valeurs absentes du dictionnaire deviennent NaN. Les usages classiques : transformer True et False en 1 et 0 pour la modélisation, définir des niveaux, appliquer un codage défini par toi.
reussite = df['maths'] >= 50
print(reussite.map({True: 1, False: 0}).tolist())Python[0, 1, 1, 0, 1, 1, 0, 1, 0, 1]5. Faut-il utiliser apply sur un DataFrame ?
La méthode apply(axis=1) appelle une fonction Python sur chaque ligne : elle est pratique mais lente, donc à réserver aux cas sans équivalent vectorisé. Si tu veux créer une colonne à partir de plusieurs autres, apply avec axis=1 peut être très pratique. Ici (avec la colonne niveau_txt créée à l’astuce 4), on donne un bonus aux élèves de niveau « moyen » qui ont plus de 75 en maths :
def regle(niveau, maths):
if niveau == 'moyen' and maths > 75:
return 1
else:
return 0
df['bonus'] = df.apply(lambda ligne: regle(ligne['niveau_txt'], ligne['maths']), axis=1)
print(df[['ID', 'niveau_txt', 'maths', 'bonus']])Python ID niveau_txt maths bonus
0 A001 élevé 35 0
1 B014 moyen 72 0
2 C022 faible 58 0
3 D031 élevé 35 0
4 E047 moyen 90 1
5 F058 faible 64 0
6 G063 élevé 47 0
7 H070 moyen 81 1
8 I082 faible 35 0
9 J095 élevé 66 0Le problème : avec axis=1, Pandas appelle ta fonction Python pour chaque ligne. C’est lent. Quand une opération vectorisée existe, elle fait le même travail bien plus vite. Ici, on peut écrire la règle directement avec des comparaisons :
df['bonus_vec'] = ((df['niveau_txt'] == 'moyen') & (df['maths'] > 75)).astype(int)
print((df['bonus'] == df['bonus_vec']).all())PythonTruePour calculer le maximum de deux colonnes, utilise directement max(axis=1) :
df['meilleur_score'] = df[['maths', 'sciences']].max(axis=1)
print(df[['maths', 'sciences', 'meilleur_score']].head(4))Python maths sciences meilleur_score
0 35 40 40
1 72 65 72
2 58 70 70
3 35 30 35plutôt que df.apply(lambda x: max(x['maths'], x['sciences']), axis=1). Sur 200 000 lignes, l’écart est énorme :
import time
import numpy as np
import pandas as pd
grand = pd.DataFrame({
'c1': np.random.default_rng(0).integers(0, 100, 200_000),
'c2': np.random.default_rng(1).integers(0, 100, 200_000),
})
debut = time.perf_counter()
grand.apply(lambda x: max(x['c1'], x['c2']), axis=1)
print(f"apply : {time.perf_counter() - debut:.3f} s")
debut = time.perf_counter()
grand[['c1', 'c2']].max(axis=1)
print(f"max() : {time.perf_counter() - debut:.3f} s")Pythonapply : 0.969 s
max() : 0.018 sRègle d’or : n’utilise pas apply ligne par ligne si les fonctions intégrées de Pandas ou de NumPy font la même chose.
6. Comment insérer une colonne à une position précise avec insert ?
La méthode insert() ajoute une colonne à la position de ton choix, au lieu de la fin. Quand tu ajoutes une colonne, elle arrive à la fin par défaut. Avec insert, tu choisis sa position : l’indice (entier) en premier argument, le nom de la colonne en deuxième, les valeurs en troisième. Les valeurs doivent avoir autant d’éléments que de lignes, et le nom ne doit pas déjà exister.
import numpy as np
import pandas as pd
petit = pd.DataFrame({'a': [1, 2, 3, 4], 'b': [10, 20, 30, 40], 'c': [5, 6, 7, 8]})
nouveau = np.random.default_rng(0).integers(5, size=len(petit))
petit.insert(2, 'nouvelle_col', nouveau)
print(petit)Python a b nouvelle_col c
0 1 10 4 5
1 2 20 3 6
2 3 30 2 7
3 4 40 1 87. Comment retirer une colonne tout en la conservant avec pop ?
La méthode pop() retire une colonne du DataFrame et te la renvoie sous forme de série, en une seule opération. Pour supprimer une colonne d’un DataFrame tout en la gardant dans une série séparée, plutôt qu’une affectation suivie d’un drop, utilise directement pop :
import pandas as pd
ventes = pd.DataFrame({'produit': ['A', 'B', 'C'], 'prix': [10, 25, 40], 'quantite': [3, 1, 2]})
prix = ventes.pop('prix')
print(prix)
print(ventes)Python0 10
1 25
2 40
Name: prix, dtype: int64
produit quantite
0 A 3
1 B 1
2 C 2Filtrage et sélection
8. Comment sélectionner des lignes par identifiants avec isin ?
La méthode isin() repère les lignes dont la valeur figure dans une liste : c’est l’équivalent du IN de SQL. En SQL, tu écrirais SELECT * FROM … WHERE ID IN ('A001', 'C022', …). En Pandas, c’est isin ; le symbole ~ inverse le filtre (équivalent de « NOT IN ») :
filtre = df['ID'].isin(['A001', 'C022', 'J095'])
print(df[filtre][['ID', 'ville', 'maths']])
print()
print(df[~filtre]['ID'].tolist())Python ID ville maths
0 A001 Lisbonne 35
2 C022 Zurich 58
9 J095 Zurich 66
['B014', 'D031', 'E047', 'F058', 'G063', 'H070', 'I082']9. Comment filtrer un DataFrame avec query() ?
La méthode query() filtre un DataFrame avec une expression écrite sous forme de texte. Elle reconnaît les opérations entre colonnes :
print(df.query('maths < sciences')[['ID', 'maths', 'sciences']])Python ID maths sciences
0 A001 35 40
2 C022 58 70
6 G063 47 52
8 I082 35 45
9 J095 66 71print(df.query('maths - sciences > 5')[['ID', 'maths', 'sciences']])Python ID maths sciences
1 B014 72 65
5 F058 64 55Tu peux combiner plusieurs conditions avec & ou and, utiliser une variable Python avec @, et tester une appartenance avec in. Si le nom d’une colonne contient un espace, entoure-le d’accents graves : `prix total` > 3.
seuil = 60
print(df.query('maths > @seuil & ville == "Paris"')[['ID', 'ville', 'maths']])
print()
print(df.query('ville in ["Zurich", "Paris"] and sciences >= 70')[['ID', 'ville', 'sciences']])Python ID ville maths
1 B014 Paris 72
4 E047 Paris 90
7 H070 Paris 81
ID ville sciences
2 C022 Zurich 70
4 E047 Paris 88
7 H070 Paris 79
9 J095 Zurich 7110. Comment comparer des colonnes avec gt(), lt(), eq() et les autres méthodes ?
Les opérateurs <, >, <=, >=, ==, != ont des équivalents sous forme de méthodes : .lt(), .gt(), .le(), .ge(), .eq() et .ne(). Ils sont pratiques pour enchaîner les méthodes sur une seule ligne et servent à :
- comparer un DataFrame à une valeur de base (résultat : des booléens)
- découper (slicing) le DataFrame selon une comparaison
- créer une nouvelle colonne booléenne à partir de la comparaison de deux colonnes
# 1. Comparaison à une valeur de base
print(df[['maths', 'sciences']].gt(50).head(3))
# 2. Slicing basé sur une comparaison
df_diff = df[df['maths'].ne(35)]
df_egal = df[df['maths'].eq(35)]
print(len(df_diff), len(df_egal))
# 3. Nouvelle colonne booléenne à partir de deux colonnes
df['fort_en_maths'] = df['maths'].ge(df['sciences'])
print(df[['ID', 'maths', 'sciences', 'fort_en_maths']].head(4))Python maths sciences
0 False False
1 True True
2 True True
7 3
ID maths sciences fort_en_maths
0 A001 35 40 False
1 B014 72 65 True
2 C022 58 70 False
3 D031 35 30 True11. Comment obtenir les N plus grandes valeurs avec nlargest() ?
Pour obtenir les N plus grandes valeurs avec toutes leurs colonnes, utilise df.nlargest(N, columns='col') ; nsmallest() fait l'inverse. On veut souvent connaître les N meilleures ou les N pires lignes selon une colonne. Appliquer nlargest sur une seule colonne ne renvoie que les valeurs : les autres colonnes sont perdues (l'index permet seulement de retrouver les lignes).
print(df['maths'].nlargest(3))Python4 90
7 81
1 72
Name: maths, dtype: int64Appliqué au DataFrame, il garde toutes les colonnes. En cas d'égalité, keep='first' (par défaut) coupe au premier rang, tandis que keep='all' garde toutes les lignes à égalité, comme ici pour les trois élèves à 35 :
print(df.nlargest(3, columns='maths')[['ID', 'ville', 'maths']])
print()
print(df.nsmallest(3, columns='maths', keep='all')[['ID', 'ville', 'maths']])Python ID ville maths
4 E047 Paris 90
7 H070 Paris 81
1 B014 Paris 72
ID ville maths
0 A001 Lisbonne 35
3 D031 Lisbonne 35
8 I082 Lisbonne 35Côté performance, nlargest et nsmallest sont plus rapides que sort_values() suivi de head(), puisqu'ils n'ont pas besoin de trier toute la colonne. D'après mes mesures, ils sont de 3 à 5 fois plus rapides selon la version de Pandas :
import time
import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
grand = pd.DataFrame({'score': rng.normal(size=1_000_000)})
debut = time.perf_counter()
grand.nlargest(3, columns='score')
print(f"nlargest : {time.perf_counter() - debut:.3f} s")
debut = time.perf_counter()
grand.sort_values(by='score', ascending=False).head(3)
print(f"sort_values+head: {time.perf_counter() - debut:.3f} s")Pythonnlargest : 0.016 s
sort_values+head: 0.062 sGroupby et agrégation
12. Comment compter les occurrences de chaque valeur avec value_counts() ?
La méthode value_counts() compte le nombre d'occurrences de chaque valeur d'une colonne. Cette méthode donne la distribution des valeurs d'une colonne. Quelques arguments utiles :
normalize=True: la fréquence au lieu du nombredropna=False: inclut les valeurs manquantes dans le décompte.reset_index(): convertit le résultat en DataFrame.sort_index(): trie par valeurs distinctes plutôt que par fréquence
print(df['ville'].value_counts())
print()
print(df['ville'].value_counts(normalize=True))Pythonville
Lisbonne 4
Paris 3
Zurich 3
Name: count, dtype: int64
ville
Lisbonne 0.4
Paris 0.3
Zurich 0.3
Name: proportion, dtype: float64import numpy as np
import pandas as pd
s = pd.Series(['a', 'b', 'a', np.nan, 'a', 'b', np.nan])
print(s.value_counts(dropna=False))
print()
print(df['ville'].value_counts().reset_index())
print()
print(df['maths'].value_counts().sort_index().head(4))Pythona 3
b 2
NaN 2
Name: count, dtype: int64
ville count
0 Lisbonne 4
1 Paris 3
2 Zurich 3
maths
35 3
47 1
58 1
64 1
Name: count, dtype: int6413. Comment extraire un groupe précis avec groupby().get_group() ?
Pour extraire un groupe précis, crée l'objet GroupBy une seule fois, puis appelle get_group('valeur') : c'est utile surtout quand tu extrais plusieurs groupes. Au lieu de coller tout de suite une agrégation à groupby(), tu peux garder l'objet GroupBy et le réutiliser. get_group() récupère alors le sous-ensemble d'un groupe précis :
par_ville = df.groupby('ville')
print(par_ville.get_group('Lisbonne')[['ID', 'maths']])Python ID maths
0 A001 35
3 D031 35
5 F058 64
8 I082 35Attention à ne pas surestimer cette astuce : pour extraire un seul groupe, un filtre booléen classique (df[df['ville'] == 'Lisbonne']) est même un peu plus rapide, car le groupby doit d'abord tout calculer. L'intérêt apparaît quand tu extrais plusieurs groupes d'un même objet GroupBy : les groupes sont calculés une seule fois. Sur un million de lignes et cinq villes, j'obtiens plus de deux fois moins de temps :
import time
import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
villes = ['Lisbonne', 'Paris', 'Zurich', 'Genève', 'Berlin']
grand = pd.DataFrame({'ville': rng.choice(villes, 1_000_000), 'x': rng.normal(size=1_000_000)})
debut = time.perf_counter()
masques = [grand[grand['ville'] == v] for v in villes]
print(f"5 filtres booléens : {time.perf_counter() - debut:.3f} s")
debut = time.perf_counter()
groupes = grand.groupby('ville')
sous_ensembles = [groupes.get_group(v) for v in villes]
print(f"1 groupby + 5 get_group : {time.perf_counter() - debut:.3f} s")Python5 filtres booléens : 0.373 s
1 groupby + 5 get_group : 0.152 s14. Comment calculer une somme cumulée par groupe ?
Pour une somme cumulée par groupe, utilise df.groupby('groupe')['valeur'].cumsum(). cumsum calcule la somme cumulée d'une colonne, mais sans tenir compte des groupes. Pour une somme cumulée par groupe, passe par groupby :
ventes = pd.DataFrame({
'groupe': ['A', 'A', 'B', 'A', 'B', 'B'],
'valeur': [10, 20, 5, 30, 15, 25],
})
ventes['cumul'] = ventes['valeur'].cumsum()
ventes['cumul_par_groupe'] = ventes.groupby('groupe')['valeur'].cumsum()
print(ventes)Python groupe valeur cumul cumul_par_groupe
0 A 10 10 10
1 A 20 30 30
2 B 5 35 5
3 A 30 65 60
4 B 15 80 20
5 B 25 105 4515. Comment calculer plusieurs statistiques à la fois avec agg() ?
Il existe de nombreuses fonctions d'agrégation (moyenne, somme, décompte…). Pour en calculer plusieurs d'un coup, par exemple la moyenne et le décompte pour repérer des moyennes calculées sur trop peu de lignes, utilise agg :
print(df.groupby('ville')['maths'].agg(['mean', 'count']))Python mean count
ville
Lisbonne 42.25 4
Paris 81.00 3
Zurich 57.00 3Tu peux aussi nommer les colonnes du résultat avec l'agrégation nommée, ce qui évite les noms de colonnes à deux niveaux :
resume = df.groupby('ville').agg(
maths_moy=('maths', 'mean'),
sciences_moy=('sciences', 'mean'),
eleves=('ID', 'count'),
)
print(resume.round(1))Python maths_moy sciences_moy eleves
ville
Lisbonne 42.2 42.5 4
Paris 81.0 77.3 3
Zurich 57.0 64.3 316. Comment créer des groupes de percentiles avec qcut() ?
Pour classer les valeurs d'une colonne numérique en groupes selon des seuils de percentile (par exemple les 5 % meilleures, de 80 à 95 %, de 50 à 80 % et les 50 % du bas), pd.qcut fait exactement ce travail, sans boucle ni apply. Tu donnes les quantiles voulus dans q et des étiquettes dans labels :
rng = np.random.default_rng(0)
notes = pd.DataFrame({'c': rng.normal(size=1000)})
notes['groupe'] = pd.qcut(
notes['c'],
q=[0, 0.5, 0.8, 0.95, 1],
labels=['bas 50 %', '50-80 %', '80-95 %', 'top 5 %'],
)
print(notes['groupe'].value_counts().sort_index())Pythongroupe
bas 50 % 500
50-80 % 300
80-95 % 150
top 5 % 50
Name: count, dtype: int64Ne confonds pas qcut (intervalles définis par des quantiles, donc des groupes d'effectifs voulus) et cut (intervalles de largeur choisie, voir l'astuce 22).
Dates
17. Comment regrouper des dates par mois ou par trimestre avec to_period() ?
La méthode dt.to_period() convertit une date en période (mois, trimestre, année…), ce qui permet de regrouper les dates par période. On manipule les dates avec des intervalles variés (jour, semaine, mois, trimestre…). Les fonctions de dates sont regroupées sous l'accesseur dt. La méthode to_period convertit une date en période, ce qui permet par exemple de distinguer décembre 2020 de décembre 2021, ce que .dt.month seul ne permet pas :
dates = pd.DataFrame({'date': pd.to_datetime(['2020-12-15', '2021-12-03', '2021-02-20', '2021-03-14', '2021-12-28'])})
dates['mois_seul'] = dates['date'].dt.month
dates['mois'] = dates['date'].dt.to_period('M')
dates['trimestre'] = dates['date'].dt.to_period('Q')
print(dates)
print()
print(dates.groupby('mois').size())Python date mois_seul mois trimestre
0 2020-12-15 12 2020-12 2020Q4
1 2021-12-03 12 2021-12 2021Q4
2 2021-02-20 2 2021-02 2021Q1
3 2021-03-14 3 2021-03 2021Q1
4 2021-12-28 12 2021-12 2021Q4
mois
2020-12 1
2021-02 1
2021-03 1
2021-12 2
Freq: M, dtype: int64Performance et mémoire
18. Comment parcourir un DataFrame ligne par ligne rapidement ?
Pour parcourir un DataFrame ligne par ligne, itertuples() est plus de 40 fois plus rapide que iterrows() dans mon test ; une opération vectorisée est encore meilleure. Si tu dois vraiment parcourir un DataFrame ligne par ligne (à éviter en général), itertuples() est nettement plus rapide que iterrows() : iterrows() fabrique une série Pandas pour chaque ligne, alors que itertuples() renvoie de simples tuples nommés. Et quand c'est possible, la vectorisation est encore bien meilleure :
import time
import numpy as np
import pandas as pd
d = pd.DataFrame({'Nombre': np.random.default_rng(0).integers(0, 100, 200_000)})
debut = time.perf_counter()
total = 0
for _, ligne in d.iterrows():
total += ligne['Nombre']
print(f"iterrows : {time.perf_counter() - debut:.2f} s (total = {total})")
debut = time.perf_counter()
total = 0
for ligne in d.itertuples(index=False):
total += ligne.Nombre
print(f"itertuples : {time.perf_counter() - debut:.2f} s (total = {total})")
debut = time.perf_counter()
total = d['Nombre'].sum()
print(f"sum() : {time.perf_counter() - debut:.4f} s (total = {total})")Pythoniterrows : 3.11 s (total = 9898742)
itertuples : 0.07 s (total = 9898742)
sum() : 0.0004 s (total = 9898742)Sur un DataFrame d'un million de lignes, j'ai mesuré environ 12 secondes pour iterrows(), 0,2 seconde pour itertuples() et moins d'une milliseconde pour sum().
19. Comment réduire la mémoire d'une colonne de texte avec le type category ?
Le type category stocke chaque valeur distincte d'une colonne de texte une seule fois : il divise la mémoire par 60 dans l'exemple ci-dessous. Les colonnes de texte contenant peu de valeurs distinctes (villes, pays, catégories…) peuvent prendre le type category, qui stocke chaque valeur distincte une seule fois. Attention : pour mesurer la mémoire d'une colonne de texte, il faut passer deep=True à memory_usage, sinon Pandas ne compte que les pointeurs (8 Mo ici au lieu de 60 Mo).
import numpy as np
import pandas as pd
villes = np.random.default_rng(0).choice(['Lisbonne', 'Paris', 'Zurich', 'Genève', 'Berlin'], 1_000_000)
s_obj = pd.Series(villes, dtype=object)
s_cat = s_obj.astype('category')
print(f"object : {s_obj.memory_usage(deep=True) / 1e6:.1f} Mo")
print(f"category : {s_cat.memory_usage(deep=True) / 1e6:.1f} Mo")Pythonobject : 60.0 Mo
category : 1.0 MoPour aller plus loin, lis mon article sur les 7 techniques d'optimisation de la mémoire avec Pandas.
Nettoyage et export
20. Comment compter les valeurs manquantes par ligne ?
Pour compter les valeurs manquantes par ligne (utile pour exclure les lignes trop incomplètes avant de construire un modèle), combine isnull() et sum(axis=1) :
dfn = pd.DataFrame({
'c1': [1, np.nan, 3, np.nan],
'c2': [5, np.nan, np.nan, np.nan],
'c3': [7, 8, 9, np.nan],
})
dfn['nb_manquants'] = dfn[['c1', 'c2', 'c3']].isnull().sum(axis=1)
print(dfn)
print()
print(dfn[dfn['nb_manquants'] < 3])Python c1 c2 c3 nb_manquants
0 1.0 5.0 7.0 0
1 NaN NaN 8.0 2
2 3.0 NaN 9.0 1
3 NaN NaN NaN 3
c1 c2 c3 nb_manquants
0 1.0 5.0 7.0 0
1 NaN NaN 8.0 2
2 3.0 NaN 9.0 121. Comment exporter proprement un DataFrame avec to_csv() ?
Sans nom de fichier, to_csv() renvoie le texte du fichier : pratique pour prévisualiser ce qui sera écrit avant d'exporter. index=False évite d'écrire l'index.
print(df[['ID', 'ville', 'maths']][:3].to_csv(index=False))PythonID,ville,maths
A001,Lisbonne,35
B014,Paris,72
C022,Zurich,58Autre piège : une colonne qui contient des entiers et des valeurs manquantes devient automatiquement une colonne de décimaux (float), et tes entiers sont exportés avec un « .0 » gênant. On voit parfois conseillé d'ajouter float_format='%.0f', mais attention : cet argument s'applique à toutes les colonnes décimales et les arrondit toutes, y compris celles qui contiennent de vraies décimales (la taille 1,72 devient 2) :
manq = pd.DataFrame({'id': [1, 2, 3], 'age': [25, np.nan, 31], 'taille': [1.72, 1.65, 1.80]})
print(manq.dtypes)
print()
print(manq.to_csv(index=False))
print(manq.to_csv(index=False, float_format='%.0f'))Pythonid int64
age float64
taille float64
dtype: object
id,age,taille
1,25.0,1.72
2,,1.65
3,31.0,1.8
id,age,taille
1,25,2
2,,2
3,31,2La bonne solution est de convertir seulement la colonne concernée en entier nullable (Int64, avec un I majuscule), qui accepte les valeurs manquantes :
manq['age'] = manq['age'].astype('Int64')
print(manq.dtypes)
print()
print(manq.to_csv(index=False))Pythonid int64
age Int64
taille float64
dtype: object
id,age,taille
1,25,1.72
2,,1.65
3,31,1.822. Comment découper des valeurs en intervalles avec cut() ?
La fonction cut() découpe des valeurs continues en intervalles discrets. Le plus simple : demander un nombre d'intervalles de même largeur.
print(pd.cut(df['maths'], bins=2).head(4))Python0 (34.945, 62.5]
1 (62.5, 90.0]
2 (34.945, 62.5]
3 (34.945, 62.5]
Name: maths, dtype: category
Categories (2, interval[float64, right]): [(34.945, 62.5] < (62.5, 90.0]]Ou définir toi-même les bornes :
print(pd.cut(df['maths'], bins=[0, 50, 100]).head(4))Python0 (0, 50]
1 (50, 100]
2 (50, 100]
3 (0, 50]
Name: maths, dtype: category
Categories (2, interval[int64, right]): [(0, 50] < (50, 100]]Ou, mieux, donner des étiquettes explicites plutôt que des intervalles bruts :
df['resultat'] = pd.cut(df['maths'], bins=[0, 50, 100], labels=['Échec', 'Réussite'])
print(df[['ID', 'maths', 'resultat']].head(5))
print()
print(df['resultat'].value_counts())Python ID maths resultat
0 A001 35 Échec
1 B014 72 Réussite
2 C022 58 Réussite
3 D031 35 Échec
4 E047 90 Réussite
resultat
Réussite 6
Échec 4
Name: count, dtype: int64Deux pièges à connaître : les intervalles sont ouverts à gauche et fermés à droite, donc la valeur exactement égale à la borne inférieure (0 ici) n'est dans aucun intervalle ; et les valeurs hors des bornes (101) donnent NaN. Ajoute include_lowest=True pour inclure la borne inférieure :
notes = pd.Series([0, 25, 50, 75, 101])
print(pd.cut(notes, bins=[0, 50, 100], labels=['A', 'B']).tolist())
print(pd.cut(notes, bins=[0, 50, 100], labels=['A', 'B'], include_lowest=True).tolist())Python[nan, 'A', 'A', 'B', nan]
['A', 'A', 'A', 'B', nan]Questions fréquentes sur les astuces Pandas
iterrows() ou itertuples() : lequel utiliser ?
itertuples(). Dans mon test sur 200 000 lignes, il est plus de 40 fois plus rapide que iterrows(). Mais la meilleure option reste l'opération vectorisée (sum(), max(axis=1), comparaisons…), qui évite toute boucle.
nlargest() est-il plus rapide que sort_values().head() ?
Oui. Comme il n'a pas besoin de trier toute la colonne, nlargest() est de 3 à 5 fois plus rapide dans mes mesures sur un million de lignes, avec Pandas 2.3 et 3.0.
df2 = df1 crée-t-il une copie d'un DataFrame ?
Non. Cette instruction donne un deuxième nom au même objet : modifier df2 modifie aussi df1. Pour obtenir une copie indépendante, utilise df1.copy().
Comment réduire la mémoire d'un gros DataFrame ?
Lis seulement les colonnes utiles avec usecols, choisis des types plus légers avec dtype (int32, float32) et convertis les colonnes de texte à peu de valeurs distinctes en category. Dans mon exemple de 200 000 lignes, la mémoire passe de 17,5 Mo à 1,8 Mo.
Quelle différence entre cut() et qcut() ?
cut() découpe selon des bornes de valeurs (par exemple 0-50 et 50-100). qcut() découpe selon des quantiles, pour obtenir des groupes d'effectifs voulus (par exemple les 5 % meilleurs).
Conclusion
Tu as maintenant 22 astuces Pandas testées pour lire, filtrer, regrouper et exporter tes données plus efficacement. Pour aller plus loin, tu peux lire mon article sur les 12 techniques de manipulation de données avec Pandas.
Connaître ces astuces, c'est bien ; les avoir dans les doigts, c'est mieux. Sur DataXp, le parcours « Analyse et visualisation des données » te fait manipuler des DataFrames sur des exercices guidés, avec un coach IA qui t'aide quand tu bloques. Les premiers modules sont gratuits.
Pour aller plus loin avec Pandas
- 7 techniques d'optimisation de la mémoire avec Pandas : gagner en mémoire
- Chargement de données efficace avec Pandas : lire plus vite
- GroupBy de Pandas en 10 étapes : maîtriser l'agrégation
- Le guide du traitement des données manquantes : valeurs manquantes
- 10 erreurs à éviter avec Pandas : performance
- 8 façons de filtrer un DataFrame : filtrage
- Pivot de Pandas : remodeler les données
- 25 extraits de code Python de référence : les bases du langage












