Fundamentos do Machine Learning com Python

0/25 aulas0%
pratica

Limpeza e Pré-processamento de Dados: Valores Ausentes e Duplicatas (Documentação Pandas)

Aprenda sobre limpeza e pré-processamento de dados: valores ausentes e duplicatas (documentação pandas)

75 min
Aula 3 de 5

Limpeza e Pré-processamento de Dados: Valores Ausentes e Duplicatas com Pandas 🧹✨

Olá, futuros cientistas de dados! Sejam bem-vindos à nossa aula prática sobre um dos passos mais cruciais no pipeline de Machine Learning: a limpeza e pré-processamento de dados. Dados "sujos" ou incompletos podem levar a modelos com baixo desempenho e conclusões erradas. Nesta aula, vamos focar em duas das impurezas mais comuns: valores ausentes e duplicatas, utilizando a poderosa biblioteca Pandas.


🎯 Objetivos da Aula

Ao final desta aula, você será capaz de:

  • Identificar valores ausentes (NaN, None) em um DataFrame.
  • Aplicar diferentes estratégias para tratar valores ausentes (remoção e preenchimento).
  • Detectar linhas duplicadas em um DataFrame.
  • Remover linhas duplicadas de forma eficiente.
  • Compreender a importância dessas etapas para a qualidade dos dados e do modelo.

1. Introdução: Por que Limpar os Dados? 🤔

Imagine que você está construindo uma casa. Você começaria a construir as paredes e o telhado em cima de um terreno cheio de lixo e buracos? Provavelmente não! Você primeiro limparia e nivelaria o terreno para garantir uma base sólida.

Com Machine Learning, é a mesma lógica! Os dados são a "base" do seu modelo. Se os dados estiverem com erros, incompletos ou inconsistentes, seu modelo será fraco e suas previsões, imprecisas.

Nesta aula, vamos abordar:

  • Valores Ausentes (Missing Values): Dados que simplesmente não estão lá. Podem ser representados por NaN (Not a Number), None, null, etc.
  • Duplicatas (Duplicates): Linhas ou registros que são idênticos ou muito semelhantes, representando a mesma informação mais de uma vez.

Vamos usar a biblioteca Pandas, que é a ferramenta padrão em Python para manipulação e análise de dados.


2. Lidando com Valores Ausentes (Missing Values) 🕵️‍♀️

Valores ausentes são um problema comum. Eles podem surgir por diversos motivos: erros de coleta, dados não aplicáveis a um registro específico, falhas no sistema, etc. O Pandas representa valores ausentes principalmente como NaN (Not a Number) para dados numéricos e None para objetos (strings, etc.).

2.1. Identificando Valores Ausentes

A primeira etapa é saber onde estão os "buracos" nos seus dados.

Vamos criar um DataFrame de exemplo para demonstrar:

import pandas as pd
import numpy as np
 
# Criando um DataFrame de exemplo com valores ausentes
data = {
    'ID': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    'Nome': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Heidi', 'Ivan', 'Judy'],
    'Idade': [24, 30, np.nan, 28, 35, 40, np.nan, 29, 31, 26],
    'Cidade': ['São Paulo', 'Rio de Janeiro', 'Belo Horizonte', 'Curitiba', 'Porto Alegre', np.nan, 'Recife', 'Salvador', 'Fortaleza', 'Natal'],
    'Salario': [5000, 6000, 7500, np.nan, 8000, 9000, 6500, 7000, np.nan, 5500]
}
df = pd.DataFrame(data)
 
print("DataFrame Original:")
print(df)

Saída esperada:

DataFrame Original:
   ID     Nome  Idade          Cidade  Salario
0   1    Alice   24.0       São Paulo   5000.0
1   2      Bob   30.0  Rio de Janeiro   6000.0
2   3  Charlie    NaN  Belo Horizonte   7500.0
3   4    David   28.0        Curitiba      NaN
4   5      Eve   35.0    Porto Alegre   8000.0
5   6    Frank   40.0             NaN   9000.0
6   7    Grace    NaN          Recife   6500.0
7   8    Heidi   29.0        Salvador   7000.0
8   9     Ivan   31.0       Fortaleza      NaN
9  10     Judy   26.0           Natal   5500.0

