Libérez tout le potentiel de Pandas en maîtrisant les fonctions personnalisées. Guide détaillé sur les différences, performances et cas d'utilisation d'apply(), map() et applymap.
Maîtriser Pandas : Exploration Approfondie des Fonctions Personnalisées avec apply(), map() et applymap()
Dans le monde de la science des données et de l'analyse, la bibliothèque Pandas de Python est un outil indispensable. Elle fournit des structures de données puissantes, flexibles et efficaces conçues pour rendre le travail avec des données structurées à la fois facile et intuitif. Bien que Pandas soit livré avec un ensemble riche de fonctions intégrées pour l'agrégation, le filtrage et la transformation, il arrive un moment dans le parcours de chaque professionnel des données où celles-ci ne suffisent pas. Vous devez appliquer votre propre logique personnalisée, une règle métier unique ou une transformation complexe qui n'est pas facilement disponible.
C'est là que la capacité d'appliquer des fonctions personnalisées devient une superpuissance. Cependant, Pandas offre plusieurs façons d'y parvenir, principalement via les méthodes apply(), map() et applymap(). Pour le nouveau venu, ces fonctions peuvent sembler déroutantes et similaires. Laquelle devriez-vous utiliser ? Quand ? Et quelles sont les implications en termes de performances de votre choix ?
Ce guide complet démystifiera ces méthodes puissantes. Nous explorerons chacune d'elles en détail, comprendrons leurs cas d'utilisation spécifiques et, plus important encore, apprendrons à choisir le bon outil pour faire le travail afin d'écrire du code Pandas propre, efficace et lisible. Nous aborderons :
- La méthode
map(): Idéale pour la transformation élément par élément sur une seule Série. - La méthode
apply(): L'outil polyvalent pour les opérations ligne par ligne ou colonne par colonne sur un DataFrame. - La méthode
applymap(): Le spécialiste des opérations élément par élément sur l'ensemble d'un DataFrame. - Considérations relatives aux performances : La différence essentielle entre ces méthodes et la véritable vectorisation.
- Meilleures pratiques : Un cadre de prise de décision pour vous aider à choisir la méthode la plus efficace à chaque fois.
Préparer le terrain : notre exemple d'ensemble de données
Pour rendre nos exemples pratiques et clairs, travaillons avec un ensemble de données cohérent et pertinent à l'échelle mondiale. Nous allons créer un exemple de DataFrame représentant les données de ventes en ligne d'une entreprise de commerce électronique internationale fictive.
import pandas as pd
import numpy as np
data = {
'OrderID': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
'Product': ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Webcam', 'Headphones', 'Docking Station', 'Mouse'],
'Category': ['Electronics', 'Accessories', 'Accessories', 'Electronics', 'Accessories', 'Audio', 'Electronics', 'Accessories'],
'Price_USD': [1200, 25, 75, 300, 50, 150, 250, 30],
'Quantity': [1, 2, 1, 2, 1, 1, 1, 3],
'Country': ['USA', 'Canada', 'USA', 'Germany', 'Japan', 'Canada', 'Germany', np.nan]
}
df = pd.DataFrame(data)
print(df)
Ce DataFrame nous donne un bon mélange de types de données (numérique, chaîne et même une valeur manquante) pour démontrer toutes les capacités de nos fonctions cibles.
La méthode `map()` : Transformation élément par élément pour une série
Qu'est-ce que `map()` ?
La méthode map() est votre outil spécialisé pour modifier les valeurs dans une seule colonne (un Series Pandas). Elle fonctionne élément par élément. Considérez-la comme disant : « Pour chaque élément de cette colonne, recherchez-le dans un dictionnaire ou transmettez-le via cette fonction et remplacez-le par le résultat. »
Elle est principalement utilisée pour deux tâches :
- Remplacer des valeurs en fonction d'un dictionnaire (un mappage).
- Appliquer une fonction simple à chaque élément.
Cas d'utilisation 1 : Mappage de valeurs avec un dictionnaire
Il s'agit de l'utilisation la plus courante et la plus efficace de map(). Imaginez que nous voulions créer une colonne « Département » plus large basée sur notre colonne « Catégorie ». Nous pouvons définir un mappage dans un dictionnaire Python et utiliser map() pour l'appliquer.
category_to_department = {
'Electronics': 'Technology',
'Accessories': 'Peripherals',
'Audio': 'Technology'
}
df['Department'] = df['Category'].map(category_to_department)
print(df[['Category', 'Department']])
Sortie :
Category Department
0 Electronics Technology
1 Accessories Peripherals
2 Accessories Peripherals
3 Electronics Technology
4 Accessories Peripherals
5 Audio Technology
6 Electronics Technology
7 Accessories Peripherals
Remarquez comme cela fonctionne avec élégance. Chaque valeur de la série « Catégorie » est recherchée dans le dictionnaire `category_to_department`, et la valeur correspondante est utilisée pour remplir la nouvelle colonne « Département ». Si une clé est introuvable dans le dictionnaire, map() produira une valeur NaN (pas un nombre), ce qui est souvent le comportement souhaité pour les catégories non mappées.
Cas d'utilisation 2 : Application d'une fonction avec `map()`
Vous pouvez également transmettre une fonction (y compris une fonction lambda) à map(). La fonction sera exécutée pour chaque élément de la série. Créons une nouvelle colonne qui nous donne une étiquette descriptive pour le prix.
def price_label(price):
if price > 200:
return 'High-Value'
elif price > 50:
return 'Mid-Value'
else:
return 'Low-Value'
df['Price_Label'] = df['Price_USD'].map(price_label)
# Utilisation d'une fonction lambda pour une tâche plus simple :
# df['Product_Length'] = df['Product'].map(lambda x: len(x))
print(df[['Product', 'Price_USD', 'Price_Label']])
Sortie :
Product Price_USD Price_Label
0 Laptop 1200 High-Value
1 Mouse 25 Low-Value
2 Keyboard 75 Mid-Value
3 Monitor 300 High-Value
4 Webcam 50 Low-Value
5 Headphones 150 Mid-Value
6 Docking Station 250 High-Value
7 Mouse 30 Low-Value
Quand utiliser `map()` : un bref résumé
- Vous travaillez sur une seule colonne (une série).
- Vous devez remplacer des valeurs en fonction d'un dictionnaire ou d'une autre série. C'est sa principale force.
- Vous devez appliquer une fonction simple élément par élément à une seule colonne.
La méthode `apply()` : L'outil polyvalent
Qu'est-ce que `apply()` ?
Si map() est un spécialiste, apply() est l'outil polyvalent. Il est plus flexible car il peut fonctionner à la fois sur les séries et les DataFrames. La clé pour comprendre apply() est le paramètre axis, qui dirige son fonctionnement :
- Sur une série : Il fonctionne élément par élément, un peu comme
map(). - Sur un DataFrame avec
axis=0(la valeur par défaut) : Il applique une fonction à chaque colonne. La fonction reçoit chaque colonne sous forme de série. - Sur un DataFrame avec
axis=1: Il applique une fonction à chaque ligne. La fonction reçoit chaque ligne sous forme de série.
`apply()` sur une série
Lorsqu'il est utilisé sur une série, apply() se comporte de manière très similaire à map(). Il applique une fonction à chaque élément. Par exemple, nous pourrions reproduire notre exemple d'étiquette de prix.
df['Price_Label_apply'] = df['Price_USD'].apply(price_label)
print(df['Price_Label_apply'].equals(df['Price_Label'])) # Sortie : True
Bien qu'ils semblent interchangeables ici, map() est souvent légèrement plus rapide pour les substitutions de dictionnaires simples et les opérations élément par élément sur une série, car il a un chemin plus optimisé pour ces tâches spécifiques.
`apply()` sur un DataFrame (colonne par colonne, `axis=0`)
Il s'agit du mode par défaut pour un DataFrame. La fonction que vous fournissez est appelée une fois pour chaque colonne. Ceci est utile pour les agrégations ou transformations colonne par colonne.
Trouvons la différence entre la valeur maximale et la valeur minimale (la plage) pour chacune de nos colonnes numériques.
numeric_cols = df[['Price_USD', 'Quantity']]
def get_range(column_series):
return column_series.max() - column_series.min()
column_ranges = numeric_cols.apply(get_range, axis=0)
print(column_ranges)
Sortie :
Price_USD 1175.0
Quantity 2.0
dtype: float64
Ici, la fonction get_range a d'abord reçu la série « Price_USD », a calculé sa plage, puis a reçu la série « Quantity » et a fait de même, renvoyant une nouvelle série avec les résultats.
`apply()` sur un DataFrame (ligne par ligne, `axis=1`)
C'est sans doute le cas d'utilisation le plus puissant et le plus courant pour apply(). Lorsque vous devez calculer une nouvelle valeur basée sur plusieurs colonnes de la même ligne, apply() avec axis=1 est votre solution de choix.
La fonction que vous transmettez recevra chaque ligne sous forme de série, où l'index est le nom des colonnes. Calculons le coût total de chaque commande.
def calculate_total_cost(row):
# 'row' est une série représentant une seule ligne
price = row['Price_USD']
quantity = row['Quantity']
return price * quantity
df['Total_Cost'] = df.apply(calculate_total_cost, axis=1)
print(df[['Product', 'Price_USD', 'Quantity', 'Total_Cost']])
Sortie :
Product Price_USD Quantity Total_Cost
0 Laptop 1200 1 1200
1 Mouse 25 2 50
2 Keyboard 75 1 75
3 Monitor 300 2 600
4 Webcam 50 1 50
5 Headphones 150 1 150
6 Docking Station 250 1 250
7 Mouse 30 3 90
C'est quelque chose que map() ne peut tout simplement pas faire, car il est limité à une seule colonne. Voyons un exemple plus complexe. Nous voulons catégoriser la priorité d'expédition de chaque commande en fonction de sa catégorie et de son pays.
def assign_shipping_priority(row):
if row['Category'] == 'Electronics' and row['Country'] == 'USA':
return 'High Priority'
elif row['Total_Cost'] > 500:
return 'High Priority'
elif row['Country'] == 'Japan':
return 'Medium Priority'
else:
return 'Standard'
df['Shipping_Priority'] = df.apply(assign_shipping_priority, axis=1)
print(df[['Category', 'Country', 'Total_Cost', 'Shipping_Priority']])
Quand utiliser `apply()` : un bref résumé
- Lorsque votre logique dépend de plusieurs colonnes dans une ligne (utilisez
axis=1). C'est sa fonctionnalité phare. - Lorsque vous devez appliquer une fonction d'agrégation sur les colonnes ou sur les lignes.
- En tant qu'outil d'application de fonction à usage général lorsque
map()ne convient pas.
Une mention spéciale : la méthode `applymap()`
Qu'est-ce que `applymap()` ?
La méthode applymap() est un autre spécialiste, mais son domaine est l'ensemble du DataFrame. Elle applique une fonction à chaque élément unique d'un DataFrame. Elle ne fonctionne pas sur une série : c'est une méthode réservée aux DataFrames.
Considérez-la comme l'exécution d'un map() sur chaque colonne simultanément. Elle est utile pour les transformations générales et globales, comme le formatage ou la conversion de type, dans toutes les cellules.
DataFrame.applymap() est en cours d'abandon. La nouvelle façon recommandée est d'utiliser DataFrame.map(). La fonctionnalité est la même. Nous utiliserons applymap() ici pour la compatibilité, mais soyez conscient de ce changement pour le code futur.
Un exemple pratique
Disons que nous avons un sous-DataFrame avec uniquement nos colonnes numériques et que nous voulons toutes les formater sous forme de chaînes de devise pour un rapport.
numeric_df = df[['Price_USD', 'Quantity', 'Total_Cost']]
# Utilisation d'une fonction lambda pour formater chaque nombre
formatted_df = numeric_df.applymap(lambda x: f'${x:,.2f}')
print(formatted_df)
Sortie :
Price_USD Quantity Total_Cost
0 $1,200.00 $1.00 $1,200.00
1 $25.00 $2.00 $50.00
2 $75.00 $1.00 $75.00
3 $300.00 $2.00 $600.00
4 $50.00 $1.00 $50.00
5 $150.00 $1.00 $150.00
6 $250.00 $1.00 $250.00
7 $30.00 $3.00 $90.00
Une autre utilisation courante consiste à nettoyer un DataFrame de données de chaîne en, par exemple, convertissant tout en minuscules.
string_df = df[['Product', 'Category', 'Country']].copy() # Créer une copie pour éviter l'avertissement SettingWithCopyWarning
# S'assurer que toutes les valeurs sont des chaînes pour éviter les erreurs
string_df = string_df.astype(str)
lower_df = string_df.applymap(str.lower)
print(lower_df)
Quand utiliser `applymap()` : un bref résumé
- Lorsque vous devez appliquer une seule fonction simple à chaque élément d'un DataFrame.
- Pour des tâches telles que la conversion de type de données, le formatage de chaîne ou les transformations mathématiques simples sur l'ensemble du DataFrame.
- N'oubliez pas son abandon au profit de
DataFrame.map()dans les versions récentes de Pandas.
Analyse approfondie des performances : Vectorisation vs. Itération
La boucle « cachée »
C'est le concept le plus important à saisir pour écrire du code Pandas haute performance. Bien que apply(), map() et applymap() soient pratiques, ce ne sont essentiellement que des enveloppes sophistiquées autour d'une boucle Python. Lorsque vous utilisez df.apply(..., axis=1), Pandas itère dans votre DataFrame ligne par ligne, transmettant chacune à votre fonction. Ce processus a des frais généraux importants et est beaucoup plus lent que les opérations qui sont optimisées en C ou Cython.
La puissance de la vectorisation
La vectorisation est la pratique consistant à effectuer des opérations sur des tableaux entiers (ou des séries) en même temps, plutôt que sur des éléments individuels. Pandas et sa bibliothèque sous-jacente, NumPy, sont spécialement conçus pour être incroyablement rapides lors des opérations vectorisées.
Revenons à notre calcul du « Total_Cost ». Nous avons utilisé apply(), mais existe-t-il une façon vectorisée ?
# Méthode 1 : Utilisation de apply() (Itération)
df['Total_Cost'] = df.apply(lambda row: row['Price_USD'] * row['Quantity'], axis=1)
# Méthode 2 : Opération vectorisée
df['Total_Cost_Vect'] = df['Price_USD'] * df['Quantity']
# Vérifier si les résultats sont les mêmes
print(df['Total_Cost'].equals(df['Total_Cost_Vect'])) # Sortie : True
La deuxième méthode est vectorisée. Elle prend l'ensemble de la série « Price_USD » et le multiplie par l'ensemble de la série « Quantity » en une seule opération hautement optimisée. Si vous deviez chronométrer ces deux méthodes sur un grand DataFrame (millions de lignes), l'approche vectorisée ne serait pas seulement plus rapide, elle serait plusieurs ordres de grandeur plus rapide. Nous parlons de secondes contre des minutes, ou de minutes contre des heures.
Quand `apply()` est-il inévitable ?
Si la vectorisation est tellement plus rapide, pourquoi ces autres méthodes existent-elles ? Parce que parfois, votre logique est trop complexe pour être vectorisée. apply() est l'outil nécessaire et correct lorsque :
- Logique conditionnelle complexe : Votre logique implique des instructions `if/elif/else` complexes qui dépendent de plusieurs colonnes, comme notre exemple `assign_shipping_priority`. Bien qu'une partie de cela puisse être réalisée avec `np.select()`, cela peut devenir illisible.
- Fonctions de bibliothèque externe : Vous devez appliquer une fonction d'une bibliothèque externe à vos données. Par exemple, appliquer une fonction d'une bibliothèque géospatiale pour calculer la distance en fonction des colonnes de latitude et de longitude, ou une fonction d'une bibliothèque de traitement du langage naturel (comme NLTK) pour effectuer une analyse des sentiments sur une colonne de texte.
- Processus itératifs : Le calcul pour une ligne donnée dépend d'une valeur calculée dans une ligne précédente (bien que cela soit rare et souvent un signe qu'une structure de données différente est nécessaire).
Meilleure pratique : Vectoriser d'abord, `apply()` ensuite
Cela conduit à la règle d'or des performances de Pandas :
Cherchez toujours d'abord une solution vectorisée. Utilisez `apply()` comme votre solution de repli puissante et flexible lorsqu'une solution vectorisée n'est pas pratique ou possible.
Résumé et points clés à retenir : Choisir le bon outil
Consolidons nos connaissances dans un cadre de prise de décision clair. Lorsque vous êtes confronté à une tâche de transformation personnalisée, posez-vous ces questions :
Tableau de comparaison
| Méthode | Fonctionne sur | Portée de l'opération | La fonction reçoit | Cas d'utilisation principal |
|---|---|---|---|---|
| Vectorisation | Série, DataFrame | Tableau entier à la fois | N/A (l'opération est directe) | Opérations arithmétiques, logiques. Performances les plus élevées. |
.map() |
Série uniquement | Élément par élément | Un seul élément | Remplacement de valeurs à partir d'un dictionnaire. |
.apply() |
Série, DataFrame | Ligne par ligne ou Colonne par colonne | Une série (une ligne ou une colonne) | Logique complexe utilisant plusieurs colonnes par ligne. |
.applymap() |
DataFrame uniquement | Élément par élément | Un seul élément | Formatage ou transformation de chaque cellule d'un DataFrame. |
Un organigramme de décision
- Mon opération peut-elle être exprimée à l'aide d'opérateurs arithmétiques de base (+, -, *, /) ou logiques (&, |, ~) sur des colonnes entières ?
→ Oui ? Utilisez une approche vectorisée. C'est la plus rapide. (par exemple, `df['col1'] * df['col2']`) - Je ne travaille que sur une seule colonne, et mon objectif principal est-il de remplacer des valeurs en fonction d'un dictionnaire ?
→ Oui ? UtilisezSeries.map(). Elle est optimisée pour cela. - Dois-je appliquer une fonction à chaque élément unique de l'ensemble de mon DataFrame ?
→ Oui ? UtilisezDataFrame.applymap()(ouDataFrame.map()dans les nouvelles versions de Pandas). - Ma logique est-elle complexe et nécessite-t-elle des valeurs de plusieurs colonnes dans chaque ligne pour calculer un seul résultat ?
→ Oui ? UtilisezDataFrame.apply(..., axis=1). C'est votre outil pour une logique complexe ligne par ligne.
Conclusion
Naviguer parmi les options pour appliquer des fonctions personnalisées dans Pandas est un rite de passage pour tout praticien des données. Bien qu'elles puissent sembler interchangeables au premier abord, map(), apply() et applymap() sont des outils distincts, chacun avec ses propres forces et cas d'utilisation idéaux. En comprenant leurs différences, vous pouvez écrire du code qui est non seulement correct, mais aussi plus lisible, maintenable et considérablement plus performant.
N'oubliez pas la hiérarchie : préférez la vectorisation pour sa vitesse brute, utilisez map() pour sa substitution de série efficace, choisissez applymap() pour les transformations à l'échelle du DataFrame et tirez parti de la puissance et de la flexibilité de apply() pour une logique complexe ligne par ligne ou colonne par colonne qui ne peut pas être vectorisée. Fort de ces connaissances, vous êtes maintenant mieux équipé pour relever tout défi de manipulation de données qui se présente à vous, transformant les données brutes en informations puissantes avec compétence et efficacité.