Skip to content
Temas
Pandas
Pandas Drop Duplicates: How to Remove Duplicate Rows in Python

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:

ProblemaQué sucedeEjemplo
Conteos infladoslen(df) y value_counts() cuentan de másUn cliente aparece 3 veces, así que "total de clientes" es 3x demasiado alto
Promedios incorrectosmean() pondera más las filas duplicadasUn pedido de alto valor contado dos veces sesga el valor promedio del pedido hacia arriba
Joins rotosFusionar con una clave con duplicados causa explosión de filasUn merge many-to-many produce un producto cartesiano en lugar de mapeo 1:1
Modelos ML deficientesDatos de entrenamiento con muestras repetidas sesgan el modeloEl modelo memoriza ejemplos duplicados y sobreajusta
Almacenamiento desperdiciadoLas filas redundantes consumen disco y memoriaUn 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  Chicago

La 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ámetroTipoValor por defectoDescripción
subsetetiqueta de columna o listaNone (todas las columnas)Solo considerar estas columnas al identificar duplicados
keep'first', 'last', False'first'Qué duplicado conservar; False elimina todas las copias
inplaceboolFalseSi es True, modifica el DataFrame en su lugar y devuelve None
ignore_indexboolFalseSi 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: bool

Para 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.99

Usar 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: 2

Esta 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-03

La 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 keepComportamientoCaso de uso
'first'Conservar la primera aparición, eliminar las posterioresConservar el registro más antiguo
'last'Conservar la última aparición, eliminar las anterioresConservar el registro más reciente
FalseEliminar todas las filas que tengan algún duplicadoEncontrar 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: 3

El 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     78

Este 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-05

El 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-02

Note 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-01

Los resultados parecen similares, pero hay diferencias importantes:

Característicadrop_duplicates()groupby().first()
VelocidadMás rápido para deduplicación simpleMás lento debido a la sobrecarga de agrupación
Manejo de NaNMantiene los valores NaN tal cualfirst() omite NaN por defecto
ÍndicePreserva el índice originalSe reinicia a las claves de grupo
AgregaciónNo puede agregar otras columnasSe puede combinar con agg() para resúmenes multi-columna
MemoriaMenor uso de memoriaCrea un objeto GroupBy intermedio
Caso de usoEliminar duplicados exactos o parcialesDeduplicar 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

FilasColumnas verificadasTiempo (aprox)
100KTodas (10 cols)~15 ms
1MTodas (10 cols)~150 ms
1M1 columna~50 ms
10M1 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 pygwalker o 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 subset para deduplicar en columnas específicas en lugar de todas las columnas.
  • Use keep='first' o keep='last' para controlar qué aparición sobrevive; use keep=False para 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 subset y 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.

📚