2.1.1. df.isnull() ou df.isna()

Esses métodos retornam um DataFrame booleano com True onde há um valor ausente e False onde há um valor presente.

# Verificando valores ausentes
print("\nDataFrame com valores ausentes (True/False):")
print(df.isnull())

2.1.2. df.isnull().sum()

Para uma visão mais útil, podemos somar os Trues por coluna, obtendo a contagem de valores ausentes em cada uma.

# Contagem de valores ausentes por coluna
print("\nContagem de valores ausentes por coluna:")
print(df.isnull().sum())

Saída esperada:

Contagem de valores ausentes por coluna:
ID         0
Nome       0
Idade      2
Cidade     1
Salario    2
dtype: int64

2.1.3. df.info()

O método info() fornece um resumo conciso do DataFrame, incluindo o número de valores não-nulos por coluna, o que indiretamente nos ajuda a ver onde faltam dados.

# Informações gerais do DataFrame
print("\nInformações do DataFrame:")
df.info()

Saída esperada (parcial, focando nos non-nulls):

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 5 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   ID       10 non-null     int64  
 1   Nome     10 non-null     object 
 2   Idade    8 non-null      float64
 3   Cidade   9 non-null      object 
 4   Salario  8 non-null      float64
dtypes: float64(2), int64(1), object(2)
memory usage: 528.0+ bytes

2.2. Tratando Valores Ausentes

Uma vez identificados, precisamos decidir como lidar com eles. As duas estratégias principais são:

  1. Remoção: Excluir linhas ou colunas que contêm valores ausentes.
  2. Preenchimento (Imputação): Substituir os valores ausentes por algum valor.

2.2.1. Removendo Valores Ausentes com df.dropna()

Este método é útil quando a quantidade de dados ausentes é pequena ou quando a remoção não causaria perda significativa de informação.

  • axis: 0 para linhas (padrão), 1 para colunas.
  • how:
    • 'any' (padrão): remove a linha/coluna se qualquer valor for NaN.
    • 'all': remove a linha/coluna se todos os valores forem NaN.
  • subset: lista de colunas para considerar ao procurar NaNs.
  • inplace: True para modificar o DataFrame original.
# Exemplo 1: Remover linhas com QUALQUER valor ausente
df_dropped_any = df.dropna()
print("\nDataFrame após remover linhas com QUALQUER valor ausente:")
print(df_dropped_any)
 
# Exemplo 2: Remover linhas onde 'Idade' ou 'Salario' são ausentes
df_dropped_subset = df.dropna(subset=['Idade', 'Salario'])
print("\nDataFrame após remover linhas com 'Idade' ou 'Salario' ausentes:")
print(df_dropped_subset)
 
# Exemplo 3: Remover colunas com QUALQUER valor ausente (muito agressivo para este DF)
# df_dropped_cols = df.dropna(axis=1)
# print("\nDataFrame após remover colunas com QUALQUER valor ausente:")
# print(df_dropped_cols)

Saída esperada (Exemplo 1):

DataFrame após remover linhas com QUALQUER valor ausente:
   ID   Nome  Idade          Cidade  Salario
0   1  Alice   24.0       São Paulo   5000.0
1   2    Bob   30.0  Rio de Janeiro   6000.0
4   5    Eve   35.0    Porto Alegre   8000.0
7   8  Heidi   29.0        Salvador   7000.0
9  10   Judy   26.0           Natal   5500.0

2.2.2. Preenchendo Valores Ausentes com df.fillna()

A imputação é a técnica de substituir os valores ausentes por um valor estimado. A escolha do valor de preenchimento depende do contexto e do tipo de dados.

  • value: O valor a ser usado para preencher os NaNs.
  • method:
    • 'ffill' (forward fill): Propaga o último valor válido para frente.
    • 'bfill' (backward fill): Propaga o próximo valor válido para trás.
  • axis: 0 para preencher por coluna (padrão), 1 para preencher por linha.
  • limit: Máximo de NaNs consecutivos a serem preenchidos (útil com ffill/bfill).
  • inplace: True para modificar o DataFrame original.
