12 techniques utiles de manipulation de données avec la librairie Pandas

couverture 12 techniques pandas

Python est le langage de prédilection des spécialistes des données, et ce pour de bonnes raisons. Il offre à la fois l’écosystème le plus vaste pour un langage de programmation et d’excellentes bibliothèques de calcul scientifique.

Si tu ne connais pas encore Python, commence par le parcours Python de DataXp, avec des exercices corrigés et un coach IA.

Parmi ces bibliothèques, Pandas est la plus utile pour les opérations de data science. Pandas associé à Scikit-learn, c’est le Graal.

Cet article présente 12 techniques de manipulation de données avec Pandas, avec quelques conseils et astuces pour travailler plus rapidement.

Pour t’aider à mieux comprendre, j’ai choisi un dataset sur lequel effectuer ces opérations : le problème de prédiction de prêt (Loan Prediction). Le fichier train.csv (614 lignes) est chargé directement depuis le dépôt GitHub de MonCoachData : tu n’as rien à télécharger.

En bref : ces 12 techniques Pandas couvrent l’essentiel de la manipulation de données : indexation booléenne, apply, imputation avec fillna(), pivot_table, multi-index, crosstab, merge, tri, graphiques directs, cut, codage des variables nominales et typage des colonnes. Elles sont appliquées au jeu de données Loan Prediction : par exemple, 80 % des personnes avec des antécédents de crédit obtiennent un prêt, contre 8 % des autres.

Tu peux aussi exécuter ces 12 techniques dans ce notebook Google Colab : chaque extrait de code est dans sa propre cellule, prêt à être modifié.

Comment charger le dataset avec Pandas ?

Je commence par importer les modules et charger le dataset dans l’environnement Python :

import pandas as pd
import numpy as np

URL = "https://raw.githubusercontent.com/moncoachdata/blog-data-science/main/manipulation-de-donnees-avec-pandas/train.csv"

data = pd.read_csv(URL, index_col="Loan_ID")
Python

# 1 – Comment filtrer des lignes avec l’indexation booléenne ?

Pour filtrer des lignes selon des conditions sur plusieurs colonnes, utilise l’indexation booléenne : chaque condition entre parenthèses, combinées avec l’opérateur &. Que fais-tu si tu souhaites filtrer les valeurs d’une ou plusieurs colonnes en fonction de conditions portant sur d’autres colonnes ?

Par exemple, imaginons que l’on veuille la liste de toutes les femmes non diplômées qui ont obtenu un prêt. L’indexation booléenne est faite pour ça : chaque condition est placée entre parenthèses, et les conditions sont combinées avec l’opérateur &.

Tu peux utiliser le code suivant :

data.loc[(data["Gender"]=="Female") & (data["Education"]=="Not Graduate") & (data["Loan_Status"]=="Y"), ["Gender","Education","Loan_Status"]]
Python
          Gender     Education Loan_Status
Loan_ID
LP001155  Female  Not Graduate           Y
LP001669  Female  Not Graduate           Y
LP001692  Female  Not Graduate           Y
LP001908  Female  Not Graduate           Y
LP002300  Female  Not Graduate           Y
LP002314  Female  Not Graduate           Y
LP002407  Female  Not Graduate           Y
LP002489  Female  Not Graduate           Y
LP002502  Female  Not Graduate           Y
LP002534  Female  Not Graduate           Y
LP002582  Female  Not Graduate           Y
LP002731  Female  Not Graduate           Y
LP002757  Female  Not Graduate           Y
LP002917  Female  Not Graduate           Y

(Pour aller plus loin : documentation Indexing and Selecting Data)

# 2 – À quoi sert la fonction apply ?

C’est l’une des principales fonctions pour manipuler les données et créer de nouvelles variables. apply applique une fonction à chaque ligne ou colonne d’un DataFrame et renvoie le résultat. Cette fonction peut être une fonction existante ou une fonction définie par l’utilisateur.

Par exemple, ici, apply permet de compter les valeurs manquantes de chaque colonne, puis de chaque ligne :

