Supprimer les lignes en double, ça ressemble à un simple set(rows). Sur une vraie liste de clients, c'est rarement le cas, car une même personne inscrite deux fois ne produit presque jamais deux lignes identiques. La date de commande change, la ville a changé, l'email a été tapé en majuscules la deuxième fois. Savoir si ces lignes sont des doublons, les données ne peuvent pas vous le dire. Tout dépend de la clé que vous comparez, et ce choix décide de tout le reste : combien de lignes disparaissent, laquelle reste et ce que vous perdez.

Ci-dessous, ces décisions sont prises une par une en Python pur sur une liste d'exemple de 412 lignes, puis vérifiées avec la commande Remove Duplicates du Spreadsheet Editor de l'application de bureau osFoundry, qui ramène ce même choix à un seul champ de texte.

La liste

La liste est générée pour la démo : quatre colonnes (Email, Name, City, Last order), 374 clients différents et 38 lignes supplémentaires qui reprennent l'email d'un client déjà présent. Chaque répétition a sa propre date de commande, et pour une sur trois la ville a été tirée à nouveau, comme pour une deuxième inscription après un déménagement. En comptant par email, 31 clients apparaissent plus d'une fois : 25 deux fois, 5 trois fois et 1 quatre fois.

import openpyxl ws = openpyxl.load_workbook("en-customers.xlsx").active header, *rows = [tuple(str(c) for c in r) for r in ws.iter_rows(values_only=True)] len(rows) # 412 

Une seule fonction, la clé en argument

Toutes les variantes ci-dessous sont la même boucle avec une autre clé. Elle garde la première ligne rencontrée pour chaque clé ou, avec keep="last", l'écrase sur place :

def dedupe(rows, key, keep="first"): seen, out = {}, [] for r in rows: k = key(r) if k in seen: if keep == "last": out[seen[k]] = r continue seen[k] = len(out) out.append(r) return out 

Une recherche dans un dict par ligne : c'est linéaire et l'ordre d'origine des lignes conservées est respecté. pandas fait la même chose avec df.drop_duplicates(subset=[...], keep="first").

Ligne entière ou clé

Trois clés sur les mêmes 412 lignes :

clé lignes conservées lignes supprimées
ligne entière 412 0
Email 374 38
Email + City 387 25

La clé « ligne entière » ne supprime rien. C'est le comportement par défaut de la plupart des boutons « supprimer les doublons », et sur une liste comme celle-ci il ne fait rien, sans le signaler, puisqu'aucune répétition n'a la même date de commande que l'original. L'email seul supprime les 38. Email plus City en supprime 25 et garde 13 lignes qui correspondent à la même personne dans une autre ville : c'est juste pour une liste d'adresses de livraison, faux pour une liste de personnes.

Le Spreadsheet Editor place ce choix dans un seul champ de sa boîte de dialogue Remove Duplicates, "Columns (blank = all, e.g. A,C)". Vide, c'est la clé « ligne entière ». A, c'est la clé email.

La boîte de dialogue Remove Duplicates du Spreadsheet Editor avec la colonne A comme clé

Lancé avec A, le compteur de la barre d'état sur la colonne A passe de 413 à 375, soit 374 emails plus l'en-tête, le même nombre que celui conservé par la version Python.

Quelle copie survit

Garder la première ligne est la convention dans Excel, dans pandas et dans cet éditeur, et du point de vue de vos données c'est arbitraire. Sur cette liste, la première ligne n'est pas la commande la plus récente du client pour 20 des 31 clients répétés, et 12 des 31 ont des copies qui ne s'accordent pas sur la ville. Après un dédoublonnage qui garde la première ligne, ces 12 conservent la ville qui a été saisie en premier.

Si c'est la ligne la plus récente qui doit gagner, triez avant de dédoublonner. Le tri de Python est stable et les dates ISO se trient correctement comme chaînes, il suffit donc de ceci :

latest = dedupe(sorted(rows, key=lambda r: r[3], reverse=True), key=lambda r: r[0]) len(latest) # 374, each with its latest order 

Dans un tableur, l'équivalent consiste à trier par la colonne de date, de la plus récente à la plus ancienne, avant de lancer Remove Duplicates.

Normaliser la clé, pas les données

La liste d'exemple a des emails propres. Les listes remplies à la main, non. Pour voir ce qu'une comparaison exacte rate, les 38 lignes répétées ont été réécrites dans une copie de la liste, en alternant cinq façons dont une deuxième inscription est souvent saisie : tout en majuscules, une espace au début, une espace à la fin, une première lettre en majuscule, et le @ et le . tapés en pleine chasse par un éditeur de méthode d'entrée (@, .). La première copie de chaque client est restée telle quelle.

import unicodedata def email_key(e): return unicodedata.normalize("NFKC", e).strip().lower() 
clé lignes conservées lignes supprimées
exacte 409 3
strip().lower() 381 31
NFKC + strip().lower() 374 38

La clé exacte en trouve 3, et ce ne sont que des copies ultérieures du même client qui se trouvaient être saisies de la même façon entre elles. strip().lower() attrape tout sauf la ponctuation en pleine chasse, presque identique à l'écran. NFKC ramène les formes en pleine chasse vers l'ASCII et les trouve toutes les 38.

La valeur normalisée ne sert que de clé. La ligne conservée garde l'email exactement tel qu'il a été écrit, ce qui compte si la liste repart dans un système qui le stocke.

Deux mises en garde. À la lettre du RFC 5321, la partie avant le @ est sensible à la casse : la passer en minuscules est donc une hypothèse pratique qui tient chez les grands fournisseurs, pas une règle. Et les astuces propres à un fournisseur, comme ignorer les points dans les adresses Gmail, fusionnent des adresses que d'autres fournisseurs considèrent comme des personnes différentes ; elles relèvent d'une étape séparée et délibérée.

Ce que coûte la normalisation

Construction d'un set de clés à partir de 1 000 000 d'emails (la liste d'exemple répétée), Python 3.12 sur un Apple M1, médiane de 5 exécutions :

clé temps
chaîne brute 5 ms
strip().lower() 80 ms
NFKC + strip().lower() 102 ms

C'est la normalisation qui prend le temps, et cela reste un dixième de seconde par million de lignes. Aucune raison de s'en passer pour gagner en vitesse.

L'ordre qui fonctionne

D'abord surligner, ensuite supprimer. Surligner les doublons sur la colonne clé (dans l'éditeur, Conditional Formatting → Highlight Cells Rules → Duplicate Values, qui marque aussi la première copie) vous montre les groupes avant toute suppression, et c'est là que vous repérez l'adresse familiale partagée qu'il ne faut pas fusionner. Ensuite, choisissez la clé, décidez quelle copie gagne et triez en conséquence, normalisez la clé si les données ont été saisies à la main, et vérifiez le nombre de lignes à la fin.

Les scripts derrière chaque chiffre de cet article n'utilisent que openpyxl et la bibliothèque standard. Le Spreadsheet Editor tourne sur votre propre machine, comme l'explique la page sur la conception local-first d'osFoundry, et l'application est gratuite depuis la page de téléchargement d'osFoundry.