Pandas #4 - Analisando e Limpando Dados

Como inspecionar rapidamente um DataFrame e tratar os problemas mais comuns de dados sujos - células vazias, formatos errados, valores incorretos e duplicatas.

Pandas #4 - Analisando e Limpando Dados

No post anterior você viu como fazer contas com Series e DataFrames. Mas antes de somar, multiplicar ou comparar qualquer coluna, tem um passo que sempre vem primeiro na prática: garantir que os dados que chegaram até você prestam. Células vazias, datas em formatos diferentes dentro da mesma coluna, valores absurdos e linhas duplicadas são a regra, não a exceção — e é aí que entra a limpeza de dados.

Neste post você vai aprender primeiro a dar uma “olhada rápida” num DataFrame recém-carregado, e depois a resolver os quatro problemas de dados sujos mais comuns: células vazias, formato errado, dados errados e duplicatas.

Inspecionando um DataFrame rapidamente

Antes de sair limpando qualquer coisa, o primeiro passo é entender o que você tem em mãos. Vamos usar como exemplo um DataFrame com o registro de treinos de uma pessoa ao longo de alguns dias:

import pandas as pd

dados = {
    "duracao": [30, 45, 60, 45, None, 60, 450],
    "pulso": [110, 117, 103, 109, 117, 102, 104],
    "calorias": [280.0, 340.0, 380.5, None, 320.0, 400.0, 300.0]
}

df = pd.DataFrame(dados)

head() e tail()

.head() mostra as primeiras linhas do DataFrame (5 por padrão, mas você pode passar outro número):

print(df.head(3))

A saída é:

   duracao  pulso  calorias
0     30.0    110     280.0
1     45.0    117     340.0
2     60.0    103     380.5

.tail() faz o mesmo, só que a partir do final:

print(df.tail(2))
   duracao  pulso  calorias
5     60.0    102     400.0
6    450.0    104     300.0

Já dá pra notar algo estranho: uma duração de 450 no meio de valores que giram em torno de 30-60. Vamos voltar nisso mais adiante.

info()

Já .info() não mostra os dados em si, mostra metadados sobre a estrutura da tabela — quantidade de linhas, colunas, tipo de cada coluna e quantos valores não-nulos existem em cada uma:

print(df.info())