# Créer une nouvelle fonction qui détermine si la valeur en paramètre est manquante:
def num_missing(x):
    return sum(x.isnull())

# On applique cette fonction pour chaque colonne:
print("Valeurs manquantes par colonne:")
print(data.apply(num_missing, axis=0)) #axis=0 définit que la fonction sera bien appliquée sur chaque colonne

# Puis application pour chaque ligne:
print("\nValeurs manquantes par ligne:")
print(data.apply(num_missing, axis=1).head()) #axis=1 définit que la fonction sera bien appliquée sur chaque ligne
Python
Valeurs manquantes par colonne:
Gender               13
Married               3
Dependents           15
Education             0
Self_Employed        32
ApplicantIncome       0
CoapplicantIncome     0
LoanAmount           22
Loan_Amount_Term     14
Credit_History       50
Property_Area         0
Loan_Status           0
dtype: int64

Valeurs manquantes par ligne:
Loan_ID
LP001002    1
LP001003    0
LP001005    0
LP001006    0
LP001008    0
dtype: int64

On obtient bien le résultat souhaité.

Remarque : on utilise la méthode head() pour la deuxième application, car le résultat contenait beaucoup trop de lignes.

(Pour aller plus loin : documentation apply de Pandas)

# 3 – Comment imputer les valeurs manquantes avec fillna() ?

La méthode fillna() le fait en une ligne. On l’utilise pour remplacer les valeurs manquantes par la moyenne (mean), le mode (mode) ou la médiane (median) de la colonne.

Imputons les colonnes Gender (sexe), Married (marié) et Self_Employed (à son compte) avec leur mode respectif. Commençons par calculer le mode de la colonne Gender :

# Calculer le mode de la colonne
data['Gender'].mode()
Python
0    Male
Name: Gender, dtype: str
Gender                0
Married               0
Dependents           15
Education             0
Self_Employed         0
ApplicantIncome       0
CoapplicantIncome     0
LoanAmount           22
Loan_Amount_Term     14
Credit_History       50
Property_Area         0
Loan_Status           0
dtype: int64

Les valeurs manquantes ont bien été imputées. Note qu’il s’agit de la forme d’imputation la plus simple. Des techniques plus sophistiquées existent, comme la modélisation des valeurs manquantes ou l’utilisation de moyennes groupées (moyenne, mode ou médiane par groupe).

Remarque : on réassigne ici la colonne plutôt que d’utiliser inplace=True. Avec les versions récentes de Pandas (copy-on-write), data['Gender'].fillna(..., inplace=True) ne modifie plus le DataFrame d’origine.

(Pour aller plus loin : documentation fillna() de Pandas & documentation mode() de Pandas)

# 4 – Comment créer une table pivot avec pivot_table ?

Pandas permet de créer des tableaux croisés dynamiques, comme dans Microsoft Excel. Ici, la colonne clé est LoanAmount, qui contient des valeurs manquantes. On peut l’imputer avec le montant moyen de chaque groupe défini par Gender, Married et Self_Employed.

La moyenne de LoanAmount pour chaque groupe se calcule ainsi, avec pivot_table :

# Tableau croisé dynamique avec pivot_table
impute_grps = data.pivot_table(values=["LoanAmount"], index=["Gender","Married","Self_Employed"], aggfunc="mean")
print(impute_grps)
Python
                              LoanAmount
Gender Married Self_Employed
Female No      No             114.691176
               Yes            125.800000
       Yes     No             134.222222
               Yes            282.250000
Male   No      No             129.936937
               Yes            180.588235
       Yes     No             153.882736
               Yes            169.395833

(Pour aller plus loin : documentation pivot_table de Pandas)

# 5 – Qu’est-ce qu’un multi-index et comment l’utiliser ?

Si on regarde le résultat de la technique #4, on remarque une propriété étrange : chaque index est composé d’une combinaison de 3 valeurs. C’est ce qu’on appelle un multi-index. Il permet d’effectuer certaines opérations très rapidement.

