Machine Learning: Processamento de Dados com Pandas
Última atualização: 2026-08-26
Pandas é o canivete suíço da ciência de dados — carregar, limpar, transformar e agregar, tudo em uma única ferramenta.
1. O que você vai aprender
- Operações essenciais de DataFrame e Series: criar, indexar, selecionar e filtrar
- Limpeza de dados na prática: lidar com valores ausentes, remover duplicatas e detectar outliers
- Transformação de dados: apply/map/transform, agregação com groupby e pivot_table
- Mesclar várias fontes de dados: merge/join/concat — combinando os pedidos dos EUA da Alice com os pedidos da China do Bob para análise
- Processamento de séries temporais: parsing de datas, reamostragem e janelas rolantes
2. A história real de uma analista de dados
(1) O problema: dados CSV bagunçados, com a limpeza consumindo 80% do tempo
O Bob conseguiu os dados brutos de pedidos do SalesPredict: dos 200 mil registros, 5% dos valores estavam nulos, 3% eram pedidos duplicados e os formatos de ID de usuário eram inconsistentes. Os dados dos EUA da Alice usavam nomes de campos diferentes dos do Bob, e os valores em EUR do Charlie precisavam de conversão de moeda. A limpeza de dados se tornou o maior gargalo de tempo no projeto de ML.
(2) A solução com Pandas
O Pandas oferece uma cadeia de ferramentas completa de limpeza de dados — tratamento de valores ausentes, detecção de duplicatas, conversão de tipos e mesclagem de várias fontes — transformando a limpeza de dados de trabalho manual em código reproduzível.
import pandas as pd
# Carregar e limpar em um pipeline
df = pd.read_csv("orders.csv")
df_clean = (df
.drop_duplicates()
.fillna({"amount": df["amount"].median()})
.assign(amount_usd=lambda x: x["amount"] * x["exchange_rate"])
)
print(f"Linhas limpas: {len(df_clean)}, Original: {len(df)}")
(3) O resultado: tempo de limpeza caiu de 3 dias para 30 minutos
Depois que o Bob codificou o fluxo de limpeza de dados com Pandas, o que antes levava 3 dias de limpeza manual passou a ser 30 minutos de execução automatizada — e o mesmo pipeline pôde ser reutilizado sempre que chegassem dados novos.
3. Operações essenciais de DataFrame e Series
(1) Criando um DataFrame
import pandas as pd
import numpy as np
# A partir de um dicionário
sales_data = pd.DataFrame({
"date": pd.date_range("2024-01-01", periods=5, freq="D"),
"category": ["Eletrônicos", "Roupas", "Alimentos", "Livros", "Casa"],
"revenue_k_usd": [250, 145, 90, 70, 400],
"orders": [1200, 800, 3000, 500, 600],
})
# A partir de um array NumPy
arr = np.random.rand(3, 4)
df_from_arr = pd.DataFrame(arr, columns=["A", "B", "C", "D"])
print(sales_data)
print(f"\nShape: {sales_data.shape}")
print(f"Tipos de dados:\n{sales_data.dtypes}")
▶ Exemplo: Carregando dados reais do SalesPredict
import pandas as pd
# Carregar CSV com dicas de tipo
df = pd.read_csv("sales_data.csv", parse_dates=["order_date"])
# Visão geral rápida
print(f"Shape: {df.shape}")
print(f"\nPrimeiras 5 linhas:\n{df.head()}")
print(f"\nTipos de dados:\n{df.dtypes}")
print(f"\nUso de memória:\n{df.memory_usage(deep=True)}")
Saída:
# Executado com sucesso
(2) Seleção e filtragem
▶ Exemplo: Várias formas de selecionar dados
import pandas as pd
df = pd.DataFrame({
"product": ["Notebook", "Celular", "Tablet", "Monitor", "Teclado"],
"category": ["Eletrônicos", "Eletrônicos", "Eletrônicos", "Eletrônicos", "Acessórios"],
"price": [999, 699, 399, 299, 49],
"stock": [50, 200, 100, 80, 500],
})
# Seleção de coluna
prices = df["price"] # Series
subset = df[["product", "price"]] # DataFrame
# Seleção de linhas com loc (rótulo) e iloc (posição)
row = df.loc[0] # Primeira linha pelo rótulo
rows = df.iloc[1:3] # Linhas 1-2 pela posição
# Filtragem condicional
expensive = df[df["price"] > 400]
elec_cheap = df[(df["category"] == "Eletrônicos") & (df["price"] < 500)]
print(f"Itens caros:\n{expensive}")
Saída:
# Executado com sucesso
| Método de seleção | Sintaxe | Caso de uso |
|---|---|---|
| Seleção de coluna | df["col"] / df[["c1","c2"]] |
Obter uma ou mais colunas |
| loc | df.loc[linha, coluna] |
Indexar por rótulo |
| iloc | df.iloc[r, c] |
Indexar por posição |
| Filtragem condicional | df[df["col"] > val] |
Filtrar por condição |
| query | df.query("price > 400") |
Filtragem no estilo SQL |
4. Limpeza de dados na prática
A limpeza de dados em Pandas é um processo de pipeline — cada etapa lida com uma classe de problema, transformando progressivamente dados sujos em dados limpos:
graph LR
RAW[Dados Brutos<br/>5% ausentes, 3% dup.] --> DEDUP[Deduplicar<br/>drop_duplicates]
DEDUP --> FILL[Preencher ausentes<br/>fillna mediana/moda]
FILL --> OUTLIER[Remover outliers<br/>corte por IQR]
OUTLIER --> CONVERT[Converter tipos<br/>astype / to_datetime]
CONVERT --> CLEAN[Dados Limpos<br/>Prontos para ML]
(1) Tratando valores ausentes
▶ Exemplo: Diagnosticando e tratando valores ausentes no SalesPredict
import pandas as pd
import numpy as np
# Simular dados com valores ausentes
df = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004, 1005, 1006],
"amount_usd": [150, np.nan, 280, np.nan, 95, 320],
"category": ["Eletrônicos", "Roupas", np.nan, "Alimentos", "Livros", "Eletrônicos"],
"user_rating": [4.5, 3.8, np.nan, 4.2, np.nan, 5.0],
})
# Diagnosticar valores ausentes
print(f"Contagem de ausentes:\n{df.isnull().sum()}")
print(f"\nProporção de ausentes:\n{df.isnull().mean().round(3)}")
# Estratégia 1: Remover linhas com qualquer valor ausente
df_drop = df.dropna()
# Estratégia 2: Preencher com mediana/moda
df_fill = df.fillna({
"amount_usd": df["amount_usd"].median(),
"category": df["category"].mode()[0],
"user_rating": df["user_rating"].mean(),
})
print(f"\nDados preenchidos:\n{df_fill}")
Saída:
# Executado com sucesso
| Estratégia | Método | Caso de uso | Risco |
|---|---|---|---|
| Remover | dropna() |
Proporção de ausentes < 5% | Perda de informação |
| Imputação pela média | fillna(df["col"].mean()) |
Numérico, aproximadamente normal | Reduz a variância |
| Imputação pela mediana | fillna(df["col"].median()) |
Numérico, com outliers | Estimativa conservadora |
| Imputação pela moda | fillna(df["col"].mode()[0]) |
Categórico | Pode super-representar a maioria |
| Preenchimento forward/backward | fillna(method="ffill") |
Séries temporais | Propaga viés |
(2) Duplicatas e outliers
▶ Exemplo: Detectando e tratando duplicatas e outliers
import pandas as pd
import numpy as np
df = pd.DataFrame({
"order_id": [1001, 1002, 1002, 1003, 1004],
"amount": [150, 280, 280, 95000, 95],
})
# Detecção de duplicatas
dupes = df.duplicated(subset=["order_id", "amount"])
print(f"Linhas duplicadas:\n{df[dupes]}")
# Remover duplicatas
df_clean = df.drop_duplicates(subset=["order_id"], keep="first")
# Detecção de outliers pelo método IQR
def detect_outliers_iqr(series, factor=1.5):
q1, q3 = series.quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - factor * iqr, q3 + factor * iqr
return (series < lower) | (series > upper)
outlier_mask = detect_outliers_iqr(df_clean["amount"])
print(f"\nOutliers:\n{df_clean[outlier_mask]}")
# Capear outliers no percentil 99
cap = df_clean["amount"].quantile(0.99)
df_capped = df_clean.assign(amount=df_clean["amount"].clip(upper=cap))
Saída:
# Funções definidas com sucesso
5. Transformação e agregação de dados
(1) apply/map/transform
▶ Exemplo: Transformação de features
import pandas as pd
df = pd.DataFrame({
"product": ["Notebook", "Celular", "Tablet"],
"price_usd": [999, 699, 399],
"cost_usd": [600, 350, 180],
})
# map: transformação element-wise em uma Series
df["price_level"] = df["price_usd"].map(
lambda x: "Alto" if x > 700 else ("Médio" if x > 400 else "Baixo")
)
# apply: transformação linha/coluna
df["margin_pct"] = df.apply(
lambda row: (row["price_usd"] - row["cost_usd"]) / row["price_usd"] * 100,
axis=1
)
# transform: saída com o mesmo shape
df["price_zscore"] = df["price_usd"].transform(
lambda x: (x - x.mean()) / x.std()
)
print(df)
Saída:
# Executado com sucesso
(2) Agregação por grupo com groupby
▶ Exemplo: Agregando métricas de vendas por categoria
import pandas as pd
df = pd.DataFrame({
"category": ["Elec", "Elec", "Roupa", "Roupa", "Alim", "Alim"],
"month": ["Jan", "Fev", "Jan", "Fev", "Jan", "Fev"],
"revenue_k": [250, 260, 145, 150, 90, 95],
"orders": [1200, 1250, 800, 820, 3000, 3100],
})
# Agregação única
cat_revenue = df.groupby("category")["revenue_k"].sum()
print(f"Receita por categoria:\n{cat_revenue}")
# Várias agregações
cat_stats = df.groupby("category").agg({
"revenue_k": ["sum", "mean", "std"],
"orders": ["sum", "mean"],
})
print(f"\nEstatísticas por categoria:\n{cat_stats}")
# Agregações nomeadas
cat_named = df.groupby("category").agg(
total_revenue=("revenue_k", "sum"),
avg_revenue=("revenue_k", "mean"),
total_orders=("orders", "sum"),
avg_order_value=("revenue_k", lambda x: x.sum() / df.loc[x.index, "orders"].sum() * 1000),
)
Saída:
# Executado com sucesso
(3) Tabelas dinâmicas com pivot_table
▶ Exemplo: Tabela dinâmica de vendas mensais por categoria
import pandas as pd
df = pd.DataFrame({
"category": ["Elec"]*3 + ["Roupa"]*3 + ["Alim"]*3,
"month": ["Jan", "Fev", "Mar"]*3,
"revenue_k": [250, 260, 270, 145, 150, 155, 90, 95, 100],
})
# Pivot: categorias como linhas, meses como colunas
pivot = df.pivot_table(
values="revenue_k",
index="category",
columns="month",
aggfunc="sum",
margins=True, # Adicionar totais de linha/coluna
)
print(pivot)
Saída:
# Executado com sucesso
| Dimensão | groupby | pivot_table |
|---|---|---|
| Formato de saída | Longo | Largo |
| Várias métricas | ✅ agg em várias colunas | ✅ values em várias colunas |
| Subtotais | ❌ Exige trabalho manual | ✅ margins=True |
| Flexibilidade | Alta (qualquer agg) | Média (aggfunc fixo) |
6. Mesclando várias fontes de dados e séries temporais
(1) Operações de merge
▶ Exemplo: Mesclando os dados dos EUA da Alice + os dados da China do Bob
import pandas as pd
# Pedidos dos EUA da Alice
us_orders = pd.DataFrame({
"order_id": ["US001", "US002", "US003"],
"amount_usd": [150, 280, 95],
"product": ["Notebook", "Celular", "Livro"],
})
# Pedidos da China do Bob (precisam de conversão de moeda)
cn_orders = pd.DataFrame({
"order_id": ["CN001", "CN002"],
"amount_cny": [1200, 3500],
"product": ["Celular", "Notebook"],
})
# Concat vertical (empilhar linhas)
cn_orders_usd = cn_orders.assign(
amount_usd=cn_orders["amount_cny"] * 0.14 # CNY para USD
).drop(columns=["amount_cny"])
all_orders = pd.concat([us_orders, cn_orders_usd], ignore_index=True)
print(f"Pedidos combinados:\n{all_orders}")
# Merge com catálogo de produtos
catalog = pd.DataFrame({
"product": ["Notebook", "Celular", "Livro", "Tablet"],
"category": ["Eletrônicos", "Eletrônicos", "Livros", "Eletrônicos"],
"margin_pct": [35, 45, 20, 30],
})
enriched = all_orders.merge(catalog, on="product", how="left")
print(f"\nPedidos enriquecidos:\n{enriched}")
Saída:
# Executado com sucesso
| Tipo de merge | Sintaxe | Equivalente SQL | Descrição |
|---|---|---|---|
| Inner | merge(how="inner") |
INNER JOIN | Interseção |
| Left | merge(how="left") |
LEFT JOIN | Mantém todas as linhas da tabela à esquerda |
| Outer | merge(how="outer") |
FULL JOIN | União |
| Concat | concat(axis=0) |
UNION | Empilha linhas verticalmente |
| Concat | concat(axis=1) |
— | Empilha colunas lado a lado |
(2) Processamento de séries temporais
▶ Exemplo: Reamostragem e estatísticas rolantes em dados diários do SalesPredict
import pandas as pd
import numpy as np
# Dados de vendas diárias
rng = pd.date_range("2024-01-01", periods=90, freq="D")
daily = pd.DataFrame({
"date": rng,
"revenue": np.random.normal(5000, 1000, 90).cumsum(),
}, index=rng)
# Reamostrar para mensal
monthly = daily["revenue"].resample("M").agg(["first", "last", "mean", "sum"])
print(f"Estatísticas mensais:\n{monthly}")
# Janela rolante: média móvel de 7 dias
daily["ma_7d"] = daily["revenue"].rolling(window=7).mean()
daily["ma_30d"] = daily["revenue"].rolling(window=30).mean()
# Variação percentual
daily["pct_change"] = daily["revenue"].pct_change()
print(f"\nÚltimas 5 linhas com estatísticas rolantes:\n{daily.tail()}")
Saída:
# Executado com sucesso
❓ Perguntas Frequentes
P: Qual a diferença entre loc e iloc? R: loc indexa pelo rótulo e inclui o endpoint; iloc indexa pela posição e exclui o endpoint. Por exemplo,
df.loc[0:3]retorna 4 linhas, enquantodf.iloc[0:3]retorna 3 linhas.
P: Como corrijo o SettingWithCopyWarning? R: É um aviso de atribuição encadeada. Use
.copy()para criar explicitamente uma cópia, ou substitua operações encadeadas por uma única atribuição com.loc[]. Por exemplo, usedf.loc[mask, "col"] = valueem vez dedf[mask]["col"] = value.
P: Como manter a coluna de agrupamento como uma coluna normal após o groupby? R: Use o parâmetro
as_index=False:df.groupby("category", as_index=False).agg(...), ou chame.reset_index()após o groupby.
P: O merge produz linhas extras inesperadas? R: Verifique se a chave do merge tem duplicatas. Use
df.duplicated(subset=chave).sum()para verificar. Se houver duplicatas, defina uma estratégia de retenção antes de mesclar (deduplicar ou agregar).
P: E se eu ficar sem memória em um conjunto de dados grande? R: Três estratégias: 1) Especifique dtypes para reduzir memória (por exemplo, int64 → int32); 2) Use o parâmetro
chunksizepara ler em partes; 3) Use a biblioteca Dask para conjuntos de dados muito grandes.
P: O parâmetro freq lança um erro em uma série temporal? R: Use
df.asfreq("D")para garantir uma frequência regular, ou usedf.resample("D").asfreq(). Séries temporais irregulares precisam ser reamostradas antes da análise.
📖 Resumo
- Um DataFrame é uma estrutura de dados tabular bidimensional, e uma Series é um array unidimensional; juntos formam o núcleo do Pandas
- Três estratégias principais para valores ausentes: remover (dropna), preencher (fillna) e interpolar (interpolate) — escolha conforme a situação
- groupby + agg lida com agregações por grupo, enquanto pivot_table produz pivôs em formato largo; cada um tem seu caso de uso
- merge realiza joins estilo SQL, e concat empilha linhas/colunas — preste atenção à escolha do parâmetro
how - Processamento de séries temporais:
parse_datespara interpretar datas,resamplepara reamostrar,rollingpara estatísticas rolantes epct_changepara calcular taxas de variação
📝 Exercícios
- Básico (Dificuldade ⭐): Carregue um arquivo CSV, use
info()edescribe()para obter uma visão geral e conte o número de valores ausentes. Dica:df.isnull().sum(). - Intermediário (Dificuldade ⭐⭐): Crie dois DataFrames (vendas dos EUA da Alice e vendas europeias do Charlie), una-os por produto com merge e calcule o total global de vendas de cada produto. Dica: una a unidade de valor para USD antes de mesclar.
- Desafio (Dificuldade ⭐⭐⭐): Em dados de vendas diárias, execute: 1) reamostre para semanal; 2) calcule uma média móvel de 4 semanas; 3) detecte outliers (pontos que se desviam mais de 2 desvios padrão da média móvel). Dica:
resample("W")+rolling(4)+ indexação booleana.
← Lição Anterior: Curso Relâmpago de NumPy | Próxima Lição: Visualização com Matplotlib e Seaborn →