Skip to content
Thèmes
Pandas
Pandas Drop Duplicates: How to Remove Duplicate Rows in Python

Pandas Drop Duplicates : Comment supprimer les lignes en double en Python

Updated on

Les lignes en double sont l'un des problèmes de qualité de données les plus courants dans les jeux de données réels. Elles s'infiltrent par des appels API répétés, des exportations CSV qui se chevauchent, des pipelines ETL défaillants ou de simples erreurs de copier-coller lors de la saisie manuelle des données. Non contrôlés, les doublons gonflent le nombre de lignes, faussent les moyennes et les sommes, et introduisent des biais dans les modèles de machine learning. Un jeu de données qui semble contenir 10 000 enregistrements clients pourrait en réalité ne compter que 8 200 clients uniques et 1 800 entrées fantômes corrompant silencieusement chaque calcul construit dessus.

La méthode pandas drop_duplicates() est le moyen standard pour détecter et supprimer ces lignes redondantes. Ce guide parcourt chaque paramètre, montre les modèles courants pour la déduplication dans le monde réel et couvre les considérations de performance pour les grands jeux de données. Chaque exemple de code est prêt à copier et inclut sa sortie attendue.

📚

Pourquoi les doublons sont importants

Avant de plonger dans le code, il vaut la peine de comprendre exactement ce que les lignes en double cassent :

ProblèmeCe qui se passeExemple
Comptages gonfléslen(df) et value_counts() surcomptentUn client apparaît 3 fois, donc le "total clients" est 3x trop élevé
Moyennes erronéesmean() pondère davantage les lignes dupliquéesUne commande de grande valeur comptée deux fois biaise la valeur moyenne des commandes vers le haut
Jointures casséesLa fusion sur une clé avec des doublons provoque une explosion de lignesUne fusion many-to-many produit un produit cartésien au lieu d'un mapping 1:1
Mauvais modèles MLLes données d'entraînement avec des échantillons répétés biaisent le modèleLe modèle mémorise les exemples dupliqués et surajuste
Stockage gaspilléLes lignes redondantes consomment du disque et de la mémoireUn jeu de données de 2 Go pourrait faire 1,4 Go après déduplication

La solution est simple : trouver les doublons, décider quelle copie conserver (le cas échéant) et supprimer le reste.

Syntaxe de base : df.drop_duplicates()

L'appel le plus simple supprime les lignes où chaque valeur de colonne est identique :

import pandas as pd
 
df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'],
    'age': [30, 25, 30, 35, 25],
    'city': ['NYC', 'LA', 'NYC', 'Chicago', 'LA']
})
 
print("Before:")
print(df)
 
df_clean = df.drop_duplicates()
 
print("\nAfter:")
print(df_clean)

Sortie :

Before:
      name  age     city
0    Alice   30      NYC
1      Bob   25       LA
2    Alice   30      NYC
3  Charlie   35  Chicago
4      Bob   25       LA

After:
      name  age     city
0    Alice   30      NYC
1      Bob   25       LA
3  Charlie   35  Chicago

La ligne 2 et la ligne 4 étaient des copies exactes de la ligne 0 et de la ligne 1, elles ont donc été supprimées. Notez que les valeurs d'index originales (0, 1, 3) sont préservées. Si vous voulez un index séquentiel propre, enchaînez .reset_index(drop=True).

Signature complète de la méthode

DataFrame.drop_duplicates(subset=None, keep='first', inplace=False, ignore_index=False)
ParamètreTypePar défautDescription
subsetétiquette de colonne ou listeNone (toutes les colonnes)Ne considérer que ces colonnes lors de l'identification des doublons
keep'first', 'last', False'first'Quel doublon conserver ; False supprime toutes les copies
inplaceboolFalseSi True, modifie le DataFrame sur place et retourne None
ignore_indexboolFalseSi True, réinitialise l'index de 0 à n-1 dans le résultat