En reprenant l’exemple du #4, on a la moyenne de chaque groupe, mais les valeurs manquantes n’ont pas encore été imputées. On peut le faire avec les techniques vues jusqu’ici :

# Itérer seulement à travers les lignes avec des valeurs manquantes sur la colonne LoanAmount
for i,row in data.loc[data['LoanAmount'].isnull(),:].iterrows():
    ind = tuple([row['Gender'],row['Married'],row['Self_Employed']])
    data.loc[i,'LoanAmount'] = impute_grps.loc[ind].values[0]

# On vérifie les valeurs manquantes:
print(data.apply(num_missing, axis=0))
Python
Gender                0
Married               0
Dependents           15
Education             0
Self_Employed         0
ApplicantIncome       0
CoapplicantIncome     0
LoanAmount            0
Loan_Amount_Term     14
Credit_History       50
Property_Area         0
Loan_Status           0
dtype: int64

Remarques :

  • Avec un multi-index, loc attend un tuple pour désigner un groupe d’index.
  • Le suffixe .values[0] est nécessaire : sans lui, loc renvoie une Series dont l’index ne correspond pas à celui du DataFrame, et l’affectation directe génère une erreur.

# 6 – Comment croiser deux variables avec crosstab ?

La fonction crosstab donne un premier aperçu des données et permet de valider une hypothèse de base.

Ici, on suppose que la colonne Credit_History influence fortement le statut du prêt (Loan_Status). On peut le tester avec un tableau croisé :

pd.crosstab(data["Credit_History"],data["Loan_Status"],margins=True)
Python
Loan_Status       N    Y  All
Credit_History
0.0              82    7   89
1.0              97  378  475
All             179  385  564

Ce sont des effectifs absolus, alors que les pourcentages sont plus intuitifs pour une analyse rapide.

On les obtient avec la fonction apply, en divisant chaque ligne par son total (la dernière valeur, dans la colonne All) :

def percConvert(ser):
    return ser/float(ser.iloc[-1])

pd.crosstab(data["Credit_History"],data["Loan_Status"],margins=True).apply(percConvert, axis=1)
Python
Loan_Status            N         Y  All
Credit_History
0.0             0.921348  0.078652  1.0
1.0             0.204211  0.795789  1.0
All             0.317376  0.682624  1.0

Il est maintenant évident que les personnes ayant des antécédents de crédit ont beaucoup plus de chances d’obtenir un prêt : 80 % d’entre elles en ont obtenu un, contre seulement 8 % de celles qui n’en ont pas.

Mais ce n’est pas tout : cela raconte aussi une histoire intéressante.

Puisque les antécédents de crédit sont très importants, imaginons que l’on prédise Y pour ceux qui en ont, et N pour les autres. Étonnamment, cette règle simple est correcte 82 + 378 = 460 fois sur 614, soit 75 % !

Crois-moi, gagner ne serait-ce que quelques dixièmes de point de précision au-delà de ce score est déjà une tâche ardue.

Remarque : ces 75 % sont calculés sur le jeu d’entraînement (train). Le jeu de test sera légèrement différent, mais proche.

Cela explique aussi pourquoi un gain de précision infime peut faire gagner des centaines de places au classement d’une compétition Kaggle.

(Pour aller plus loin : documentation crosstab Pandas)

# 7 – Comment fusionner des DataFrames avec merge ?

On fusionne deux DataFrames avec la méthode merge. La fusion de DataFrames devient essentielle lorsqu’il faut rassembler des informations provenant de différentes sources.

Prenons un cas hypothétique où l’on dispose du prix moyen au mètre carré pour chaque type de zone (Property_Area). Définissons un DataFrame :

prop_rates = pd.DataFrame([1000, 5000, 12000], index=['Rural','Semiurban','Urban'],columns=['rates'])

prop_rates
Python
           rates
Rural       1000
Semiurban   5000
Urban      12000