A saída é:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7 entries, 0 to 6
Data columns (total 3 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   duracao   6 non-null      float64
 1   pulso     7 non-null      int64
 2   calorias  6 non-null      float64
dtypes: float64(2), int64(1)

Repare que duracao e calorias têm só 6 valores não-nulos, contra 7 linhas no total — ou seja, cada uma tem uma célula vazia. É exatamente esse tipo de sinal que o .info() ajuda a enxergar rápido, sem precisar vasculhar a tabela inteira na mão.

Nota: rodar .head(), .tail() e .info() logo depois de carregar qualquer dataset novo é um hábito que economiza muito tempo de debug mais adiante.

Tratando células vazias

Células vazias (representadas como NaN) atrapalham cálculos estatísticos e podem até quebrar algumas operações. O Pandas dá duas saídas principais: remover as linhas com valor vazio, ou preenchê-las com algum valor.

Removendo linhas com dropna()

df_limpo = df.dropna()
print(df_limpo)

Por padrão, .dropna() devolve um novo DataFrame, sem alterar o original. Se quiser modificar o próprio df, use inplace=True:

df.dropna(inplace=True)

Preenchendo valores vazios com fillna()

Às vezes remover a linha inteira é desperdício — melhor preencher só a célula vazia. Dá pra aplicar em todo o DataFrame ou numa coluna específica:

df["calorias"].fillna(300, inplace=True)

# ou preenchendo por coluna, num único comando
df.fillna({"calorias": 300}, inplace=True)

Preenchendo com média, mediana ou moda

Chutar um número fixo (como o 300 acima) raramente é a melhor ideia. É mais comum calcular um valor a partir da própria coluna:

import pandas as pd

dados = {
    "duracao": [30, 45, 60, 45, None, 60, 450],
    "pulso": [110, 117, 103, 109, 117, 102, 104],
    "calorias": [280.0, 340.0, 380.5, None, 320.0, 400.0, 300.0]
}
df = pd.DataFrame(dados)

media = df["calorias"].mean()
df.fillna({"calorias": media}, inplace=True)

A média costuma ser um bom padrão pra colunas numéricas contínuas; a moda é mais indicada pra colunas categóricas.

Corrigindo o formato dos dados

Outro problema comum é uma coluna que deveria ter um tipo consistente (datas, por exemplo) mas guarda valores em formatos diferentes — "2026/03/10" numa linha, "10-03-2026" em outra.

import pandas as pd

dados = {
    "data": ["2026/03/01", "2026/03/02", "10-03-2026", None],
    "duracao": [30, 45, 60, 45]
}
df = pd.DataFrame(dados)

df["data"] = pd.to_datetime(df["data"], format="mixed")
print(df)

O parâmetro format="mixed" diz pro pd.to_datetime() tentar reconhecer formatos diferentes dentro da mesma coluna, em vez de exigir um padrão único. Valores que não conseguem ser convertidos (como um None) viram NaT (“Not a Time” — o equivalente a NaN, só que pra datas).

Depois de converter, dá pra remover as linhas que ficaram com data inválida:

df.dropna(subset=["data"], inplace=True)

O parâmetro subset restringe a busca por valores vazios só à coluna data, em vez de olhar o DataFrame inteiro.

Corrigindo dados errados

Diferente de uma célula vazia, um dado errado é um valor presente, só que implausível — lembra daquela duração de 450 minutos lá em cima, cercada de valores entre 30 e 60?

Corrigindo um valor pontualmente

Se você sabe exatamente qual linha e qual deveria ser o valor correto, o .loc resolve direto:

df.loc[6, "duracao"] = 45

Corrigindo em lote com uma regra

Pra datasets maiores, não dá pra corrigir linha por linha na mão. Uma abordagem comum é definir um limite e substituir tudo que ultrapassa ele:

for indice in df.index:
    if df.loc[indice, "duracao"] > 120:
        df.loc[indice, "duracao"] = 120

Removendo a linha em vez de corrigir

Se não dá pra saber qual seria o valor certo, às vezes a saída mais simples é descartar a linha inteira:

for indice in df.index:
    if df.loc[indice, "duracao"] > 120:
        df.drop(indice, inplace=True)

Vale lembrar que remover dados sempre tem um custo — você está jogando fora informação que pode fazer falta depois. Use com critério.

Removendo linhas duplicadas

Por fim, o problema mais direto de resolver: linhas repetidas inteiras, que costumam entrar no dataset por erro de coleta ou de junção de fontes diferentes.

Encontrando duplicatas com duplicated()

print(df.duplicated())

O retorno é uma Series de booleanos, uma posição por linha: True quando aquela linha é idêntica a alguma anterior, False caso contrário.

Removendo com drop_duplicates()

df.drop_duplicates(inplace=True)

Assim como nos outros métodos de limpeza, inplace=True aplica a remoção direto no df, sem precisar reatribuir o resultado a uma nova variável.

Com o DataFrame inspecionado e limpo dos problemas mais comuns, ele já está em condições pra um próximo nível de cuidado: o post seguinte aprofunda especificamente em dados ausentes, com isna(), ffill(), bfill() e interpolate() — ferramentas que complementam o dropna()/fillna() que você acabou de ver.

Fonte adaptada: Pandas - Analyzing DataFrames, Pandas - Cleaning Data, Pandas - Cleaning Empty Cells, Pandas - Cleaning Data of Wrong Format, Pandas - Fixing Wrong Data, Pandas - Removing Duplicates