📊 Guia de Estudos · Ciência de Dados

Análise de
Correlação

Da matemática às armadilhas ocultas — vários tópicos que você precisa saber sobre correlação, com código Python e visualizações avançadas.

📖 Leitura: ~25 min 🐍 Python 3.10+ 📐 Nível: Intermediário/Avançado
// 01

Fundamentos: O Que é Correlação de Verdade?

Correlação é uma das ferramentas mais usadas — e mais mal-interpretadas — em ciência de dados. No seu núcleo, ela mede a força e direção de uma associação linear entre duas variáveis. Mas há muito mais profundidade aqui do que a maioria dos tutoriais revela.

Matematicamente, correlação emerge da ideia de covariância normalizada: o quanto dois sinais "andam juntos", ajustado pela escala de cada um. Isso resolve um problema fundamental — a covariância bruta depende das unidades de medida, tornando impossível comparar relações entre diferentes pares de variáveis.

Definição precisa: Um coeficiente de correlação é qualquer medida escalar que quantifica a dependência estatística entre variáveis, com propriedades de invariância a transformações monotônicas (no caso de Spearman/Kendall) ou lineares (no caso de Pearson).

Por que correlação importa tanto?

Em sistemas reais — seja pecuária de corte, meteorologia, finanças ou saúde — raramente temos relações causais limpas. O que temos são padrões de co-variação que podem indicar causas subjacentes, permitir previsões e guiar decisões. A correlação é a porta de entrada para entender essas estruturas.

💡

Uma matriz de correlação é uma das visualizações mais poderosas em análise de dados: cada célula representa o coeficiente entre dois pares de variáveis. Vermelho = correlação positiva forte, Azul = correlação negativa forte. Esse tipo de visualização revela clusters de variáveis que "se comportam juntos" — como mostra o heatmap interativo na seção 7.

// 02

A Matemática por Trás da Correlação

1. Variância e Desvio Padrão

Antes de correlação, precisamos de variância — a medida de dispersão de uma variável em torno da sua média:

→ Variância Amostral
s²(X) = 1/(n-1) · Σᵢ (xᵢ − x̄)²

Por que (n−1) e não n? Isso se chama Correção de Bessel. Quando calculamos a variância de uma amostra, usamos a média amostral x̄ — que já foi estimada a partir dos mesmos dados. Isso "consome" um grau de liberdade: os n desvios (xᵢ − x̄) não são independentes, pois sua soma é sempre zero. Dividir por (n−1) compensa esse viés e garante que, em média, s² estime corretamente a variância da população. Dividindo por n, a variância amostral seria sistematicamente menor que a real — o que distorceria qualquer análise baseada nela.

2. Covariância

A covariância captura como duas variáveis variam juntas. Se X cresce quando Y cresce, a covariância é positiva; se uma cresce e a outra cai, é negativa.

→ Covariância Amostral
Cov(X, Y) = 1/(n-1) · Σᵢ (xᵢ − x̄)(yᵢ − ȳ)

⚠️ Problema da Covariância: Se X é medido em kg e Y em metros, Cov(X,Y) tem unidade kg·m — impossível comparar com Cov(A,B) em outras unidades. Por isso normalizamos.

3. Coeficiente de Pearson (r)

Pearson resolve o problema de unidades dividindo a covariância pelo produto dos desvios padrão:

→ Correlação de Pearson
r(X, Y) = Cov(X, Y) / (s(X) · s(Y))
= Σᵢ(xᵢ − x̄)(yᵢ − ȳ) / √[Σᵢ(xᵢ − x̄)² · Σᵢ(yᵢ − ȳ)²]

Resultado: r ∈ [−1, +1] — adimensional e comparável entre qualquer par de variáveis.

4. Interpretação Geométrica

Matematicamente, r é o cosseno do ângulo entre os vetores centralizados de X e Y no espaço ℝⁿ. Quando r = 1, os vetores são paralelos (mesma direção). Quando r = 0, são ortogonais (perpendiculares). Quando r = −1, são antiparalelos.

→ Interpretação Vetorial
r = cos(θ) = (X − x̄) · (Y − ȳ) / |X − x̄| · |Y − ȳ|

5. R² — Coeficiente de Determinação

O quadrado de r tem uma interpretação elegante: representa a proporção da variância de Y explicada por X em uma regressão linear simples.