# Vamos recriar o DataFrame para cada exemplo de fillna para não acumular modificações
df_fillna = pd.DataFrame(data)
 
# Exemplo 1: Preencher valores ausentes em 'Idade' com a média da coluna
media_idade = df_fillna['Idade'].mean()
df_fillna['Idade'] = df_fillna['Idade'].fillna(media_idade)
print(f"\nMédia da Idade: {media_idade:.2f}")
print("\nDataFrame após preencher 'Idade' com a média:")
print(df_fillna)
 
# Exemplo 2: Preencher valores ausentes em 'Salario' com a mediana da coluna
mediana_salario = df_fillna['Salario'].median()
df_fillna['Salario'] = df_fillna['Salario'].fillna(mediana_salario)
print(f"\nMediana do Salário: {mediana_salario:.2f}")
print("\nDataFrame após preencher 'Salario' com a mediana:")
print(df_fillna)
 
# Exemplo 3: Preencher valores ausentes em 'Cidade' com um valor constante (ex: 'Desconhecida')
df_fillna['Cidade'] = df_fillna['Cidade'].fillna('Desconhecida')
print("\nDataFrame após preencher 'Cidade' com 'Desconhecida':")
print(df_fillna)
 
# Exemplo 4: Preencher usando forward fill (ffill)
df_ffill = pd.DataFrame(data)
df_ffill['Salario'] = df_ffill['Salario'].fillna(method='ffill')
print("\nDataFrame após preencher 'Salario' com ffill:")
print(df_ffill)
 
# Exemplo 5: Preencher usando backward fill (bfill)
df_bfill = pd.DataFrame(data)
df_bfill['Idade'] = df_bfill['Idade'].fillna(method='bfill')
print("\nDataFrame após preencher 'Idade' com bfill:")
print(df_bfill)

Saída esperada (Exemplo 1, 2, 3 combinados):

Média da Idade: 30.38

DataFrame após preencher 'Idade' com a média:
   ID     Nome      Idade          Cidade  Salario
0   1    Alice  24.000000       São Paulo   5000.0
1   2      Bob  30.000000  Rio de Janeiro   6000.0
2   3  Charlie  30.375000  Belo Horizonte   7500.0
3   4    David  28.000000        Curitiba      NaN
4   5      Eve  35.000000    Porto Alegre   8000.0
5   6    Frank  40.000000             NaN   9000.0
6   7    Grace  30.375000          Recife   6500.0
7   8    Heidi  29.000000        Salvador   7000.0
8   9     Ivan  31.000000       Fortaleza      NaN
9  10     Judy  26.000000           Natal   5500.0

Mediana do Salário: 6750.00

DataFrame após preencher 'Salario' com a mediana:
   ID     Nome      Idade          Cidade  Salario
0   1    Alice  24.000000       São Paulo   5000.0
1   2      Bob  30.000000  Rio de Janeiro   6000.0
2   3  Charlie  30.375000  Belo Horizonte   7500.0
3   4    David  28.000000        Curitiba   6750.0
4   5      Eve  35.000000    Porto Alegre   8000.0
5   6    Frank  40.000000             NaN   9000.0
6   7    Grace  30.375000          Recife   6500.0
7   8    Heidi  29.000000        Salvador   7000.0
8   9     Ivan  31.000000       Fortaleza   6750.0
9  10     Judy  26.000000           Natal   5500.0

DataFrame após preencher 'Cidade' com 'Desconhecida':
   ID     Nome      Idade          Cidade  Salario
0   1    Alice  24.000000       São Paulo   5000.0
1   2      Bob  30.000000  Rio de Janeiro   6000.0
2   3  Charlie  30.375000  Belo Horizonte   7500.0
3   4    David  28.000000        Curitiba   6750.0
4   5      Eve  35.000000    Porto Alegre   8000.0
5   6    Frank  40.000000  Desconhecida   9000.0
6   7    Grace  30.375000          Recife   6500.0
7   8    Heidi  29.000000        Salvador   7000.0
8   9     Ivan  31.000000       Fortaleza   6750.0
9  10     Judy  26.000000           Natal   5500.0