Trouver les doublons d'abord avec df.duplicated()

Avant de supprimer les doublons, vous voulez souvent les inspecter. La méthode duplicated() retourne une Series booléenne marquant les lignes en double :

df = pd.DataFrame({
    'order_id': [101, 102, 103, 101, 104, 102],
    'product': ['Widget', 'Gadget', 'Widget', 'Widget', 'Gizmo', 'Gadget'],
    'amount': [29.99, 49.99, 29.99, 29.99, 19.99, 49.99]
})
 
# Show which rows are duplicates
print(df.duplicated())

Sortie :

0    False
1    False
2    False
3     True
4    False
5     True
dtype: bool

Pour voir les lignes en double réelles :

duplicates = df[df.duplicated(keep=False)]
print(duplicates)

Sortie :

   order_id product  amount
0       101  Widget   29.99
1       102  Gadget   49.99
3       101  Widget   29.99
5       102  Gadget   49.99

L'utilisation de keep=False marque toutes les copies comme doublons (pas seulement la deuxième occurrence), ce qui vous permet de voir chaque ligne impliquée dans la duplication.

Compter les doublons

Pour obtenir un décompte rapide du nombre de lignes en double :

num_duplicates = df.duplicated().sum()
print(f"Number of duplicate rows: {num_duplicates}")
 
total_rows = len(df)
unique_rows = df.drop_duplicates().shape[0]
print(f"Total: {total_rows}, Unique: {unique_rows}, Duplicates: {total_rows - unique_rows}")

Sortie :

Number of duplicate rows: 2
Total: 6, Unique: 4, Duplicates: 2

C'est une vérification de bon sens utile avant et après le nettoyage.

Le paramètre subset : Vérifier uniquement des colonnes spécifiques

Souvent, vous voudrez dédupliquer sur la base d'une colonne clé plutôt que d'exiger que chaque colonne corresponde. Le paramètre subset vous permet de spécifier quelles colonnes déterminent l'unicité :

df = pd.DataFrame({
    'email': ['alice@mail.com', 'bob@mail.com', 'alice@mail.com', 'charlie@mail.com'],
    'name': ['Alice', 'Bob', 'Alice Smith', 'Charlie'],
    'signup_date': ['2025-01-01', '2025-01-02', '2025-01-15', '2025-01-03']
})
 
print("Original:")
print(df)
 
# Deduplicate by email only
df_deduped = df.drop_duplicates(subset=['email'])
print("\nDeduplicated by email:")
print(df_deduped)

Sortie :

Original:
              email       name signup_date
0    alice@mail.com      Alice  2025-01-01
1      bob@mail.com        Bob  2025-01-02
2    alice@mail.com  Alice Smith  2025-01-15
3  charlie@mail.com    Charlie  2025-01-03

Deduplicated by email:
              email     name signup_date
0    alice@mail.com    Alice  2025-01-01
1      bob@mail.com      Bob  2025-01-02
3  charlie@mail.com  Charlie  2025-01-03

La ligne 2 a été supprimée car alice@mail.com apparaissait déjà à la ligne 0, même si les valeurs name et signup_date différaient. Vous pouvez aussi passer plusieurs colonnes : subset=['email', 'name'] ne considérerait les lignes comme dupliquées que lorsque les deux colonnes correspondent.

Le paramètre keep : first, last ou False

Le paramètre keep contrôle quelle occurrence survit :

df = pd.DataFrame({
    'sensor_id': ['S1', 'S2', 'S1', 'S2', 'S1'],
    'reading': [22.5, 18.3, 23.1, 18.3, 24.0],
    'timestamp': ['08:00', '08:00', '09:00', '09:00', '10:00']
})
 
print("keep='first' (default):")
print(df.drop_duplicates(subset=['sensor_id'], keep='first'))
 
print("\nkeep='last':")
print(df.drop_duplicates(subset=['sensor_id'], keep='last'))
 