💡 Exemplo prático: r = 0.70 → R² = 0.49. Significa que 49% da variação em Y pode ser "explicada" (no sentido estatístico) pela variação em X. Os outros 51% dependem de outras variáveis ou ruído.

6. Significância Estatística de r

Um r calculado pode ser grande por acaso, especialmente com amostras pequenas. Precisamos testar se r é estatisticamente significativo usando um teste-t:

→ Teste de Hipótese para r (H₀: ρ = 0)
t = r · √(n−2) / √(1 − r²)

Distribui-se como t de Student com (n−2) graus de liberdade sob H₀.

Python · Cálculo Manual de r e p-value
import numpy as np
from scipy import stats

# Dataset exemplo
x = np.array([2.1, 3.4, 4.7, 5.2, 6.8, 7.1, 8.3, 9.0])
y = np.array([1.8, 3.0, 5.2, 4.9, 7.4, 6.8, 9.1, 8.7])

# ── Cálculo manual ──────────────────────────────
n = len(x)
x_mean, y_mean = x.mean(), y.mean()

cov_xy = np.sum((x - x_mean) * (y - y_mean)) / (n - 1)
std_x  = np.sqrt(np.sum((x - x_mean)**2) / (n - 1))
std_y  = np.sqrt(np.sum((y - y_mean)**2) / (n - 1))

r_manual = cov_xy / (std_x * std_y)

# ── Teste-t para significância ──────────────────
t_stat  = r_manual * np.sqrt(n - 2) / np.sqrt(1 - r_manual**2)
p_value = 2 * stats.t.sf(np.abs(t_stat), df=n-2)  # bilateral

# ── Via scipy (validação) ───────────────────────
r_scipy, p_scipy = stats.pearsonr(x, y)

print(f"r manual  : {r_manual:.4f}")
print(f"r scipy   : {r_scipy:.4f}")
print(f"t-statistic: {t_stat:.4f}")
print(f"p-value   : {p_value:.4f}")
print(f"R² = {r_scipy**2:.4f}  →  explica {r_scipy**2*100:.1f}% da variância")
// 03

Tipos de Correlação em Detalhe

Visualize abaixo como dados com diferentes correlações se comportam em scatter plots. O formato, dispersão e orientação da nuvem de pontos revelam muito sobre a natureza da relação.

Correlação Parcial

A correlação parcial mede a associação entre X e Y controlando o efeito de uma ou mais variáveis Z. É fundamental em análises multivariadas onde confundidores estão presentes.

→ Correlação Parcial de X e Y dado Z
r(X, Y | Z) = (r(X,Y)r(X,Z)·r(Y,Z)) / √[(1 − r²(X,Z)) · (1 − r²(Y,Z))]
Python · Correlação Parcial
import pandas as pd
import numpy as np
from scipy import stats

def partial_corr(data: pd.DataFrame, x: str, y: str, z: list) -> dict:
    """
    Calcula correlação parcial de x e y controlando por z.
    Usa resíduos da regressão OLS para remover efeito de z.
    """
    from sklearn.linear_model import LinearRegression

    lr = LinearRegression()

    # Resíduos de X ~ Z
    lr.fit(data[z], data[x])
    res_x = data[x] - lr.predict(data[z])

    # Resíduos de Y ~ Z
    lr.fit(data[z], data[y])
    res_y = data[y] - lr.predict(data[z])

    # Correlação dos resíduos = correlação parcial
    r, p = stats.pearsonr(res_x, res_y)
    return {"r_partial": round(r, 4), "p_value": round(p, 4)}

# Exemplo: Temperatura afeta tanto consumo de sorvete quanto afogamentos?
# Sem controle: alta corr. sorvete ↔ afogamentos (confundidor: temperatura!)
df = pd.DataFrame({
    "sorvete":    [120, 200, 350, 450, 600, 700, 650, 500, 300, 150],
    "afogamentos":[  2,   3,   5,   8,  12,  15,  13,   9,   4,   2],
    "temperatura": [ 15,  18,  22,  26,  31,  35,  33,  28,  21,  16],
})

r_bruta, p_bruta = stats.pearsonr(df["sorvete"], df["afogamentos"])
r_parcial = partial_corr(df, "sorvete", "afogamentos", ["temperatura"])

