Skip to content
Tópicos
Pandas
Pandas Drop Duplicates: How to Remove Duplicate Rows in Python

Pandas Drop Duplicates: Como remover linhas duplicadas em Python

Updated on

Linhas duplicadas são um dos problemas de qualidade de dados mais comuns em conjuntos de dados do mundo real. Elas se infiltram através de chamadas de API repetidas, exportações CSV sobrepostas, pipelines ETL com defeito ou simples erros de copiar e colar durante a entrada manual de dados. Se não forem controladas, as duplicatas inflam a contagem de linhas, distorcem médias e somas e introduzem viés em modelos de machine learning. Um conjunto de dados que parece ter 10.000 registros de clientes pode na verdade ter 8.200 clientes únicos e 1.800 entradas fantasma corrompendo silenciosamente cada cálculo construído sobre ele.

O método drop_duplicates() do pandas é a forma padrão de detectar e remover essas linhas redundantes. Este guia percorre cada parâmetro, mostra padrões comuns para deduplicação no mundo real e aborda considerações de desempenho para grandes conjuntos de dados. Cada exemplo de código está pronto para copiar e inclui sua saída esperada.

📚

Por que as duplicatas importam

Antes de pular para o código, vale a pena entender exatamente o que as linhas duplicadas quebram:

ProblemaO que aconteceExemplo
Contagens infladaslen(df) e value_counts() contam a maisUm cliente aparece 3 vezes, então "total de clientes" está 3x alto demais
Médias erradasmean() pondera mais as linhas duplicadasUm pedido de alto valor contado duas vezes distorce o valor médio do pedido para cima
Joins quebradosMesclar com uma chave com duplicatas causa explosão de linhasUm merge muitos-para-muitos produz um produto cartesiano em vez de mapeamento 1:1
Modelos ML ruinsDados de treinamento com amostras repetidas enviesam o modeloO modelo memoriza exemplos duplicados e faz overfitting
Armazenamento desperdiçadoLinhas redundantes consomem disco e memóriaUm conjunto de dados de 2 GB poderia ser 1,4 GB após deduplicação

A solução é direta: encontrar as duplicatas, decidir qual cópia manter (se alguma) e remover o resto.

Sintaxe básica: df.drop_duplicates()

A chamada mais simples remove linhas onde cada valor de coluna é 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)

Saída:

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

A linha 2 e a linha 4 eram cópias exatas da linha 0 e da linha 1, então foram removidas. Note que os valores de índice originais (0, 1, 3) são preservados. Se você quiser um índice sequencial limpo, encadeie .reset_index(drop=True).

Assinatura completa do método

DataFrame.drop_duplicates(subset=None, keep='first', inplace=False, ignore_index=False)
ParâmetroTipoPadrãoDescrição
subsetrótulo de coluna ou listaNone (todas as colunas)Considerar apenas estas colunas ao identificar duplicatas
keep'first', 'last', False'first'Qual duplicata manter; False remove todas as cópias
inplaceboolFalseSe True, modifica o DataFrame no lugar e retorna None
ignore_indexboolFalseSe True, redefine o índice de 0 a n-1 no resultado

Encontrando duplicatas primeiro com df.duplicated()

Antes de remover duplicatas, você frequentemente quer inspecioná-las. O método duplicated() retorna uma Series booleana marcando linhas 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())

Saída:

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

Para ver as linhas duplicadas reais:

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

Saída:

   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 as cópias como duplicatas (não apenas a segunda ocorrência), para que você possa ver cada linha envolvida na duplicação.

Contando duplicatas

Para obter uma contagem rápida de quantas linhas duplicadas existem:

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}")

Saída:

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

Esta é uma verificação de sanidade útil antes e depois da limpeza.

O parâmetro subset: Verificar apenas colunas específicas

Frequentemente você vai querer deduplicar com base em uma coluna chave em vez de exigir que cada coluna corresponda. O parâmetro subset permite especificar quais colunas determinam a unicidade:

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)

Saída:

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

A linha 2 foi removida porque alice@mail.com já aparecia na linha 0, mesmo que os valores de name e signup_date fossem diferentes. Você também pode passar múltiplas colunas: subset=['email', 'name'] só consideraria linhas duplicadas quando ambas as colunas correspondem.