print("\nkeep=False (drop all duplicates):")
print(df.drop_duplicates(subset=['sensor_id'], keep=False))

Sortie :

keep='first' (default):
  sensor_id  reading timestamp
0        S1     22.5     08:00
1        S2     18.3     08:00

keep='last':
  sensor_id  reading timestamp
3        S2     18.3     09:00
4        S1     24.0     10:00

keep=False (drop all duplicates):
Empty DataFrame
Columns: [sensor_id, reading, timestamp]
Index: []
Valeur keepComportementCas d'utilisation
'first'Conserver la première occurrence, supprimer les suivantesConserver l'enregistrement le plus ancien
'last'Conserver la dernière occurrence, supprimer les précédentesConserver l'enregistrement le plus récent
FalseSupprimer toutes les lignes ayant un doublonTrouver les lignes véritablement uniques (aucune copie)

Dans l'exemple ci-dessus, keep=False retourne un DataFrame vide car les deux identifiants de capteur apparaissent plus d'une fois.

In-place vs. Retourner un nouveau DataFrame

Par défaut, drop_duplicates() retourne un nouveau DataFrame et laisse l'original inchangé. Définir inplace=True modifie l'original directement :

df = pd.DataFrame({
    'id': [1, 2, 2, 3],
    'value': ['a', 'b', 'b', 'c']
})
 
# Returns new DataFrame (original unchanged)
df_new = df.drop_duplicates()
print(f"Original length: {len(df)}, New length: {len(df_new)}")
 
# Modifies in place (returns None)
df.drop_duplicates(inplace=True)
print(f"After inplace: {len(df)}")

Sortie :

Original length: 4, New length: 3
After inplace: 3

Le style pandas moderne recommande d'éviter inplace=True et d'utiliser l'assignation à la place (df = df.drop_duplicates()). Cela rend le code plus facile à lire et à déboguer, surtout dans les opérations enchaînées.

Détection de doublons insensible à la casse

Par défaut, pandas traite "Alice" et "alice" comme des valeurs différentes. Pour une déduplication insensible à la casse, normalisez d'abord la colonne :

df = pd.DataFrame({
    'name': ['Alice', 'alice', 'ALICE', 'Bob', 'bob'],
    'score': [90, 85, 92, 78, 80]
})
 
# Create a normalized column for comparison
df['name_lower'] = df['name'].str.lower()
 
# Deduplicate on the normalized column, keep the first original-case entry
df_deduped = df.drop_duplicates(subset=['name_lower']).drop(columns=['name_lower'])
print(df_deduped)

Sortie :

    name  score
0  Alice     90
3    Bob     78

Ce pattern préserve la casse originale tout en identifiant correctement les doublons insensibles à la casse.

Exemple concret : Nettoyage d'une base de données clients

Voici un scénario réaliste. Vous recevez un export client d'un CRM où le même client a été saisi plusieurs fois par différents commerciaux :

import pandas as pd
 
customers = pd.DataFrame({
    'customer_id': [1001, 1002, 1001, 1003, 1002, 1004],
    'name': ['Acme Corp', 'Beta Inc', 'Acme Corp', 'Gamma LLC', 'Beta Inc.', 'Delta Co'],
    'email': ['acme@mail.com', 'beta@mail.com', 'acme@mail.com', 'gamma@mail.com', 'beta@mail.com', 'delta@mail.com'],
    'revenue': [50000, 30000, 52000, 45000, 30000, 20000],
    'last_contact': ['2025-12-01', '2025-11-15', '2026-01-10', '2025-10-20', '2025-11-15', '2026-01-05']
})
 
print(f"Rows before cleaning: {len(customers)}")
print(f"Duplicate customer_ids: {customers.duplicated(subset=['customer_id']).sum()}")
 
# Sort by last_contact descending so the most recent entry is first
customers['last_contact'] = pd.to_datetime(customers['last_contact'])
customers = customers.sort_values('last_contact', ascending=False)
 