3. Lidando com Duplicatas 👯‍♀️

Registros duplicados podem distorcer suas análises e treinar seu modelo com informações redundantes, levando a resultados enviesados.

3.1. Identificando Duplicatas

Vamos criar um novo DataFrame com algumas linhas duplicadas para testar.

# Criando um DataFrame de exemplo com duplicatas
data_dup = {
    'Nome': ['Ana', 'Bruno', 'Carlos', 'Ana', 'Daniela', 'Bruno', 'Eduardo'],
    'Idade': [25, 30, 22, 25, 28, 30, 35],
    'Cidade': ['São Paulo', 'Rio', 'Belo Horizonte', 'São Paulo', 'Curitiba', 'Rio', 'Porto Alegre'],
    'Pontuacao': [85, 90, 78, 85, 92, 90, 88]
}
df_dup = pd.DataFrame(data_dup)
 
print("DataFrame com Duplicatas:")
print(df_dup)

Saída esperada:

DataFrame com Duplicatas:
      Nome  Idade          Cidade  Pontuacao
0      Ana     25       São Paulo         85
1    Bruno     30             Rio         90
2   Carlos     22  Belo Horizonte         78
3      Ana     25       São Paulo         85
4  Daniela     28        Curitiba         92
5    Bruno     30             Rio         90
6  Eduardo     35    Porto Alegre         88

3.1.1. df.duplicated()

Este método retorna uma Série booleana indicando se cada linha é uma duplicata de uma linha anterior. Por padrão, ele considera todas as colunas.

  • subset: Lista de nomes de colunas para considerar na identificação de duplicatas.
  • keep:
    • 'first' (padrão): Marca todas as duplicatas como True, exceto a primeira ocorrência.
    • 'last': Marca todas as duplicatas como True, exceto a última ocorrência.
    • False: Marca todas as ocorrências de linhas duplicadas como True.
# Identificando duplicatas
print("\nLinhas duplicadas (True = duplicata, keep='first'):")
print(df_dup.duplicated())
 
# Contagem de duplicatas
print(f"\nTotal de linhas duplicadas (keep='first'): {df_dup.duplicated().sum()}")
 
# Identificando duplicatas considerando apenas 'Nome' e 'Cidade'
print("\nLinhas duplicadas (considerando 'Nome' e 'Cidade', keep='first'):")
print(df_dup.duplicated(subset=['Nome', 'Cidade']))
 
# Identificando TODAS as ocorrências de duplicatas
print("\nTodas as ocorrências de linhas duplicadas (keep=False):")
print(df_dup[df_dup.duplicated(keep=False)])

Saída esperada (parcial):

Linhas duplicadas (True = duplicata, keep='first'):
0    False
1    False
2    False
3     True
4    False
5     True
6    False
dtype: bool

Total de linhas duplicadas (keep='first'): 2

3.2. Removendo Duplicatas com df.drop_duplicates()

Uma vez identificadas, a remoção é direta. Este método remove as linhas duplicadas do DataFrame.

  • Os parâmetros subset, keep e inplace funcionam da mesma forma que em duplicated().
# Exemplo 1: Remover duplicatas, mantendo a primeira ocorrência (padrão)
df_unique_first = df_dup.drop_duplicates()
print("\nDataFrame após remover duplicatas (keep='first'):")
print(df_unique_first)
 
# Exemplo 2: Remover duplicatas, mantendo a última ocorrência
df_unique_last = df_dup.drop_duplicates(keep='last')
print("\nDataFrame após remover duplicatas (keep='last'):")
print(df_unique_last)
 
# Exemplo 3: Remover duplicatas considerando apenas 'Nome' e 'Idade'
df_unique_subset = df_dup.drop_duplicates(subset=['Nome', 'Idade'])
print("\nDataFrame após remover duplicatas (subset=['Nome', 'Idade']):")
print(df_unique_subset)

Saída esperada (Exemplo 1):