On peut maintenant fusionner ces informations avec le DataFrame d’origine, avec la méthode merge :

data_merged = data.merge(right=prop_rates, how='inner',left_on='Property_Area',right_index=True, sort=False)

data_merged.pivot_table(values='Credit_History',index=['Property_Area','rates'], aggfunc=len)
Python
                     Credit_History
Property_Area rates
Rural         1000              179
Semiurban     5000              233
Urban         12000             202

Le tableau croisé dynamique confirme que la fusion a réussi. Note que l’argument values n’est pas pertinent ici, car on compte simplement les lignes.

(Pour aller plus loin : documentation merge Pandas)

# 8 – Comment trier un DataFrame selon plusieurs colonnes ?

Pandas permet de trier facilement un DataFrame selon plusieurs colonnes. Voici comment faire :

data_sorted = data.sort_values(['ApplicantIncome','CoapplicantIncome'], ascending=False)

data_sorted[['ApplicantIncome','CoapplicantIncome']].head(10)
Python
          ApplicantIncome  CoapplicantIncome
Loan_ID
LP002317            81000                0.0
LP002101            63337                0.0
LP001585            51763                0.0
LP001536            39999                0.0
LP001640            39147             4750.0
LP002422            37719                0.0
LP001637            33846                0.0
LP001448            23803                0.0
LP002624            20833             6667.0
LP001922            20667                0.0

(Pour aller plus loin : documentation sort_values Pandas)

# 9 – Comment tracer des boîtes à moustaches et des histogrammes avec Pandas ?

Tu ignores peut-être que les boîtes à moustaches et les histogrammes peuvent se tracer directement avec Pandas, sans appeler Matplotlib séparément (il doit simplement être installé). Une seule ligne de code suffit.

Par exemple, pour comparer la distribution de ApplicantIncome selon les valeurs de Loan_Status :

data.boxplot(column="ApplicantIncome",by="Loan_Status")
Python
Boîtes à moustaches du revenu du demandeur (ApplicantIncome) selon le statut du prêt (Loan_Status) : les deux groupes ont des distributions très proches, avec de nombreuses valeurs extrêmes
data.hist(column="ApplicantIncome",by="Loan_Status",bins=30)
Python
Histogrammes du revenu du demandeur (ApplicantIncome) en 30 intervalles, pour les prêts refusés (N) et accordés (Y) : la distribution est fortement asymétrique dans les deux cas

Cela montre que le revenu n’est pas un facteur décisif en soi : il n’y a pas de différence appréciable entre les personnes qui ont obtenu le prêt et celles à qui il a été refusé.

(Pour aller plus loin : documentation hist Pandas & documentation boxplot Pandas)

# 10 – Comment regrouper des valeurs numériques avec cut ?

La fonction pd.cut regroupe des valeurs numériques en intervalles nommés. Parfois, les valeurs numériques ont plus de sens une fois regroupées.

Par exemple, pour modéliser le trafic (nombre de voitures sur la route) en fonction de l’heure de la journée (en minutes), la minute exacte est peu utile. La période de la journée (« matin », « après-midi », « soirée », « nuit », « nuit tardive ») est bien plus parlante.

Modéliser le trafic de cette manière est plus intuitif et évite le sur-ajustement (overfitting).

On définit ici une fonction simple, réutilisable pour regrouper facilement n’importe quelle variable avec pd.cut :

# Regroupement (binning) :
def binning(col, cut_points, labels=None):
    # Définir les valeurs min et max:
    minval = col.min()
    maxval = col.max()

    # Créer une liste en ajoutant min et max à cut_points
    break_points = [minval] + cut_points + [maxval]

    # Si aucun label, on utilise par défaut les labels 0 ... (n-1)
    if not labels:
        labels = range(len(cut_points)+1)

    # Regroupement en utilisant la fonction cut de Pandas
    colBin = pd.cut(col,bins=break_points,labels=labels,include_lowest=True)
    return colBin