# Keep the most recent record for each customer_id
customers_clean = customers.drop_duplicates(subset=['customer_id'], keep='first')
customers_clean = customers_clean.sort_values('customer_id').reset_index(drop=True)
 
print(f"\nRows after cleaning: {len(customers_clean)}")
print(customers_clean)

Sortie :

Rows before cleaning: 6
Duplicate customer_ids: 2

Rows after cleaning: 4
   customer_id       name           email  revenue last_contact
0         1001  Acme Corp   acme@mail.com    52000   2026-01-10
1         1002   Beta Inc  beta@mail.com    30000   2025-11-15
2         1003  Gamma LLC  gamma@mail.com    45000   2025-10-20
3         1004   Delta Co  delta@mail.com    20000   2026-01-05

Le pattern clé ici est trier d'abord, puis dédupliquer avec keep='first'. En triant par last_contact en ordre décroissant avant la déduplication, l'enregistrement le plus récent pour chaque client survit.

Exemple concret : Déduplication de résultats de web scraping

Les web scrapers produisent couramment des doublons lorsque les pages sont crawlées plusieurs fois ou que la pagination se chevauche :

import pandas as pd
 
scraped = pd.DataFrame({
    'url': [
        'https://shop.com/item/101',
        'https://shop.com/item/102',
        'https://shop.com/item/101',
        'https://shop.com/item/103',
        'https://shop.com/item/102',
        'https://shop.com/item/104',
    ],
    'title': ['Blue Widget', 'Red Gadget', 'Blue Widget', 'Green Gizmo', 'Red Gadget', 'Yellow Thing'],
    'price': [19.99, 29.99, 19.99, 39.99, 31.99, 14.99],
    'scraped_at': ['2026-02-01', '2026-02-01', '2026-02-02', '2026-02-02', '2026-02-02', '2026-02-02']
})
 
print(f"Total scraped rows: {len(scraped)}")
print(f"Unique URLs: {scraped['url'].nunique()}")
 
# Keep the latest scrape for each URL (prices may have changed)
scraped['scraped_at'] = pd.to_datetime(scraped['scraped_at'])
scraped = scraped.sort_values('scraped_at', ascending=False)
products = scraped.drop_duplicates(subset=['url'], keep='first').reset_index(drop=True)
 
print(f"\nCleaned rows: {len(products)}")
print(products)

Sortie :

Total scraped rows: 6
Unique URLs: 4

Cleaned rows: 4
                          url        title  price scraped_at
0  https://shop.com/item/101  Blue Widget  19.99 2026-02-02
1  https://shop.com/item/102   Red Gadget  31.99 2026-02-02
2  https://shop.com/item/103  Green Gizmo  39.99 2026-02-02
3  https://shop.com/item/104  Yellow Thing  14.99 2026-02-02

Notez que le prix du Red Gadget a été mis à jour de 29,99 à 31,99 entre les crawls. En conservant l'entrée la plus récente, nous capturons le prix le plus actuel.

drop_duplicates() vs groupby().first() : Quand utiliser lequel

Les deux approches peuvent dédupliquer les données, mais elles fonctionnent différemment :

import pandas as pd
 
df = pd.DataFrame({
    'user_id': [1, 1, 2, 2, 3],
    'action': ['login', 'purchase', 'login', 'login', 'purchase'],
    'timestamp': ['2026-01-01', '2026-01-02', '2026-01-01', '2026-01-03', '2026-01-01']
})
 
# Method 1: drop_duplicates
result1 = df.drop_duplicates(subset=['user_id'], keep='first')
print("drop_duplicates:")
print(result1)
 
# Method 2: groupby().first()
result2 = df.groupby('user_id').first().reset_index()
print("\ngroupby().first():")
print(result2)

Sortie :

drop_duplicates:
   user_id    action   timestamp
0        1     login  2026-01-01
2        2     login  2026-01-01
4        3  purchase  2026-01-01