print(f"Correlação bruta  sorvete↔afogamentos: r={r_bruta:.3f}")     # ~0.99 (!)
print(f"Corr. parcial (| temperatura):          r={r_parcial['r_partial']:.3f}")  # ~0.05
# → Relação espúria! Era toda explicada pela temperatura.
// 04

O Que a Correlação NÃO Mostra

Este é o capítulo mais importante do artigo. A correlação é poderosa, mas suas limitações são igualmente importantes de entender — e frequentemente ignoradas.

🔁

1. Causalidade

A correlação nunca implica causa. Storks na Europa correlacionam com taxa de natalidade (r ≈ 0.62). Sorvete e afogamentos também. Você precisa de design experimental ou DAGs causais para inferir causas.

📉

2. Não-linearidade

Pearson detecta apenas relações lineares. X e X² têm r≈0 para distribuições simétricas, mas são perfeitamente dependentes. Se seu scatter plot parece uma parábola, Pearson é cego para isso.

👾

3. Outliers Dominantes

Um único ponto outlier pode inflar r de 0.1 para 0.8 ou destruir uma correlação genuína de 0.9. Sempre visualize antes de calcular — nunca confie em r sem olhar o scatter plot.

🪆

4. Variáveis Ocultas (Confounders)

Uma terceira variável Z pode criar correlação artificial entre X e Y. O Quarteto de Anscombe (abaixo) mostra 4 datasets com r idêntico mas distribuições completamente diferentes.

🪟

5. Paradoxo de Simpson

Uma correlação observada globalmente pode se inverter quando os dados são segmentados por grupos. A direção da relação em subgrupos pode ser oposta à observada no total.

📏

6. Sensibilidade ao Range

Restrição de range atenua a correlação observada. Se você mede somente atletas de elite, a correlação entre treino e desempenho parece menor do que na população geral.

O Quarteto de Anscombe

Francis Anscombe criou em 1973 quatro datasets que têm exatamente as mesmas estatísticas descritivas (média, variância, r de Pearson, linha de regressão), mas são completamente diferentes visualmente. Uma lição definitiva sobre por que visualizar seus dados é inegociável.

⚠️ Todos os 4 datasets acima têm: x̄ = 9.0 | ȳ ≈ 7.5 | r ≈ 0.816 | regressão y ≈ 3 + 0.5x. Mas olhando os gráficos, são totalmente distintos — I é linear, II é curvo, III tem um outlier, IV é uma linha vertical. Nunca reporte r sem o scatter plot!

Correlações Espúrias: A Arte de Enganar com Dados

Tyler Vigen coletou exemplos hilários de correlações estatisticamente significativas que são obviamente absurdas. Consumo de queijo per capita correlaciona com mortes por enrolamento em lençóis (r = 0.947). Esses exemplos ilustram como com dados suficientes, qualquer correlação pode parecer real — por isso interpretação contextual e teoria prévia são essenciais.

// 05

Todos os Métodos de Cálculo

Método Tipo de Dado Detecta Robusto a Outliers Quando Usar
Pearson (r) Contínuo, Normal Relação linear Não Dados contínuos, distribuição normal, sem outliers extremos
Spearman (ρ) Ordinal/Contínuo Relação monotônica Sim Dados ordinais, não-normais, com outliers, relações não-lineares monótonas
Kendall (τ) Ordinal Relação monotônica Sim Amostras pequenas, dados com muitos empates
Point-Biserial Binário + Contínuo Diferença de médias Médio Variável binária (0/1) vs. variável contínua
Phi (φ) Binário + Binário Associação 2x2 Médio Duas variáveis dicotômicas (tabela de contingência 2x2)
Cramér's V Categórico Associação nominal Sim Variáveis categóricas nominais com mais de 2 categorias
MIC (Maximal Info) Qualquer Qualquer dependência Médio Quando a forma da relação é desconhecida (não-linear, não-monotônica)
Distance Corr. (dCor) Qualquer Dependência geral Médio Detectar qualquer forma de dependência, incluindo não-linear complexa

Spearman em Detalhe

Spearman substitui os valores pelos seus ranks (posições ordenadas) e então calcula Pearson nos ranks. A fórmula simplificada quando não há empates:

→ Correlação de Spearman
ρ = 1 − 6 · Σ dᵢ² / n(n² − 1)

onde dᵢ = diferença entre os ranks de xᵢ e yᵢ

Kendall's Tau em Detalhe