cut_points = [90,140,190]
labels = ["low","medium","high","very high"]
data["LoanAmount_Bin"] = binning(data["LoanAmount"], cut_points, labels)
print(data["LoanAmount_Bin"].value_counts(sort=False))
Python
LoanAmount_Bin
low          104
medium       273
high         146
very high     91
Name: count, dtype: int64

(Pour aller plus loin : documentation cut Pandas)

# 11 – Comment coder des données nominales avec replace ?

Il arrive souvent qu’il faille modifier les catégories d’une variable nominale, pour plusieurs raisons :

  1. Certains algorithmes (comme la régression logistique) exigent des entrées numériques : les variables nominales sont donc généralement codées 0, 1… (n-1).
  2. Une même catégorie peut être représentée de deux façons. Par exemple, une température peut être enregistrée comme « High », « Medium », « Low », « H », « low ». Ici, « High » et « H » désignent la même catégorie, et « Low » et « low » ne diffèrent que par la casse, mais Python les lit comme des niveaux différents.
  3. Certaines catégories ont des fréquences très basses : il est généralement préférable de les regrouper.

J’ai défini une fonction générique qui prend un dictionnaire en entrée et code les valeurs avec la méthode replace de Pandas :

# Définit une fonction générique en utilisant la fonction replace de Pandas
def coding(col, codeDict):
    colCoded = pd.Series(col, copy=True)
    for key, value in codeDict.items():
        colCoded = colCoded.replace(key, value)
    return colCoded

# Coder LoanStatus avec Y=1 et N=0:
print('Avant codage:')
print(data["Loan_Status"].value_counts())
data["Loan_Status_Coded"] = coding(data["Loan_Status"], {'N':0,'Y':1})
print('\nAprès codage:')
print(data["Loan_Status_Coded"].value_counts())
Python
Avant codage:
Loan_Status
Y    422
N    192
Name: count, dtype: int64

Après codage:
Loan_Status_Coded
1    422
0    192
Name: count, dtype: int64

Les effectifs sont identiques avant et après le codage : notre codage est correct.

(Pour aller plus loin : documentation replace Pandas)

# 12 – Comment parcourir les lignes d’un DataFrame et corriger les types ?

Ce n’est pas une opération très fréquente, mais mieux vaut savoir la faire pour ne pas rester coincé. Il peut parfois être nécessaire de parcourir toutes les lignes avec une boucle for.

Un problème courant est par exemple le mauvais typage des variables en Python. Il se produit généralement lorsque :

  1. les variables nominales aux catégories numériques sont traitées comme des nombres ;
  2. les variables numériques contenant des caractères dans l’une des lignes (à cause d’une erreur dans les données) sont considérées comme catégorielles.

Il est donc conseillé de définir manuellement les types des colonnes.

Commençons par vérifier les types de toutes les colonnes :

# Vérifier les types de données:
data.dtypes
Python
Gender                    str
Married                   str
Dependents                str
Education                 str
Self_Employed             str
ApplicantIncome         int64
CoapplicantIncome     float64
LoanAmount            float64
Loan_Amount_Term      float64
Credit_History        float64
Property_Area             str
Loan_Status               str
LoanAmount_Bin       category
Loan_Status_Coded      object
dtype: object

On voit ici que Credit_History est une variable nominale, mais elle apparaît avec le type float (nombre à virgule).

Une bonne solution consiste à créer un fichier CSV listant les noms et les types des colonnes. On peut ensuite écrire une fonction générique qui lit ce fichier et affecte les types. Commençons par créer le fichier datatypes.csv : une ligne par colonne, avec son type.

# Création du fichier datatypes.csv : une ligne par colonne, avec son type
types = """feature,type
Loan_ID,ordinal
Gender,categorical
Married,categorical
Dependents,ordinal
Education,categorical
Self_Employed,categorical
ApplicantIncome,continuous
CoapplicantIncome,continuous
LoanAmount,continuous
Loan_Amount_Term,continuous
Credit_History,categorical
Property_Area,categorical
Loan_Status,categorical
"""