groupby().first():
   user_id    action   timestamp
0        1     login  2026-01-01
1        2     login  2026-01-01
2        3  purchase  2026-01-01

Les résultats semblent similaires, mais il y a des différences importantes :

Caractéristiquedrop_duplicates()groupby().first()
VitessePlus rapide pour la déduplication simplePlus lent en raison de la surcharge de regroupement
Gestion des NaNConserve les valeurs NaN telles quellesfirst() ignore les NaN par défaut
IndexPréserve l'index originalRéinitialisé aux clés de groupe
AgrégationNe peut pas agréger d'autres colonnesPeut être combiné avec agg() pour des résumés multi-colonnes
MémoireUtilisation mémoire plus faibleCrée un objet GroupBy intermédiaire
Cas d'utilisationSupprimer les doublons exacts ou partielsDédupliquer tout en calculant des agrégats

Règle générale : Utilisez drop_duplicates() quand vous voulez simplement supprimer les lignes en double. Utilisez groupby().first() (ou groupby().agg()) quand vous devez aussi agréger des valeurs des lignes en double -- par exemple, additionner le chiffre d'affaires sur les enregistrements clients en double tout en conservant le premier nom.

Astuces de performance pour les grands DataFrames

Lorsque vous travaillez avec des millions de lignes, la déduplication peut devenir un goulot d'étranglement. Voici des moyens pratiques pour l'accélérer :

1. Spécifier les colonnes subset

Vérifier toutes les colonnes est plus lent que vérifier uniquement les colonnes clés :

# Slower: checks every column
df.drop_duplicates()
 
# Faster: checks only the key column
df.drop_duplicates(subset=['user_id'])

2. Utiliser les types de données appropriés

Convertissez les colonnes texte en type category avant la déduplication si elles ont une faible cardinalité :

df['status'] = df['status'].astype('category')
df['country'] = df['country'].astype('category')
df_clean = df.drop_duplicates(subset=['status', 'country'])

3. Trier avant la déduplication uniquement si nécessaire

Trier un grand DataFrame juste pour contrôler quelle ligne keep='first' sélectionne ajoute un temps significatif. Si vous ne vous souciez pas de quel doublon survit, sautez le tri.

4. Envisager le traitement par chunks pour les très gros fichiers

Pour les fichiers qui ne tiennent pas en mémoire, traitez par chunks :

chunks = pd.read_csv('large_file.csv', chunksize=100_000)
seen = set()
clean_chunks = []
 
for chunk in chunks:
    chunk = chunk.drop_duplicates(subset=['id'])
    new_rows = chunk[~chunk['id'].isin(seen)]
    seen.update(new_rows['id'].tolist())
    clean_chunks.append(new_rows)
 
df_clean = pd.concat(clean_chunks, ignore_index=True)

5. Benchmark : performance typique

LignesColonnes vérifiéesTemps (approx)
100KToutes (10 cols)~15 ms
1MToutes (10 cols)~150 ms
1M1 colonne~50 ms
10M1 colonne~500 ms

Ces chiffres varient selon le matériel et les types de données, mais la conclusion clé est que drop_duplicates() évolue linéairement et traite la plupart des jeux de données en moins d'une seconde.

Explorez vos données nettoyées avec PyGWalker

Après avoir supprimé les doublons, l'étape suivante est généralement d'explorer le jeu de données nettoyé -- vérifier les distributions, repérer les valeurs aberrantes et confirmer que la déduplication a fonctionné comme prévu. Au lieu d'écrire plusieurs appels matplotlib ou seaborn, vous pouvez utiliser PyGWalker (opens in a new tab), une bibliothèque Python open-source qui transforme n'importe quel DataFrame pandas en une interface de visualisation interactive de type Tableau directement dans Jupyter Notebook.

import pandas as pd
import pygwalker as pyg
 