Kendall conta pares concordantes (ambas variáveis crescem juntas) vs. discordantes (uma cresce, outra cai):

→ Tau de Kendall
τ = (PQ) / √[(P+Q+T)(P+Q+U)]

P = pares concordantes · Q = discordantes · T, U = empates em X e Y (τ-b de Kendall)

Python · Todos os Métodos de Correlação
import numpy as np
import pandas as pd
from scipy import stats
from scipy.stats import pointbiserialr, kendalltau, spearmanr, pearsonr

# ─────────────────────────────────────────────────────────
#  1. PEARSON — relação linear, dados contínuos normais
# ─────────────────────────────────────────────────────────
r_pearson, p_pearson = pearsonr(x, y)

#  2. SPEARMAN — monotônico, robusto a outliers
r_spearman, p_spearman = spearmanr(x, y)

#  3. KENDALL — amostras pequenas, muitos empates
r_kendall, p_kendall = kendalltau(x, y)

#  4. POINT-BISERIAL — variável binária vs contínua
binary = np.array([0,1,0,1,1,0,1,0])
r_pb, p_pb = pointbiserialr(binary, y)

#  5. CRAMÉR'S V — variáveis categóricas
def cramers_v(x_cat, y_cat):
    confusion = pd.crosstab(x_cat, y_cat)
    chi2 = stats.chi2_contingency(confusion)[0]
    n = confusion.sum().sum()
    phi2 = chi2 / n
    r_dim, k_dim = confusion.shape
    return np.sqrt(phi2 / min(k_dim - 1, r_dim - 1))

#  6. DISTANCE CORRELATION — detecta qualquer dependência
def distance_corr(X, Y):
    """Distance Correlation de Székely et al. (2007)"""
    n = len(X)
    a = np.abs(X[:, None] - X[None, :])
    b = np.abs(Y[:, None] - Y[None, :])
    # Doubly center the distance matrices
    A = a - a.mean(axis=0) - a.mean(axis=1)[:, None] + a.mean()
    B = b - b.mean(axis=0) - b.mean(axis=1)[:, None] + b.mean()
    dcov2_xy = (A * B).mean()
    dcov2_xx = (A * A).mean()
    dcov2_yy = (B * B).mean()
    dcor = np.sqrt(np.maximum(dcov2_xy, 0) / np.sqrt(dcov2_xx * dcov2_yy))
    return dcor
// 06

Bibliotecas Python: Guia Prático

pandas
df.corr() · matriz de correlação · rápido para exploração
scipy.stats
pearsonr, spearmanr, kendalltau · retorna (r, p-value)
numpy
np.corrcoef() · matriz de correlação básica
pingouin
correlação parcial, bicor, testes robustos, intervalos de confiança
seaborn
heatmap, pairplot, regplot · visualizações prontas
plotly
heatmaps interativos, scatter matrix, hover info
statsmodels
correlação com correção de Newey-West para séries temporais
minepy
MIC (Maximal Information Coefficient) para relações não-lineares
Python · Pingouin — Correlação Completa com IC e Potência
import pingouin as pg
import pandas as pd

# pingouin retorna um DataFrame rico com r, CI, p, potência, n
result = pg.corr(x=df['peso_kg'], y=df['gmdd'], method='pearson')
print(result)
# Output:          n         r        CI95%         p-unc  BF10  power
#         pearson 150  0.7832  [0.70, 0.85]  3.21e-32  inf   1.000

# Correlação parcial com covariáveis
result_partial = pg.partial_corr(
    data=df,
    x='peso_kg',
    y='gmdd',         # ganho médio diário de peso
    covar=['idade_dias', 'raca_code']
)

# Matriz de correlação com p-values via pingouin
corr_matrix = pg.pairwise_corr(
    data=df[['peso', 'gmdd', 'temperatura', 'precipitacao']],
    method='spearman',
    padjust='bonferroni'    # correção para múltiplos testes!
)
print(corr_matrix[['X', 'Y', 'r', 'p-corr', 'CI95%']])

💡 Correção para múltiplos testes: Ao calcular uma matriz de correlação com k variáveis, você faz k(k-1)/2 testes. Com 10 variáveis = 45 testes! A chance de encontrar um falso positivo explode. Sempre aplique correção de Bonferroni, FDR (Benjamini-Hochberg) ou similar.

// 07

Visualizações: do Básico ao Avançado

7.1 Heatmap de Correlação Interativo