DataFrame após remover duplicatas (keep='first'):
      Nome  Idade          Cidade  Pontuacao
0      Ana     25       São Paulo         85
1    Bruno     30             Rio         90
2   Carlos     22  Belo Horizonte         78
4  Daniela     28        Curitiba         92
6  Eduardo     35    Porto Alegre         88

4. Exercícios Práticos: Mão na Massa! 🚀

Agora é a sua vez de aplicar o que aprendemos. Você receberá um conjunto de dados simulado e terá que limpá-lo.

Cenário: Dados de Cadastro de Clientes 📊

Você recebeu um DataFrame com informações de clientes, mas ele está com alguns problemas: valores ausentes e registros duplicados. Sua tarefa é limpar esses dados.

import pandas as pd
import numpy as np
import random
 
# Gerando dados de exemplo com mais complexidade
np.random.seed(42)
random.seed(42)
 
nomes = ['João', 'Maria', 'Pedro', 'Ana', 'Lucas', 'Mariana', 'Fernando', 'Paula', 'Gustavo', 'Julia']
sobrenomes = ['Silva', 'Santos', 'Oliveira', 'Souza', 'Pereira']
cidades = ['São Paulo', 'Rio de Janeiro', 'Belo Horizonte', 'Curitiba', 'Porto Alegre', 'Salvador']
status = ['Ativo', 'Inativo', 'Pendente']
 
data_complex = []
for i in range(20):
    nome_completo = f"{random.choice(nomes)} {random.choice(sobrenomes)}"
    idade = random.randint(18, 60) if random.random() > 0.1 else np.nan # 10% de chance de NaN
    cidade = random.choice(cidades) if random.random() > 0.05 else np.nan # 5% de chance de NaN
    email = f"{nome_completo.replace(' ', '.').lower()}@{random.choice(['email.com', 'mail.org'])}"
    renda = round(random.uniform(2000, 15000), 2) if random.random() > 0.15 else np.nan # 15% de chance de NaN
    status_cliente = random.choice(status)
 
    data_complex.append([nome_completo, idade, cidade, email, renda, status_cliente])
 
# Adicionar algumas duplicatas intencionais
data_complex.append(['João Silva', 25, 'São Paulo', 'joao.silva@email.com', 4500.00, 'Ativo'])
data_complex.append(['Maria Santos', 30, 'Rio de Janeiro', 'maria.santos@mail.org', 6000.00, 'Ativo'])
data_complex.append(['João Silva', 25, 'São Paulo', 'joao.silva@email.com', 4500.00, 'Ativo']) # Duplicata exata
data_complex.append(['Pedro Oliveira', 22, 'Belo Horizonte', 'pedro.oliveira@email.com', 3800.00, 'Pendente'])
data_complex.append(['Pedro Oliveira', 22, 'Belo Horizonte', 'pedro.oliveira@email.com', 3800.00, 'Pendente']) # Duplicata exata
 
df_clientes = pd.DataFrame(data_complex, columns=['Nome Completo', 'Idade', 'Cidade', 'Email', 'Renda', 'Status'])
 
print("DataFrame de Clientes Original:")
print(df_clientes)
print("\nInformações do DataFrame Original:")
df_clientes.info()
print("\nContagem de valores ausentes no DataFrame Original:")
print(df_clientes.isnull().sum())
print("\nContagem de duplicatas no DataFrame Original:")
print(df_clientes.duplicated().sum())

Suas Tarefas:

Utilize o DataFrame df_clientes para realizar as seguintes operações. Lembre-se de criar cópias do DataFrame se quiser testar diferentes abordagens sem alterar o original (df_limpo = df_clientes.copy()).

📝 Task List:

  • 1. Identificação Inicial:

    • Verifique novamente a contagem de valores ausentes por coluna.
    • Verifique a contagem de linhas duplicadas (considerando todas as colunas).
  • 2. Tratamento de Valores Ausentes:

    • Para a coluna 'Idade', preencha os valores ausentes com a mediana da coluna.
    • Para a coluna 'Renda', preencha os valores ausentes com a média da coluna.
    • Para a coluna 'Cidade', preencha os valores ausentes com o valor 'Não Informado'.
  • 3. Tratamento de Duplicatas:

    • Remova as linhas duplicadas do DataFrame, mantendo apenas a primeira ocorrência de cada registro único.
  • 4. Verificação Final:

    • Após todas as operações, imprima as informações (.info()) do DataFrame limpo.
    • Imprima a contagem final de valores ausentes.
    • Imprima a contagem final de duplicatas.
    • Exiba as 5 primeiras linhas do DataFrame final.
