Análise exploratória de dados (EDA) é a etapa em que você conhece o dataset antes de modelar ou reportar. Estatísticas descritivas, distribuições, correlações, outliers e padrões temporais — sem essa fase, qualquer análise subsequente parte de premissas não verificadas. Este artigo cobre um fluxo de EDA completo e reutilizável.

Carregando e inspecionando o dataset

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("vendas_2026.csv", sep=";", decimal=",",
                 parse_dates=["data_pedido"], dayfirst=True)

# Visão geral:
print(df.shape)           # (linhas, colunas)
print(df.dtypes)          # tipos de cada coluna
print(df.head())          # primeiras 5 linhas
print(df.tail())          # últimas 5 linhas
print(df.sample(5))       # 5 linhas aleatórias — melhor que head() para amostras reais

Qualidade dos dados

# Nulos por coluna (quantidade e percentual):
nulos = pd.DataFrame({
    "qtd": df.isnull().sum(),
    "pct": (df.isnull().mean() * 100).round(2)
}).query("qtd > 0").sort_values("pct", ascending=False)
print(nulos)

# Duplicatas:
print(f"Linhas duplicadas: {df.duplicated().sum()}")

# Cardinalidade de colunas categóricas:
for col in df.select_dtypes("object").columns:
    print(f"{col}: {df[col].nunique()} valores únicos")
    print(df[col].value_counts().head(5))
    print()

Estatísticas descritivas

# Resumo numérico completo:
desc = df.describe().T
desc["cv"] = desc["std"] / desc["mean"]   # coeficiente de variação
print(desc)

# Percentis customizados:
print(df["valor"].quantile([0.01, 0.25, 0.5, 0.75, 0.90, 0.99]))

Análise de distribuição

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Histograma com KDE:
sns.histplot(df["valor"], kde=True, ax=axes[0], color="#4a148c")
axes[0].set_title("Distribuição do Valor do Pedido")

# Boxplot para detectar outliers:
sns.boxplot(y=df["valor"], ax=axes[1], color="#b39ddb")
axes[1].set_title("Outliers no Valor do Pedido")

plt.tight_layout()
plt.show()

Análise de correlação

numericas = df.select_dtypes(include="number")
correlacao = numericas.corr()

fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(correlacao, annot=True, fmt=".2f",
            cmap="RdYlGn", center=0, ax=ax)
ax.set_title("Correlação entre Variáveis Numéricas")
plt.tight_layout()
plt.show()

Séries temporais: tendência e sazonalidade

# Vendas mensais:
mensal = df.set_index("data_pedido")["valor"].resample("ME").sum()
mensal.plot(figsize=(12, 4), title="Receita Mensal", color="#4a148c")
plt.tight_layout()
plt.show()

# Crescimento mês a mês (%):
mensal_pct = mensal.pct_change() * 100
print(mensal_pct.round(1))

Análise por categoria

por_regiao = df.groupby("regiao")["valor"].agg(
    total="sum",
    media="mean",
    contagem="count"
).sort_values("total", ascending=False)
print(por_regiao)

# Boxplot por categoria:
fig, ax = plt.subplots(figsize=(10, 5))
df.boxplot(column="valor", by="regiao", ax=ax)
ax.set_title("Distribuição do Valor por Região")
plt.suptitle("")  # remove título automático do boxplot
plt.tight_layout()
plt.show()

Detecção de outliers com IQR

Q1 = df["valor"].quantile(0.25)
Q3 = df["valor"].quantile(0.75)
IQR = Q3 - Q1
limite_inf = Q1 - 1.5 * IQR
limite_sup = Q3 + 1.5 * IQR

outliers = df[(df["valor"] < limite_inf) | (df["valor"] > limite_sup)]
print(f"Outliers detectados: {len(outliers)} ({len(outliers)/len(df)*100:.1f}%)")

Perguntas frequentes

Devo remover outliers sempre?

Não automaticamente. Investigue primeiro: o outlier é um erro de digitação, um evento legítimo incomum, ou uma fraude? A decisão depende do contexto de negócio, não de uma regra estatística automática.

Qual biblioteca usar para EDA automatizada?

ydata-profiling (antigo pandas-profiling) gera um relatório HTML completo com uma linha: ProfileReport(df).to_file("eda.html"). Útil para uma visão rápida inicial, mas não substitui a análise guiada pelo entendimento do negócio.