A visualização mais clássica. O heatmap abaixo é gerado dinamicamente com dados simulados. Vermelho = positivo, Azul = negativo, círculo maior = correlação mais forte.

Python · Heatmap com Seaborn (Profissional)
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

def plot_corr_heatmap(df: pd.DataFrame, method: str = 'pearson',
                       mask_upper: bool = True, annotate: bool = True):
    """Heatmap de correlação profissional com máscara triangular."""
    corr = df.corr(method=method)
    
    # Máscara triangular superior (evita redundância)
    mask = np.zeros_like(corr, dtype=bool)
    if mask_upper:
        mask[np.triu_indices_from(mask)] = True
    
    fig, ax = plt.subplots(figsize=(10, 8))
    
    cmap = sns.diverging_palette(220, 20, as_cmap=True)
    
    sns.heatmap(
        corr,
        mask=mask,
        cmap=cmap,
        center=0,
        vmin=-1, vmax=1,
        annot=annotate,
        fmt=".2f",
        square=True,
        linewidths=0.5,
        linecolor='#1a1d2e',
        cbar_kws={"shrink": 0.8, "label": f"r de {method.capitalize()}"},
        ax=ax
    )
    
    ax.set_title(f'Matriz de Correlação ({method.capitalize()})',
                fontsize=14, fontweight='bold', pad=20)
    plt.tight_layout()
    plt.savefig('correlation_heatmap.png', dpi=150, bbox_inches='tight')
    return fig
▶ resultado — corrplot estilo R (círculos proporcionais ao |r|, cor ao sinal)

7.2 Scatter Plot Matrix (Pair Plot)

Python · Pair Plot com Seaborn + Coeficientes
import seaborn as sns
from scipy.stats import pearsonr

def corrfunc(x, y, **kwargs):
    """Adiciona r e p-value em cada painel do pairplot."""
    r, p = pearsonr(x, y)
    ax = plt.gca()
    label = f"r = {r:.2f}\np = {p:.3f}"
    ax.annotate(label, xy=(0.05, 0.85), xycoords=ax.transAxes,
               fontsize=9, color='#10b981' if p < 0.05 else '#94a3b8')

g = sns.PairGrid(df, vars=['peso', 'gmdd', 'temperatura', 'precipitacao'],
                 hue='fazenda')
g.map_upper(sns.scatterplot, alpha=0.6, size=3)
g.map_lower(corrfunc)  # r + p nos painéis inferiores
g.map_diag(sns.kdeplot, fill=True)
g.add_legend()
plt.suptitle('Pair Plot com Correlações por Fazenda', y=1.02)
▶ resultado — pair plot interativo (scatter + kde diagonal + r em cada célula)

7.3 Correlograma Circular

Python · Plotly Interactive Heatmap
import plotly.graph_objects as go
import plotly.express as px

corr = df.corr()

fig = go.Figure(go.Heatmap(
    z=corr.values,
    x=corr.columns.tolist(),
    y=corr.index.tolist(),
    colorscale='RdBu_r',
    zmin=-1, zmax=1,
    text=corr.round(2).values,
    texttemplate="%{text}",
    hovertemplate="%{x} × %{y}: %{z:.3f}<extra></extra>",
    colorbar=dict(title="Pearson r"),
))

fig.update_layout(
    title="Matriz de Correlação Interativa",
    template="plotly_dark",
    width=700, height=600,
    xaxis=dict(tickangle=-45),
)

fig.write_html("correlation_interactive.html")
fig.show()
▶ resultado — heatmap de células (passe o mouse para ver o valor exato)

7.4 Visualização de Correlação como Grafo

Para muitas variáveis, um grafo de rede onde as arestas representam correlações acima de um threshold é uma visualização poderosa e intuitiva.

Python · Network Graph de Correlações
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np