# --- Seu código começa aqui ---
# Faça uma cópia do DataFrame original para trabalhar
df_limpo = df_clientes.copy()
 
# 1. Identificação Inicial
print("\n--- Identificação Inicial ---")
# ... (seu código aqui) ...
print("Contagem de valores ausentes por coluna (antes da limpeza):")
print(df_limpo.isnull().sum())
print("\nContagem de linhas duplicadas (antes da limpeza):")
print(df_limpo.duplicated().sum())
 
# 2. Tratamento de Valores Ausentes
print("\n--- Tratamento de Valores Ausentes ---")
# Idade: preencher com a mediana
mediana_idade = df_limpo['Idade'].median()
df_limpo['Idade'] = df_limpo['Idade'].fillna(mediana_idade)
print(f"Idade preenchida com mediana: {mediana_idade:.2f}")
 
# Renda: preencher com a média
media_renda = df_limpo['Renda'].mean()
df_limpo['Renda'] = df_limpo['Renda'].fillna(media_renda)
print(f"Renda preenchida com média: {media_renda:.2f}")
 
# Cidade: preencher com 'Não Informado'
df_limpo['Cidade'] = df_limpo['Cidade'].fillna('Não Informado')
print("Cidade preenchida com 'Não Informado'")
 
# 3. Tratamento de Duplicatas
print("\n--- Tratamento de Duplicatas ---")
# Remover duplicatas, mantendo a primeira ocorrência
linhas_antes_dup = len(df_limpo)
df_limpo.drop_duplicates(inplace=True)
linhas_depois_dup = len(df_limpo)
print(f"Foram removidas {linhas_antes_dup - linhas_depois_dup} linhas duplicadas.")
 
# 4. Verificação Final
print("\n--- Verificação Final ---")
print("\nInformações do DataFrame Limpo:")
df_limpo.info()
print("\nContagem de valores ausentes no DataFrame Limpo:")
print(df_limpo.isnull().sum())
print("\nContagem de duplicatas no DataFrame Limpo:")
print(df_limpo.duplicated().sum())
print("\nPrimeiras 5 linhas do DataFrame Limpo:")
print(df_limpo.head())
# --- Seu código termina aqui ---

5. Resumo e Próximos Passos 🚀

Parabéns! Você concluiu uma etapa fundamental no pré-processamento de dados.

Nesta aula, você aprendeu a:

  • Identificar e quantificar valores ausentes (.isnull().sum(), .info()).
  • Tratar valores ausentes removendo linhas/colunas (.dropna()) ou preenchendo-os com estratégias como média, mediana, moda ou valores específicos (.fillna()).
  • Identificar e remover linhas duplicadas (.duplicated(), .drop_duplicates()).

A limpeza de dados é um processo iterativo e contextual. A melhor abordagem para lidar com valores ausentes ou duplicatas sempre dependerá do seu conjunto de dados específico e do problema de Machine Learning que você está tentando resolver.

O que vem a seguir?

No próximo módulo, exploraremos outras técnicas de pré-processamento, como:

  • Codificação de Variáveis Categóricas: Transformar categorias em números.
  • Normalização e Escalonamento de Dados: Ajustar a escala de variáveis numéricas.
  • Tratamento de Outliers: Identificar e lidar com valores extremos.

Continue praticando e explorando a documentação do Pandas para aprofundar seus conhecimentos! Até a próxima! 👋

© 2025 Escola All Dev. Todos os direitos reservados.

Limpeza e Pré-processamento de Dados: Valores Ausentes e Duplicatas (Documentação Pandas) - Fundamentos do Machine Learning com Python | escola.all.dev.br