Fundamentos do Machine Learning com Python
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)
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:
- Remoção: Excluir linhas ou colunas que contêm valores ausentes.
- 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:0para linhas (padrão),1para colunas.how:'any'(padrão): remove a linha/coluna se qualquer valor forNaN.'all': remove a linha/coluna se todos os valores foremNaN.
subset: lista de colunas para considerar ao procurarNaNs.inplace:Truepara 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 osNaNs.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:0para preencher por coluna (padrão),1para preencher por linha.limit: Máximo deNaNs consecutivos a serem preenchidos (útil comffill/bfill).inplace:Truepara 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 comoTrue, exceto a primeira ocorrência.'last': Marca todas as duplicatas comoTrue, exceto a última ocorrência.False: Marca todas as ocorrências de linhas duplicadas comoTrue.
# 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,keepeinplacefuncionam da mesma forma que emduplicated().
# 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.
- Após todas as operações, imprima as informações (
# --- 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! 👋