def correlation_network(df: pd.DataFrame, threshold: float = 0.5,
                          method: str = 'pearson'):
    """
    Gera grafo onde nós = variáveis, arestas = |r| >= threshold.
    Espessura da aresta proporcional a |r|, cor = sinal.
    """
    corr = df.corr(method=method)
    G = nx.Graph()
    G.add_nodes_from(corr.columns)

    for i in range(len(corr.columns)):
        for j in range(i + 1, len(corr.columns)):
            r = corr.iloc[i, j]
            if abs(r) >= threshold:
                G.add_edge(corr.columns[i], corr.columns[j],
                           weight=abs(r), sign=1 if r > 0 else -1)

    pos = nx.spring_layout(G, seed=42, k=2)
    edge_colors = ['#ef4444' if G[u][v]['sign'] > 0 else '#60a5fa'
                   for u, v in G.edges()]
    edge_widths = [G[u][v]['weight'] * 5 for u, v in G.edges()]

    fig, ax = plt.subplots(figsize=(12, 8))
    ax.set_facecolor('#0c0e14')
    fig.set_facecolor('#0c0e14')
    
    nx.draw_networkx(G, pos=pos, ax=ax,
        node_color='#6ee7b7', node_size=600,
        font_color='white', font_size=8,
        edge_color=edge_colors, width=edge_widths, alpha=0.9)
    
    ax.set_title(f'Grafo de Correlação (|r| ≥ {threshold})', color='white')
    return fig, G
▶ resultado — grafo de correlação interativo (arraste os nós)

7.5 Matriz de Correlação Circular (Chord Diagram)

O chord diagram é uma visualização avançada onde cada variável é um arco no círculo e as conexões representam as correlações. Ideal para publicações científicas e dashboards executivos.

Python · Chord Diagram com Holoviews + Bokeh
import holoviews as hv
from holoviews import opts
hv.extension('bokeh')

corr = df.corr().abs()  # apenas magnitude
cols = corr.columns.tolist()

# Criar lista de conexões (source, target, valor)
links = []
for i, c1 in enumerate(cols):
    for j, c2 in enumerate(cols):
        if i < j and corr.loc[c1, c2] > 0.4:
            links.append((i, j, corr.loc[c1, c2]))

chord = hv.Chord((links, hv.Dataset(
    pd.DataFrame(enumerate(cols), columns=['index', 'name']),
    'index'))
).opts(
    opts.Chord(
        labels='name',
        cmap='Category20',
        edge_cmap='Category20',
        edge_color=hv.dim('source').str(),
        width=600, height=600,
        title='Chord Diagram de Correlações'
    )
)
hv.save(chord, 'chord_correlation.html')
▶ resultado — chord diagram interativo (hover para ver correlação)
// 08

Tópicos Avançados

Correlação em Séries Temporais

Em séries temporais, correlação entre X e Y pode ser espúria se ambas têm tendência. É fundamental verificar e remover não-estacionariedade antes de calcular correlações.

Python · Correlação Cross-Lag em Séries Temporais
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import ccf, adfuller
import matplotlib.pyplot as plt

def cross_lag_correlation(x: pd.Series, y: pd.Series,
                            max_lag: int = 24) -> pd.DataFrame:
    """
    Calcula correlação cruzada para diferentes lags.
    Útil para descobrir relações temporais defasadas.
    Ex: preço da ração hoje → peso do gado daqui a N dias?
    """
    # Verificar estacionariedade (ADF test)
    p_x = adfuller(x.dropna())[1]
    p_y = adfuller(y.dropna())[1]
    
    if p_x > 0.05:
        x = x.diff().dropna()  # diferenciação para estacionarizar
    if p_y > 0.05:
        y = y.diff().dropna()

    # Cross-correlation function
    lags = range(-max_lag, max_lag + 1)
    corrs = []
    for lag in lags:
        if lag < 0:
            r, _ = stats.pearsonr(x[:lag], y[-lag:])
        elif lag > 0:
            r, _ = stats.pearsonr(x[lag:], y[:-lag])
        else:
            r, _ = stats.pearsonr(x, y)
        corrs.append({'lag': lag, 'r': r})
    
    return pd.DataFrame(corrs)

# Uso: descobrir se temperatura afeta GMDD com lag de N dias
ccf_result = cross_lag_correlation(df['temperatura'], df['gmdd'], max_lag=30)
best_lag = ccf_result.loc[ccf_result['r'].abs().idxmax()]
print(f"Maior correlação no lag {best_lag['lag']} dias: r={best_lag['r']:.3f}")

Correlação de Matrizes: Análise de Componentes Principais

A matriz de correlação é fundamental para PCA — os autovalores e autovetores da matriz de correlação são a base da análise de componentes principais.

→ Decomposição Espectral da Matriz de Correlação
R = V Λ Vᵀ

R = matriz de correlação · V = matriz de autovetores (loadings) · Λ = diagonal de autovalores