with open("datatypes.csv", "w") as f:
    f.write(types)
Python

On le relit ensuite avec Pandas :

# Lecture du fichier:
colTypes = pd.read_csv('datatypes.csv')
print(colTypes)
Python
              feature         type
0             Loan_ID      ordinal
1              Gender  categorical
2             Married  categorical
3          Dependents      ordinal
4           Education  categorical
5       Self_Employed  categorical
6     ApplicantIncome   continuous
7   CoapplicantIncome   continuous
8          LoanAmount   continuous
9    Loan_Amount_Term   continuous
10     Credit_History  categorical
11      Property_Area  categorical
12        Loan_Status  categorical

Une fois le fichier chargé, on parcourt chaque ligne et on attribue le type indiqué dans la colonne type à la variable nommée dans la colonne feature :

# Itérer à travers chaque ligne et assigner le type de variable
# Remarque: astype est utilisé pour assigner des types

for i, row in colTypes.iterrows():  # i: dataframe index; row: chaque ligne (row) au format series
    if row['type']=="categorical":
        data[row['feature']]=data[row['feature']].astype(object)
    elif row['type']=="continuous":
        data[row['feature']]=data[row['feature']].astype(float)
print(data.dtypes)
Python
Gender                 object
Married                object
Dependents                str
Education              object
Self_Employed          object
ApplicantIncome       float64
CoapplicantIncome     float64
LoanAmount            float64
Loan_Amount_Term      float64
Credit_History         object
Property_Area          object
Loan_Status            object
LoanAmount_Bin       category
Loan_Status_Coded      object
dtype: object

La colonne Credit_History est maintenant de type object, le type utilisé par Pandas pour représenter les variables nominales.

Remarque : avec Pandas 3, les colonnes de texte sont affichées avec le type str et non plus object. Les résultats de cet article ont été obtenus avec Pandas 3.0 ; avec une version plus ancienne, tu verras object à la place de str.

(Pour aller plus loin : documentation iterrows Pandas)

Questions fréquentes sur la manipulation de données avec Pandas

Comment remplacer les valeurs manquantes d’une colonne avec Pandas ?

Avec la méthode fillna(), qui remplace les valeurs manquantes par la moyenne, le mode ou la médiane de la colonne. Pour une variable nominale, calcule le mode avec mode() et prends son premier élément, car mode() renvoie une Series.

Quelle est la différence entre pivot_table et crosstab ?

pivot_table calcule une statistique (ici la moyenne de LoanAmount) pour chaque groupe défini par plusieurs colonnes. crosstab compte les effectifs croisés de deux variables et permet de tester rapidement une hypothèse, comme l’effet de Credit_History sur Loan_Status.

Comment fusionner deux DataFrames avec Pandas ?

Avec la méthode merge. Dans l’exemple de l’article, on fusionne le prix moyen au mètre carré de chaque type de zone (Property_Area) avec le DataFrame d’origine.

Comment regrouper des valeurs numériques en catégories avec Pandas ?

Avec pd.cut, qui découpe une variable numérique en intervalles. Par exemple, l’heure de la journée en minutes devient « matin », « après-midi », « soirée », « nuit » ou « nuit tardive ». C’est plus intuitif et cela évite le sur-ajustement.

Pourquoi définir manuellement le type des colonnes d’un DataFrame ?

Parce que Pandas se trompe parfois : une variable nominale à catégories numériques est lue comme un nombre, et une variable numérique contenant un caractère par erreur est lue comme catégorielle. Dans l’article, Credit_History apparaît en float alors que c’est une variable nominale.

Conclusion

Dans cet article, nous avons vu diverses fonctions Pandas qui facilitent l’exploration des données et le traitement des variables. Nous avons aussi défini quelques fonctions génériques, réutilisables sur d’autres datasets.

Pour aller plus loin avec Pandas, consulte aussi notre guide des 10 astuces Pandas en Python. Pour progresser en Python, découvre aussi nos 21 astuces et concepts Python pour améliorer ton code.

Publications similaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *