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:
| Problema | O que acontece | Exemplo |
|---|---|---|
| Contagens infladas | len(df) e value_counts() contam a mais | Um cliente aparece 3 vezes, então "total de clientes" está 3x alto demais |
| Médias erradas | mean() pondera mais as linhas duplicadas | Um pedido de alto valor contado duas vezes distorce o valor médio do pedido para cima |
| Joins quebrados | Mesclar com uma chave com duplicatas causa explosão de linhas | Um merge muitos-para-muitos produz um produto cartesiano em vez de mapeamento 1:1 |
| Modelos ML ruins | Dados de treinamento com amostras repetidas enviesam o modelo | O modelo memoriza exemplos duplicados e faz overfitting |
| Armazenamento desperdiçado | Linhas redundantes consomem disco e memória | Um 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 ChicagoA 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âmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
subset | rótulo de coluna ou lista | None (todas as colunas) | Considerar apenas estas colunas ao identificar duplicatas |
keep | 'first', 'last', False | 'first' | Qual duplicata manter; False remove todas as cópias |
inplace | bool | False | Se True, modifica o DataFrame no lugar e retorna None |
ignore_index | bool | False | Se 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: boolPara 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.99Usar 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: 2Esta é 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-03A 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 keep | Comportamento | Caso de uso |
|---|---|---|
'first' | Manter a primeira ocorrência, remover as posteriores | Manter o registro mais antigo |
'last' | Manter a última ocorrência, remover as anteriores | Manter o registro mais recente |
False | Remover todas as linhas que tenham qualquer duplicata | Encontrar 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: 3O 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 78Este 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-05O 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-02Note 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-01Os resultados parecem semelhantes, mas há diferenças importantes:
| Característica | drop_duplicates() | groupby().first() |
|---|---|---|
| Velocidade | Mais rápido para deduplicação simples | Mais lento devido à sobrecarga de agrupamento |
| Tratamento de NaN | Mantém valores NaN como estão | first() pula NaN por padrão |
| Índice | Preserva o índice original | Redefine para chaves de grupo |
| Agregação | Não pode agregar outras colunas | Pode combinar com agg() para resumos multi-coluna |
| Memória | Uso de memória menor | Cria objeto GroupBy intermediário |
| Caso de uso | Remover duplicatas exatas ou parciais | Deduplicar 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
| Linhas | Colunas verificadas | Tempo (aprox) |
|---|---|---|
| 100K | Todas (10 cols) | ~15 ms |
| 1M | Todas (10 cols) | ~150 ms |
| 1M | 1 coluna | ~50 ms |
| 10M | 1 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 pygwalkerou 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
subsetpara deduplicar em colunas específicas em vez de todas as colunas. - Use
keep='first'oukeep='last'para controlar qual ocorrência sobrevive; usekeep=Falsepara 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
subsete 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.