O parâmetro keep: first, last ou False

O parâmetro keep controla qual ocorrência 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))

Saída:

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 keepComportamentoCaso de uso
'first'Manter a primeira ocorrência, remover as posterioresManter o registro mais antigo
'last'Manter a última ocorrência, remover as anterioresManter o registro mais recente
FalseRemover todas as linhas que tenham qualquer duplicataEncontrar linhas verdadeiramente únicas (sem cópias)

No exemplo acima, keep=False retorna um DataFrame vazio porque ambos os IDs de sensor aparecem mais de uma vez.

In-place vs. Retornar um novo DataFrame

Por padrão, drop_duplicates() retorna um novo DataFrame e deixa o original inalterado. Definir inplace=True modifica o original diretamente:

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)}")

Saída:

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

O estilo moderno de pandas recomenda evitar inplace=True e usar atribuição no lugar (df = df.drop_duplicates()). Isso torna o código mais fácil de ler e depurar, especialmente em operações encadeadas.

Detecção de duplicatas insensível a maiúsculas e minúsculas

Por padrão, pandas trata "Alice" e "alice" como valores diferentes. Para deduplicação insensível a maiúsculas e minúsculas, normalize a coluna primeiro:

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)

Saída:

    name  score
0  Alice     90
3    Bob     78

Este padrão preserva a capitalização original enquanto identifica corretamente duplicatas insensíveis a maiúsculas e minúsculas.

Exemplo do mundo real: Limpando um banco de dados de clientes

Aqui está um cenário realista. Você recebe uma exportação de clientes de um CRM onde o mesmo cliente foi inserido várias vezes por diferentes representantes de vendas:

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)

Saída:

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

O padrão chave aqui é ordenar primeiro, depois deduplicar com keep='first'. Ao ordenar por last_contact em ordem decrescente antes de deduplicar, o registro mais recente para cada cliente sobrevive.

Exemplo do mundo real: Deduplicando resultados de web scraping

Web scrapers comumente produzem duplicatas quando páginas são rastreadas múltiplas vezes ou a paginação se sobrepõe:

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)

Saída:

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 o preço do Red Gadget atualizou de 29,99 para 31,99 entre os rastreamentos. Ao manter a entrada mais recente, capturamos o preço mais atual.

drop_duplicates() vs groupby().first(): Quando usar cada um

Ambas as abordagens podem deduplicar dados, mas funcionam de maneira 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)

Saída:

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

Os resultados parecem semelhantes, mas há diferenças importantes:

Característicadrop_duplicates()groupby().first()
VelocidadeMais rápido para deduplicação simplesMais lento devido à sobrecarga de agrupamento
Tratamento de NaNMantém valores NaN como estãofirst() pula NaN por padrão
ÍndicePreserva o índice originalRedefine para chaves de grupo
AgregaçãoNão pode agregar outras colunasPode combinar com agg() para resumos multi-coluna
MemóriaUso de memória menorCria objeto GroupBy intermediário
Caso de usoRemover duplicatas exatas ou parciaisDeduplicar enquanto também calcula agregados

Regra geral: Use drop_duplicates() quando simplesmente quiser remover linhas duplicadas. Use groupby().first() (ou groupby().agg()) quando também precisar agregar valores das linhas duplicadas -- por exemplo, somar receita entre registros de clientes duplicados enquanto mantém o primeiro nome.

Dicas de desempenho para DataFrames grandes

Ao trabalhar com milhões de linhas, a deduplicação pode se tornar um gargalo. Aqui estão maneiras práticas de acelerá-la:

1. Especificar colunas subset

Verificar todas as colunas é mais lento do que verificar apenas as colunas chave:

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

2. Usar tipos de dados apropriados

Converta colunas de texto para o tipo category antes da deduplicação se elas tiverem baixa cardinalidade:

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

3. Ordenar antes da deduplicação apenas quando necessário

Ordenar um DataFrame grande apenas para controlar qual linha keep='first' seleciona adiciona tempo significativo. Se você não se importa com qual duplicata sobrevive, pule a ordenação.

4. Considerar processamento em chunks para arquivos muito grandes

Para arquivos que não cabem na memória, processe em 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: desempenho típico