Rolling Correlation: Correlação que Muda no Tempo

Python · Rolling Correlation com Pandas
# Correlação móvel com janela de 30 dias
rolling_corr = df['temperatura'].rolling(window=30).corr(df['gmdd'])

# Visualizar evolução da correlação no tempo
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)

df['temperatura'].plot(ax=axes[0], color='#f59e0b', label='Temperatura')
df['gmdd'].plot(ax=axes[0], color='#6ee7b7', label='GMDD', secondary_y=True)
axes[0].set_title('Séries Originais')

rolling_corr.plot(ax=axes[1], color='#60a5fa')
axes[1].axhline(0, color='white', linestyle='--', alpha=0.4)
axes[1].fill_between(rolling_corr.index, rolling_corr, 0,
                      where=rolling_corr > 0, alpha=0.3, color='#ef4444')
axes[1].fill_between(rolling_corr.index, rolling_corr, 0,
                      where=rolling_corr < 0, alpha=0.3, color='#3b82f6')
axes[1].set_title('Rolling Correlation (janela 30 dias)')
plt.tight_layout()
// 09

Quando Usar Cada Método — Guia de Decisão

Fluxo de decisão simplificado: Os dados são contínuos e normalmente distribuídos sem outliers? → Pearson. São ordinais ou com outliers? → Spearman. Amostra pequena ou muitos empates? → Kendall. Uma variável é binária? → Point-Biserial. Ambas categóricas? → Cramér's V. Relação pode ser não-linear e complexa? → MIC ou Distance Correlation.

Situação Método Recomendado Python
Dados contínuos, normais, sem outliers Pearson scipy.stats.pearsonr
Dados não-normais ou com outliers Spearman scipy.stats.spearmanr
Amostra pequena (n < 30), empates Kendall τ-b scipy.stats.kendalltau
Binário vs. contínuo Point-Biserial scipy.stats.pointbiserialr
Categórico vs. categórico Cramér's V pingouin.cramers_v
Relação não-linear desconhecida Distance Correlation custom / dcor library
Controlando confundidores Correlação Parcial pingouin.partial_corr
Séries temporais com lag Cross-Correlation statsmodels.tsa.stattools.ccf
Correlação em evolução temporal Rolling Correlation pandas .rolling().corr()

Checklist Antes de Reportar uma Correlação

✅ Visualizei o scatter plot?
✅ Verifiquei a normalidade das variáveis (para Pearson)?
✅ Identifiquei e tratei outliers?
✅ Calculei e reportei o p-value?
✅ Reportei o intervalo de confiança de r?
✅ Apliquei correção para múltiplos testes (se matriz)?
✅ Considerei possíveis confundidores?
✅ Não afirmei causalidade com base só em r?

// 10

Visualizações Interativas Extras

Rolling Correlation — Como a Correlação Muda ao Longo do Tempo

A correlação entre duas variáveis não é estática. O gráfico abaixo mostra como uma Rolling Correlation com janela de 20 pontos oscila no tempo, revelando períodos de correlação positiva, negativa, ou ausência de relação.

// Rolling Correlation — Simulação Interativa
Série X Série Y Rolling r (janela=20)

Cross-Lag Correlation — Relação com Defasagem

Frequentemente, um evento em X só afeta Y depois de alguns dias ou meses. O gráfico de Cross-Lag exibe o coeficiente de correlação para cada defasagem (lag), ajudando a identificar o "atraso natural" da relação entre variáveis — por exemplo, chuva hoje e crescimento do pasto daqui a 7 dias.

// Cross-Lag Correlation — Exemplo Chuva vs. GMDD (lags −15 a +15 dias)
Barras acima de zero = correlação positiva naquele lag · Linha vermelha pontilhada = banda de significância (±1.96/√n)

Grafo de Correlação — Rede de Variáveis

Quando há muitas variáveis, o grafo de rede é mais legível que um heatmap. Cada nó é uma variável; arestas vermelhas indicam correlação positiva forte (|r| ≥ 0.5), azuis indicam correlação negativa forte. A espessura da aresta é proporcional à magnitude de |r|.

// Grafo de Correlação — Dados Simulados Bovinocultura
Positiva forte (r ≥ 0.5) Negativa forte (r ≤ −0.5) Variável
// 11

Glossário de Termos

Referência rápida dos principais termos técnicos utilizados neste guia.