# Your cleaned customer data
customers_clean = pd.DataFrame({
    'customer_id': [1001, 1002, 1003, 1004],
    'name': ['Acme Corp', 'Beta Inc', 'Gamma LLC', 'Delta Co'],
    'revenue': [52000, 30000, 45000, 20000],
    'region': ['East', 'West', 'East', 'South']
})
 
# Launch interactive visualization
walker = pyg.walk(customers_clean)

Avec PyGWalker, vous pouvez glisser region sur l'axe x et revenue sur l'axe y pour voir instantanément la distribution du chiffre d'affaires par région. Vous pouvez créer des diagrammes en barres, des nuages de points, des histogrammes et des cartes de chaleur en glissant-déposant des champs -- aucun code de graphique requis. C'est particulièrement utile après la déduplication quand vous voulez vérifier que votre logique de nettoyage a produit des résultats sensés.

Installez PyGWalker avec pip install pygwalker ou essayez-le dans Google Colab (opens in a new tab).

FAQ

Est-ce que drop_duplicates() modifie le DataFrame original ?

Non, par défaut drop_duplicates() retourne un nouveau DataFrame et laisse l'original inchangé. Pour modifier sur place, passez inplace=True, mais l'approche recommandée est d'utiliser l'assignation : df = df.drop_duplicates().

Comment supprimer les doublons basés sur une seule colonne ?

Passez le nom de la colonne au paramètre subset : df.drop_duplicates(subset=['email']). Cela conserve la première ligne pour chaque email unique et supprime les lignes suivantes avec le même email, indépendamment des différences dans les autres colonnes.

Quelle est la différence entre duplicated() et drop_duplicates() ?

duplicated() retourne une Series booléenne indiquant quelles lignes sont des doublons (utile pour l'inspection et le comptage). drop_duplicates() retourne un DataFrame avec les lignes en double supprimées. Utilisez duplicated() d'abord pour comprendre vos données, puis drop_duplicates() pour les nettoyer.

Peut-on supprimer des doublons basés sur une condition ?

Pas directement avec drop_duplicates(). À la place, triez votre DataFrame par la colonne de condition d'abord, puis appelez drop_duplicates() avec keep='first'. Par exemple, pour conserver la ligne avec le chiffre d'affaires le plus élevé pour chaque client : df.sort_values('revenue', ascending=False).drop_duplicates(subset=['customer_id'], keep='first').

Comment gérer les doublons insensibles à la casse ?

Créez une colonne temporaire en minuscules, dédupliquez dessus, puis supprimez la colonne auxiliaire : df['key'] = df['name'].str.lower() suivi de df.drop_duplicates(subset=['key']).drop(columns=['key']). Cela préserve la casse originale tout en identifiant correctement les doublons.

Conclusion

La suppression des lignes en double est une étape fondamentale dans tout workflow de nettoyage de données. La méthode pandas drop_duplicates() gère la grande majorité des tâches de déduplication avec seulement quelques paramètres :

  • Utilisez subset pour dédupliquer sur des colonnes spécifiques plutôt que sur toutes les colonnes.
  • Utilisez keep='first' ou keep='last' pour contrôler quelle occurrence survit ; utilisez keep=False pour supprimer toutes les copies.
  • Utilisez duplicated() pour inspecter et compter les doublons avant de les supprimer.
  • Triez avant de dédupliquer quand vous devez conserver une ligne spécifique (par exemple, l'enregistrement le plus récent).
  • Pour la déduplication insensible à la casse, normalisez en minuscules dans une colonne temporaire d'abord.
  • Pour les grands jeux de données, limitez les colonnes subset et utilisez des types de données appropriés pour maintenir les performances.

Une fois vos données nettoyées, des outils comme PyGWalker (opens in a new tab) vous permettent d'explorer visuellement le résultat sans écrire de code de graphique, vous aidant à vérifier que la déduplication a fonctionné correctement et à passer directement à l'analyse.

📚