LinhasColunas verificadasTempo (aprox)
100KTodas (10 cols)~15 ms
1MTodas (10 cols)~150 ms
1M1 coluna~50 ms
10M1 coluna~500 ms

Esses números variam por hardware e tipos de dados, mas a conclusão chave é que drop_duplicates() escala linearmente e processa a maioria dos conjuntos de dados em menos de um segundo.

Explore seus dados limpos com PyGWalker

Após remover duplicatas, o próximo passo geralmente é explorar o conjunto de dados limpo -- verificar distribuições, encontrar outliers e confirmar que a deduplicação funcionou como esperado. Em vez de escrever múltiplas chamadas matplotlib ou seaborn, você pode usar PyGWalker (opens in a new tab), uma biblioteca Python de código aberto que transforma qualquer DataFrame pandas em uma interface de visualização interativa estilo Tableau diretamente no 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)

Com PyGWalker, você pode arrastar region para o eixo x e revenue para o eixo y para ver instantaneamente a distribuição de receita por região. Você pode criar gráficos de barras, gráficos de dispersão, histogramas e mapas de calor arrastando e soltando campos -- sem código de gráfico necessário. É especialmente útil após a deduplicação quando você quer verificar se sua lógica de limpeza produziu resultados sensatos.

Instale PyGWalker com pip install pygwalker ou experimente no Google Colab (opens in a new tab).

FAQ

drop_duplicates() modifica o DataFrame original?

Não, por padrão drop_duplicates() retorna um novo DataFrame e deixa o original inalterado. Para modificar no lugar, passe inplace=True, mas a abordagem recomendada é usar atribuição: df = df.drop_duplicates().

Como remover duplicatas com base em apenas uma coluna?

Passe o nome da coluna para o parâmetro subset: df.drop_duplicates(subset=['email']). Isso mantém a primeira linha para cada email único e remove as linhas subsequentes com o mesmo email, independentemente de diferenças em outras colunas.

Qual é a diferença entre duplicated() e drop_duplicates()?

duplicated() retorna uma Series booleana marcando quais linhas são duplicatas (útil para inspeção e contagem). drop_duplicates() retorna um DataFrame com as linhas duplicadas removidas. Use duplicated() primeiro para entender seus dados, depois drop_duplicates() para limpá-los.

Posso remover duplicatas com base em uma condição?

Não diretamente com drop_duplicates(). Em vez disso, ordene seu DataFrame pela coluna de condição primeiro, depois chame drop_duplicates() com keep='first'. Por exemplo, para manter a linha com a maior receita para cada cliente: df.sort_values('revenue', ascending=False).drop_duplicates(subset=['customer_id'], keep='first').

Como lidar com duplicatas insensíveis a maiúsculas e minúsculas?

Crie uma coluna temporária em minúsculas, deduplique nela e depois remova a coluna auxiliar: df['key'] = df['name'].str.lower() seguido de df.drop_duplicates(subset=['key']).drop(columns=['key']). Isso preserva a capitalização original enquanto identifica corretamente as duplicatas.

Conclusão

Remover linhas duplicadas é um passo fundamental em qualquer fluxo de trabalho de limpeza de dados. O método drop_duplicates() do pandas lida com a grande maioria das tarefas de deduplicação com apenas alguns parâmetros:

  • Use subset para deduplicar em colunas específicas em vez de todas as colunas.
  • Use keep='first' ou keep='last' para controlar qual ocorrência sobrevive; use keep=False para remover todas as cópias.
  • Use duplicated() para inspecionar e contar duplicatas antes de removê-las.
  • Ordene antes de deduplicar quando precisar manter uma linha específica (por exemplo, o registro mais recente).
  • Para deduplicação insensível a maiúsculas e minúsculas, normalize para minúsculas em uma coluna temporária primeiro.
  • Para grandes conjuntos de dados, limite as colunas do subset e use tipos de dados apropriados para manter o desempenho rápido.

Uma vez que seus dados estejam limpos, ferramentas como PyGWalker (opens in a new tab) permitem que você explore visualmente o resultado sem escrever código de gráfico, ajudando a verificar que a deduplicação funcionou corretamente e seguir direto para a análise.

📚