Pandas Drop Duplicates: Cómo eliminar filas duplicadas en Python
Updated on
Las filas duplicadas son uno de los problemas de calidad de datos más comunes en conjuntos de datos del mundo real. Se infiltran a través de llamadas API repetidas, exportaciones CSV superpuestas, pipelines ETL defectuosos o simples errores de copiar y pegar durante la entrada manual de datos. Si no se controlan, los duplicados inflan los conteos de filas, distorsionan promedios y sumas, e introducen sesgo en los modelos de machine learning. Un conjunto de datos que parece tener 10.000 registros de clientes podría realmente tener 8.200 clientes únicos y 1.800 entradas fantasma corrompiendo silenciosamente cada cálculo construido sobre él.
El método drop_duplicates() de pandas es la forma estándar de detectar y eliminar estas filas redundantes. Esta guía recorre cada parámetro, muestra patrones comunes para la deduplicación en el mundo real y cubre consideraciones de rendimiento para conjuntos de datos grandes. Cada ejemplo de código está listo para copiar e incluye su salida esperada.
Por qué importan los duplicados
Antes de saltar al código, vale la pena entender exactamente qué rompen las filas duplicadas:
| Problema | Qué sucede | Ejemplo |
|---|---|---|
| Conteos inflados | len(df) y value_counts() cuentan de más | Un cliente aparece 3 veces, así que "total de clientes" es 3x demasiado alto |
| Promedios incorrectos | mean() pondera más las filas duplicadas | Un pedido de alto valor contado dos veces sesga el valor promedio del pedido hacia arriba |
| Joins rotos | Fusionar con una clave con duplicados causa explosión de filas | Un merge many-to-many produce un producto cartesiano en lugar de mapeo 1:1 |
| Modelos ML deficientes | Datos de entrenamiento con muestras repetidas sesgan el modelo | El modelo memoriza ejemplos duplicados y sobreajusta |
| Almacenamiento desperdiciado | Las filas redundantes consumen disco y memoria | Un conjunto de datos de 2 GB podría ser de 1,4 GB después de la deduplicación |
La solución es sencilla: encontrar los duplicados, decidir qué copia conservar (si alguna) y eliminar el resto.
Sintaxis básica: df.drop_duplicates()
La llamada más simple elimina filas donde cada valor de columna es idéntico:
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)Salida:
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 ChicagoLa fila 2 y la fila 4 eran copias exactas de la fila 0 y la fila 1, por lo que fueron eliminadas. Note que los valores originales del índice (0, 1, 3) se preservan. Si desea un índice secuencial limpio, encadene .reset_index(drop=True).
Firma completa del método
DataFrame.drop_duplicates(subset=None, keep='first', inplace=False, ignore_index=False)| Parámetro | Tipo | Valor por defecto | Descripción |
|---|---|---|---|
subset | etiqueta de columna o lista | None (todas las columnas) | Solo considerar estas columnas al identificar duplicados |
keep | 'first', 'last', False | 'first' | Qué duplicado conservar; False elimina todas las copias |
inplace | bool | False | Si es True, modifica el DataFrame en su lugar y devuelve None |
ignore_index | bool | False | Si es True, reinicia el índice de 0 a n-1 en el resultado |
Encontrar duplicados primero con df.duplicated()
Antes de eliminar duplicados, a menudo querrá inspeccionarlos. El método duplicated() devuelve una Series booleana que marca las filas duplicadas:
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())Salida:
0 False
1 False
2 False
3 True
4 False
5 True
dtype: boolPara ver las filas duplicadas reales:
duplicates = df[df.duplicated(keep=False)]
print(duplicates)Salida:
order_id product amount
0 101 Widget 29.99
1 102 Gadget 49.99
3 101 Widget 29.99
5 102 Gadget 49.99Usar keep=False marca todas las copias como duplicados (no solo la segunda aparición), para que pueda ver cada fila involucrada en la duplicación.
Contar duplicados
Para obtener un conteo rápido de cuántas filas duplicadas existen:
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}")Salida:
Number of duplicate rows: 2
Total: 6, Unique: 4, Duplicates: 2Esta es una verificación de cordura útil antes y después de la limpieza.
El parámetro subset: Verificar solo columnas específicas
A menudo querrá deduplicar basándose en una columna clave en lugar de requerir que cada columna coincida. El parámetro subset le permite especificar qué columnas determinan la unicidad:
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)Salida:
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-03La fila 2 fue eliminada porque alice@mail.com ya aparecía en la fila 0, aunque los valores de name y signup_date diferían. También puede pasar múltiples columnas: subset=['email', 'name'] solo consideraría filas duplicadas cuando ambas columnas coincidan.
El parámetro keep: first, last o False
El parámetro keep controla qué aparición sobrevive:
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))Salida:
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: []| Valor de keep | Comportamiento | Caso de uso |
|---|---|---|
'first' | Conservar la primera aparición, eliminar las posteriores | Conservar el registro más antiguo |
'last' | Conservar la última aparición, eliminar las anteriores | Conservar el registro más reciente |
False | Eliminar todas las filas que tengan algún duplicado | Encontrar filas verdaderamente únicas (sin copias) |
En el ejemplo anterior, keep=False devuelve un DataFrame vacío porque ambos IDs de sensor aparecen más de una vez.
In-place vs. Devolver un nuevo DataFrame
Por defecto, drop_duplicates() devuelve un nuevo DataFrame y deja el original sin cambios. Establecer inplace=True modifica el original directamente:
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)}")Salida:
Original length: 4, New length: 3
After inplace: 3El estilo moderno de pandas recomienda evitar inplace=True y usar asignación en su lugar (df = df.drop_duplicates()). Esto hace que el código sea más fácil de leer y depurar, especialmente en operaciones encadenadas.
Detección de duplicados sin distinción de mayúsculas y minúsculas
Por defecto, pandas trata "Alice" y "alice" como valores diferentes. Para deduplicación sin distinción de mayúsculas y minúsculas, normalice la columna primero:
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)Salida:
name score
0 Alice 90
3 Bob 78Este patrón preserva las mayúsculas originales mientras identifica correctamente los duplicados sin distinción de mayúsculas y minúsculas.
Ejemplo del mundo real: Limpieza de una base de datos de clientes
Aquí hay un escenario realista. Recibe una exportación de clientes de un CRM donde el mismo cliente fue ingresado múltiples veces por diferentes representantes de ventas:
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)Salida:
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-05El patrón clave aquí es ordenar primero, luego deduplicar con keep='first'. Al ordenar por last_contact en orden descendente antes de deduplicar, el registro más reciente para cada cliente sobrevive.
Ejemplo del mundo real: Deduplicación de resultados de web scraping
Los web scrapers comúnmente producen duplicados cuando las páginas se rastrean múltiples veces o la paginación se superpone:
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)Salida:
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-02Note que el precio del Red Gadget se actualizó de 29,99 a 31,99 entre los rastreos. Al conservar la entrada más reciente, capturamos el precio más actual.
drop_duplicates() vs groupby().first(): Cuándo usar cada uno
Ambos enfoques pueden deduplicar datos, pero funcionan de manera diferente:
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)Salida:
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-01Los resultados parecen similares, pero hay diferencias importantes:
| Característica | drop_duplicates() | groupby().first() |
|---|---|---|
| Velocidad | Más rápido para deduplicación simple | Más lento debido a la sobrecarga de agrupación |
| Manejo de NaN | Mantiene los valores NaN tal cual | first() omite NaN por defecto |
| Índice | Preserva el índice original | Se reinicia a las claves de grupo |
| Agregación | No puede agregar otras columnas | Se puede combinar con agg() para resúmenes multi-columna |
| Memoria | Menor uso de memoria | Crea un objeto GroupBy intermedio |
| Caso de uso | Eliminar duplicados exactos o parciales | Deduplicar mientras se calculan agregados |
Regla general: Use drop_duplicates() cuando simplemente quiera eliminar filas duplicadas. Use groupby().first() (o groupby().agg()) cuando también necesite agregar valores de las filas duplicadas -- por ejemplo, sumar ingresos de registros de clientes duplicados mientras se conserva el primer nombre.
Consejos de rendimiento para DataFrames grandes
Cuando se trabaja con millones de filas, la deduplicación puede convertirse en un cuello de botella. Aquí hay formas prácticas de acelerarla:
1. Especificar columnas subset
Verificar todas las columnas es más lento que verificar solo las columnas clave:
# Slower: checks every column
df.drop_duplicates()
# Faster: checks only the key column
df.drop_duplicates(subset=['user_id'])2. Usar tipos de datos apropiados
Convierta columnas de texto al tipo category antes de la deduplicación si tienen baja cardinalidad:
df['status'] = df['status'].astype('category')
df['country'] = df['country'].astype('category')
df_clean = df.drop_duplicates(subset=['status', 'country'])3. Ordenar antes de la deduplicación solo cuando sea necesario
Ordenar un DataFrame grande solo para controlar qué fila selecciona keep='first' añade un tiempo significativo. Si no le importa qué duplicado sobrevive, omita la ordenación.
4. Considerar procesamiento por chunks para archivos muy grandes
Para archivos que no caben en memoria, procese por 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: rendimiento típico
| Filas | Columnas verificadas | Tiempo (aprox) |
|---|---|---|
| 100K | Todas (10 cols) | ~15 ms |
| 1M | Todas (10 cols) | ~150 ms |
| 1M | 1 columna | ~50 ms |
| 10M | 1 columna | ~500 ms |
Estos números varían según el hardware y los tipos de datos, pero la conclusión clave es que drop_duplicates() escala linealmente y procesa la mayoría de los conjuntos de datos en menos de un segundo.
Explore sus datos limpios con PyGWalker
Después de eliminar duplicados, el siguiente paso suele ser explorar el conjunto de datos limpio -- verificar distribuciones, detectar valores atípicos y confirmar que la deduplicación funcionó como se esperaba. En lugar de escribir múltiples llamadas a matplotlib o seaborn, puede usar PyGWalker (opens in a new tab), una biblioteca Python de código abierto que convierte cualquier DataFrame de pandas en una interfaz de visualización interactiva tipo Tableau directamente en 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)Con PyGWalker, puede arrastrar region al eje x y revenue al eje y para ver instantáneamente la distribución de ingresos por región. Puede crear gráficos de barras, diagramas de dispersión, histogramas y mapas de calor arrastrando y soltando campos -- sin necesidad de código para gráficos. Es especialmente útil después de la deduplicación cuando desea verificar que su lógica de limpieza produjo resultados sensatos.
Instale PyGWalker con
pip install pygwalkero pruébelo en Google Colab (opens in a new tab).
FAQ
¿drop_duplicates() modifica el DataFrame original?
No, por defecto drop_duplicates() devuelve un nuevo DataFrame y deja el original sin cambios. Para modificar en su lugar, pase inplace=True, pero el enfoque recomendado es usar asignación: df = df.drop_duplicates().
¿Cómo elimino duplicados basándome en una sola columna?
Pase el nombre de la columna al parámetro subset: df.drop_duplicates(subset=['email']). Esto conserva la primera fila para cada email único y elimina las filas subsiguientes con el mismo email, independientemente de las diferencias en otras columnas.
¿Cuál es la diferencia entre duplicated() y drop_duplicates()?
duplicated() devuelve una Series booleana que marca qué filas son duplicados (útil para inspección y conteo). drop_duplicates() devuelve un DataFrame con las filas duplicadas eliminadas. Use duplicated() primero para entender sus datos, luego drop_duplicates() para limpiarlos.
¿Puedo eliminar duplicados basándome en una condición?
No directamente con drop_duplicates(). En su lugar, ordene su DataFrame por la columna de condición primero, luego llame a drop_duplicates() con keep='first'. Por ejemplo, para conservar la fila con el mayor ingreso para cada cliente: df.sort_values('revenue', ascending=False).drop_duplicates(subset=['customer_id'], keep='first').
¿Cómo manejo duplicados sin distinción de mayúsculas y minúsculas?
Cree una columna temporal en minúsculas, deduplique sobre ella y luego elimine la columna auxiliar: df['key'] = df['name'].str.lower() seguido de df.drop_duplicates(subset=['key']).drop(columns=['key']). Esto preserva las mayúsculas originales mientras identifica correctamente los duplicados.
Conclusión
Eliminar filas duplicadas es un paso fundamental en cualquier flujo de trabajo de limpieza de datos. El método drop_duplicates() de pandas maneja la gran mayoría de las tareas de deduplicación con solo unos pocos parámetros:
- Use
subsetpara deduplicar en columnas específicas en lugar de todas las columnas. - Use
keep='first'okeep='last'para controlar qué aparición sobrevive; usekeep=Falsepara eliminar todas las copias. - Use
duplicated()para inspeccionar y contar duplicados antes de eliminarlos. - Ordene antes de deduplicar cuando necesite conservar una fila específica (por ejemplo, el registro más reciente).
- Para deduplicación sin distinción de mayúsculas y minúsculas, normalice a minúsculas en una columna temporal primero.
- Para conjuntos de datos grandes, limite las columnas de
subsety use tipos de datos apropiados para mantener el rendimiento rápido.
Una vez que sus datos estén limpios, herramientas como PyGWalker (opens in a new tab) le permiten explorar visualmente el resultado sin escribir código para gráficos, ayudándole a verificar que la deduplicación funcionó correctamente y pasar directamente al análisis.