Fase 3 — Visualização e comunicação | Pré-requisitos: Aulas 0 a 16 | Duração estimada: 85 minutos


Introdução

Análise univariada descreve uma variável. Análise bivariada investiga relações entre pares. Análise multivariada vai além: investiga como três ou mais variáveis se relacionam simultaneamente, e especialmente como a relação entre duas variáveis muda quando você introduz uma terceira.

Esta última dimensão — interações — é onde análise multivariada tem mais valor analítico. Uma relação entre salário e experiência pode ser muito diferente para homens e mulheres, ou para departamentos diferentes. Sem a terceira variável, você vê a média de todos os grupos misturados. Com ela, você vê a estrutura real.

O desafio técnico é que humanos percebem espaço em duas dimensões. Codificar três ou mais variáveis num gráfico 2D exige criatividade e disciplina — criatividade para encontrar canais adicionais (cor, tamanho, forma, painéis), disciplina para não sobrecarregar o gráfico a ponto de torná-lo ilegível.


Configuração

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import seaborn as sns
from scipy import stats

sns.set_theme(style='ticks', palette='muted')
plt.rcParams.update({
    'figure.dpi': 120,
    'axes.spines.top': False,
    'axes.spines.right': False,
    'axes.grid': True,
    'axes.grid.alpha': 0.3,
    'axes.grid.linewidth': 0.5,
    'font.size': 11
})

np.random.seed(42)
n = 1000

df = pd.DataFrame({
    'salario': np.random.lognormal(8.5, 0.5, n),
    'idade': np.random.normal(38, 10, n).clip(18, 70).astype(int),
    'anos_experiencia': np.random.exponential(8, n).clip(0, 40).astype(int),
    'departamento': np.random.choice(
        ['Vendas', 'TI', 'RH', 'Financeiro', 'Operações'],
        n, p=[0.30, 0.25, 0.15, 0.20, 0.10]
    ),
    'nivel': np.random.choice(
        ['Júnior', 'Pleno', 'Sênior', 'Gerente'],
        n, p=[0.35, 0.30, 0.25, 0.10]
    ),
    'satisfacao': np.random.choice(range(1, 6), n,
                                   p=[0.05, 0.10, 0.25, 0.40, 0.20]),
    'genero': np.random.choice(['M', 'F'], n, p=[0.55, 0.45])
})

# Correlações realistas
df['salario'] = (df['salario']
    + df['anos_experiencia'] * 800
    + (df['nivel'] == 'Gerente').astype(int) * 15000
    + (df['nivel'] == 'Sênior').astype(int) * 7000
    + (df['departamento'] == 'TI').astype(int) * 4000
)

Scatter com terceira variável codificada em cor

A extensão mais natural do scatter bivariado é adicionar uma terceira variável como cor dos pontos:

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

# Terceira variável categórica como cor
nivel_ordem = ['Júnior', 'Pleno', 'Sênior', 'Gerente']
paleta = dict(zip(nivel_ordem, sns.color_palette('Set2', 4)))

for nivel in nivel_ordem:
    mask = df['nivel'] == nivel
    axes[0].scatter(
        df.loc[mask, 'anos_experiencia'],
        df.loc[mask, 'salario'],
        alpha=0.5, s=20, label=nivel,
        color=paleta[nivel], edgecolors='none'
    )

axes[0].set_xlabel('Anos de experiência')
axes[0].set_ylabel('Salário (R$)')
axes[0].set_title('Salário vs. experiência por nível')
axes[0].legend(title='Nível', fontsize=9, markerscale=2)
axes[0].yaxis.set_major_formatter(ticker.FuncFormatter(
    lambda x, p: f'R$ {x/1000:.0f}k'
))

# Terceira variável numérica como cor (colormap)
sc = axes[1].scatter(
    df['anos_experiencia'], df['salario'],
    c=df['idade'], cmap='viridis',
    alpha=0.5, s=20, edgecolors='none'
)
cbar = plt.colorbar(sc, ax=axes[1])
cbar.set_label('Idade')
axes[1].set_xlabel('Anos de experiência')
axes[1].set_ylabel('Salário (R$)')
axes[1].set_title('Salário vs. experiência (cor = idade)')
axes[1].yaxis.set_major_formatter(ticker.FuncFormatter(
    lambda x, p: f'R$ {x/1000:.0f}k'
))

plt.tight_layout()
plt.show()

Bubble chart: quatro variáveis num gráfico

O bubble chart adiciona uma quarta variável como tamanho dos pontos. Funciona bem quando a variável de tamanho tem interpretação direta e os valores não são muito próximos:

# Agregar por departamento e nível
resumo = (df.groupby(['departamento', 'nivel'])
          .agg(
              salario_medio=('salario', 'mean'),
              experiencia_media=('anos_experiencia', 'mean'),
              satisfacao_media=('satisfacao', 'mean'),
              n_funcionarios=('salario', 'count')
          )
          .reset_index()
)

fig, ax = plt.subplots(figsize=(11, 7))

depts = resumo['departamento'].unique()
paleta_dept = dict(zip(depts, sns.color_palette('Set2', len(depts))))

for dept in depts:
    dados = resumo[resumo['departamento'] == dept]
    sc = ax.scatter(
        dados['experiencia_media'],
        dados['salario_medio'],
        s=dados['n_funcionarios'] * 8,   # tamanho proporcional ao n
        c=[paleta_dept[dept]] * len(dados),
        alpha=0.6,
        edgecolors='white',
        linewidths=0.8,
        label=dept
    )

# Anotar pontos de interesse
for _, row in resumo[resumo['nivel'] == 'Gerente'].iterrows():
    ax.annotate(
        row['departamento'],
        (row['experiencia_media'], row['salario_medio']),
        xytext=(5, 5), textcoords='offset points',
        fontsize=8, color='gray'
    )

# Legenda de tamanho
for n_ref in [10, 30, 60]:
    ax.scatter([], [], s=n_ref * 8, c='gray', alpha=0.5,
               label=f'n = {n_ref}', edgecolors='white')

ax.set_xlabel('Experiência média (anos)')
ax.set_ylabel('Salário médio (R$)')
ax.set_title('Salário médio vs. experiência por departamento e nível\n'
             '(tamanho = número de funcionários)')
ax.yaxis.set_major_formatter(ticker.FuncFormatter(
    lambda x, p: f'R$ {x/1000:.0f}k'
))
ax.legend(title='Departamento / Tamanho', fontsize=9,
          bbox_to_anchor=(1.01, 1), loc='upper left')

plt.tight_layout()
plt.show()

Pair plot: todas as relações de uma vez

O pair plot (ou scatter matrix) mostra todas as combinações de pares de variáveis numéricas num grid. A diagonal mostra a distribuição de cada variável individualmente:

variaveis = ['salario', 'idade', 'anos_experiencia', 'satisfacao']

# Pair plot com cor por grupo
g = sns.pairplot(
    df[variaveis + ['nivel']],
    hue='nivel',
    hue_order=['Júnior', 'Pleno', 'Sênior', 'Gerente'],
    palette='Set2',
    diag_kind='kde',        # KDE na diagonal
    plot_kws={'alpha': 0.3, 's': 15, 'edgecolors': 'none'},
    diag_kws={'fill': True, 'alpha': 0.4}
)

g.figure.suptitle('Pair plot — relações entre variáveis por nível', y=1.02)

# Ajustar formatação dos eixos de salário
for ax in g.axes.flat:
    if ax.get_xlabel() == 'salario' or ax.get_ylabel() == 'salario':
        if ax.get_xlabel() == 'salario':
            ax.xaxis.set_major_formatter(ticker.FuncFormatter(
                lambda x, p: f'{x/1000:.0f}k'
            ))
        if ax.get_ylabel() == 'salario':
            ax.yaxis.set_major_formatter(ticker.FuncFormatter(
                lambda x, p: f'{x/1000:.0f}k'
            ))

plt.tight_layout()
plt.show()

O pair plot é excelente para exploração mas raramente vai direto para uma apresentação — ele tem muita informação para ser absorvida rapidamente. Use para descoberta, não para comunicação.


FacetGrid: painéis condicionados

O FacetGrid cria um grid de gráficos idênticos, cada um mostrando um subconjunto dos dados. É a solução para "quero ver a mesma relação em cada grupo":

# FacetGrid com scatter por departamento
g = sns.FacetGrid(
    df,
    col='departamento',
    col_wrap=3,              # máximo de 3 painéis por linha
    height=3.5,
    aspect=1.2,
    sharey=True,             # mesmo eixo Y em todos os painéis
    sharex=True
)

g.map_dataframe(
    sns.scatterplot,
    x='anos_experiencia',
    y='salario',
    hue='nivel',
    hue_order=['Júnior', 'Pleno', 'Sênior', 'Gerente'],
    palette='Set2',
    alpha=0.5,
    s=15,
    edgecolors='none'
)

g.add_legend(title='Nível')
g.set_axis_labels('Anos de experiência', 'Salário (R$)')
g.set_titles(col_template='{col_name}')

for ax in g.axes.flat:
    ax.yaxis.set_major_formatter(ticker.FuncFormatter(
        lambda x, p: f'R$ {x/1000:.0f}k'
    ))

g.figure.suptitle('Relação experiência–salário por departamento', y=1.02)
plt.tight_layout()
plt.show()
# FacetGrid com histogramas por dois fatores (linha e coluna)
g2 = sns.FacetGrid(
    df[df['nivel'].isin(['Júnior', 'Sênior'])],
    row='nivel',
    col='genero',
    height=3,
    aspect=1.4,
    sharey=False
)

g2.map_dataframe(
    sns.histplot,
    x='salario',
    bins=25,
    color='steelblue',
    edgecolor='white',
    linewidth=0.4
)

g2.set_axis_labels('Salário (R$)', 'Frequência')
g2.set_titles(row_template='{row_name}', col_template='{col_name}')
g2.figure.suptitle('Distribuição salarial por nível e gênero', y=1.02)

for ax in g2.axes.flat:
    ax.xaxis.set_major_formatter(ticker.FuncFormatter(
        lambda x, p: f'R$ {x/1000:.0f}k'
    ))

plt.tight_layout()
plt.show()

Heatmap de pivot table: três variáveis em grade

Quando você tem duas variáveis categóricas e uma numérica, um heatmap de pivot table mostra os três juntos de forma compacta:

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

# Salário médio por departamento e nível
pivot_salario = df.pivot_table(
    values='salario',
    index='departamento',
    columns='nivel',
    aggfunc='mean'
)[['Júnior', 'Pleno', 'Sênior', 'Gerente']]

sns.heatmap(
    pivot_salario / 1000,   # em R$ mil
    annot=True,
    fmt='.1f',
    cmap='YlOrRd',
    ax=axes[0],
    linewidths=0.5,
    cbar_kws={'label': 'Salário médio (R$ mil)'}
)
axes[0].set_title('Salário médio (R$ mil)\npor departamento e nível')
axes[0].set_xlabel('Nível')
axes[0].set_ylabel('Departamento')

# Satisfação média por departamento e nível
pivot_satisf = df.pivot_table(
    values='satisfacao',
    index='departamento',
    columns='nivel',
    aggfunc='mean'
)[['Júnior', 'Pleno', 'Sênior', 'Gerente']]

sns.heatmap(
    pivot_satisf,
    annot=True,
    fmt='.2f',
    cmap='RdYlGn',
    vmin=1, vmax=5,
    center=3,
    ax=axes[1],
    linewidths=0.5,
    cbar_kws={'label': 'Satisfação média (1-5)'}
)
axes[1].set_title('Satisfação média\npor departamento e nível')
axes[1].set_xlabel('Nível')
axes[1].set_ylabel('')

plt.tight_layout()
plt.show()

Interações: quando a relação entre X e Y depende de Z

Detectar interações é um dos objetivos centrais da análise multivariada. Uma interação existe quando a relação entre duas variáveis é diferente dependendo do valor de uma terceira.

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

# Relação experiência-salário por gênero
# Sem interação: as linhas seriam paralelas
for genero, cor, label in [('M', 'steelblue', 'Masculino'),
                             ('F', 'coral', 'Feminino')]:
    dados = df[df['genero'] == genero]

    # Scatter
    axes[0].scatter(dados['anos_experiencia'], dados['salario'],
                    alpha=0.2, s=15, color=cor, edgecolors='none')

    # Linha de tendência
    slope, intercept, r, p, se = stats.linregress(
        dados['anos_experiencia'], dados['salario']
    )
    x_range = np.linspace(0, 40, 100)
    axes[0].plot(x_range, slope * x_range + intercept,
                 color=cor, linewidth=2.5,
                 label=f'{label} (r={r:.2f})')

axes[0].set_xlabel('Anos de experiência')
axes[0].set_ylabel('Salário (R$)')
axes[0].set_title('Interação: experiência × salário por gênero')
axes[0].legend(fontsize=9)
axes[0].yaxis.set_major_formatter(ticker.FuncFormatter(
    lambda x, p: f'R$ {x/1000:.0f}k'
))

# Relação satisfação-salário por nível
for nivel, cor in zip(['Júnior', 'Sênior'],
                       ['#2166AC', '#D73027']):
    dados = df[df['nivel'] == nivel]
    medias = dados.groupby('satisfacao')['salario'].mean()

    axes[1].plot(medias.index, medias.values / 1000,
                 marker='o', linewidth=2, color=cor,
                 markersize=8, label=nivel)

    # Intervalo de confiança
    ic = dados.groupby('satisfacao')['salario'].sem() * 1.96 / 1000
    axes[1].fill_between(
        medias.index,
        (medias - ic * 1000) / 1000,
        (medias + ic * 1000) / 1000,
        alpha=0.15, color=cor
    )

axes[1].set_xlabel('Satisfação (1-5)')
axes[1].set_ylabel('Salário médio (R$ mil)')
axes[1].set_title('Satisfação vs. salário médio por nível\n(com IC 95%)')
axes[1].legend(title='Nível', fontsize=9)
axes[1].set_xticks([1, 2, 3, 4, 5])

plt.tight_layout()
plt.show()

Quantificando interações

# Correlações por subgrupo — mudança da força da relação
print("Correlação experiência-salário por departamento:")
for dept in df['departamento'].unique():
    dados = df[df['departamento'] == dept]
    r, p = stats.pearsonr(dados['anos_experiencia'], dados['salario'])
    print(f"  {dept:12s}: r = {r:.3f} (p = {p:.3f})")

print("\nCorrelação experiência-salário por nível:")
for nivel in ['Júnior', 'Pleno', 'Sênior', 'Gerente']:
    dados = df[df['nivel'] == nivel]
    r, p = stats.pearsonr(dados['anos_experiencia'], dados['salario'])
    print(f"  {nivel:10s}: r = {r:.3f} (p = {p:.3f})")

Parallel coordinates: muitas variáveis ao mesmo tempo

Parallel coordinates representam cada observação como uma linha que passa por todos os eixos verticais — um eixo por variável. É útil para detectar padrões em conjuntos com muitas variáveis:

from pandas.plotting import parallel_coordinates
from matplotlib.colors import to_rgba

# Normalizar variáveis para mesma escala
variaveis_pc = ['salario', 'idade', 'anos_experiencia', 'satisfacao']
df_pc = df[variaveis_pc + ['nivel']].copy()

for col in variaveis_pc:
    col_min = df_pc[col].min()
    col_max = df_pc[col].max()
    df_pc[col] = (df_pc[col] - col_min) / (col_max - col_min)

# Amostrar para não sobrecarregar o gráfico
df_amostra = df_pc.groupby('nivel').apply(
    lambda x: x.sample(min(50, len(x)), random_state=42)
).reset_index(drop=True)

fig, ax = plt.subplots(figsize=(12, 5))

cores_nivel = {
    'Júnior': '#4DAF4A',
    'Pleno': '#377EB8',
    'Sênior': '#FF7F00',
    'Gerente': '#E41A1C'
}

parallel_coordinates(
    df_amostra,
    class_column='nivel',
    color=[cores_nivel[n] for n in ['Júnior', 'Pleno', 'Sênior', 'Gerente']],
    ax=ax,
    alpha=0.3,
    linewidth=1
)

ax.set_title('Parallel coordinates — perfil multivariado por nível\n'
             '(valores normalizados 0-1)')
ax.set_xticklabels(['Salário', 'Idade', 'Experiência', 'Satisfação'])
ax.legend(title='Nível', bbox_to_anchor=(1.01, 1), loc='upper left', fontsize=9)
ax.grid(axis='x', alpha=0.5)
ax.set_ylabel('Valor normalizado')

plt.tight_layout()
plt.show()

Heatmap de correlação parcial

A correlação parcial mede a relação entre duas variáveis removendo o efeito de outras variáveis — mais próxima de causalidade do que a correlação simples:

def correlacao_parcial(df, variaveis):
    """
    Calcula matriz de correlações parciais usando inversão da matriz de covariância.
    """
    dados = df[variaveis].dropna()
    cov = dados.cov()

    # Pseudo-inversa da matriz de covariância
    cov_inv = np.linalg.pinv(cov.values)

    # Correlação parcial a partir da inversa
    n = len(variaveis)
    corr_parcial = np.zeros((n, n))

    for i in range(n):
        for j in range(n):
            corr_parcial[i, j] = (
                -cov_inv[i, j] /
                np.sqrt(cov_inv[i, i] * cov_inv[j, j])
            )

    np.fill_diagonal(corr_parcial, 1.0)
    return pd.DataFrame(corr_parcial, index=variaveis, columns=variaveis)

variaveis_num = ['salario', 'idade', 'anos_experiencia', 'satisfacao']
corr_simples = df[variaveis_num].corr()
corr_parcial_df = correlacao_parcial(df, variaveis_num)

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

for ax, matriz, titulo in [
    (axes[0], corr_simples, 'Correlação simples de Pearson'),
    (axes[1], corr_parcial_df, 'Correlação parcial')
]:
    sns.heatmap(
        matriz,
        ax=ax,
        annot=True,
        fmt='.2f',
        cmap='RdBu_r',
        vmin=-1, vmax=1,
        center=0,
        square=True,
        linewidths=0.5,
        cbar_kws={'shrink': 0.8}
    )
    ax.set_title(titulo, fontsize=12)

fig.suptitle('Correlação simples vs. parcial\n'
             '(parcial: efeito das demais variáveis removido)', y=1.02)
plt.tight_layout()
plt.show()

Catplot: gráficos categóricos com facets

catplot do seaborn combina gráficos categóricos com facets num único comando:

# Distribuição de salário por nível, separado por departamento
g = sns.catplot(
    data=df[df['departamento'].isin(['TI', 'Vendas', 'Financeiro'])],
    x='nivel',
    y='salario',
    col='departamento',
    kind='box',
    order=['Júnior', 'Pleno', 'Sênior', 'Gerente'],
    palette='Set2',
    height=4,
    aspect=0.9,
    flierprops=dict(marker='o', markersize=2, alpha=0.3)
)

g.set_axis_labels('', 'Salário (R$)')
g.set_titles(col_template='{col_name}')
g.figure.suptitle('Distribuição salarial por nível e departamento', y=1.02)

for ax in g.axes.flat:
    ax.yaxis.set_major_formatter(ticker.FuncFormatter(
        lambda x, p: f'R$ {x/1000:.0f}k'
    ))
    ax.tick_params(axis='x', rotation=30)

plt.tight_layout()
plt.show()

Análise multivariada com tabelas pivot

Além de heatmaps, tabelas pivot com múltiplas métricas revelam estrutura multivariada de forma compacta:

# Pivot com múltiplas métricas
resumo_multi = df.groupby(['departamento', 'nivel']).agg(
    n=('salario', 'count'),
    salario_mediano=('salario', 'median'),
    satisfacao_media=('satisfacao', 'mean'),
    pct_senior_mais=('nivel', lambda x: (x.isin(['Sênior', 'Gerente'])).mean() * 100)
).round(2)

print("Resumo multivariado por departamento e nível:")
print(resumo_multi.to_string())

# Tabela de correlações por subgrupo
print("\nForça da relação experiência-salário por combinação de fatores:")
resultado = []
for dept in df['departamento'].unique():
    for gen in ['M', 'F']:
        dados = df[(df['departamento'] == dept) & (df['genero'] == gen)]
        if len(dados) > 20:
            r, p = stats.pearsonr(dados['anos_experiencia'], dados['salario'])
            resultado.append({
                'departamento': dept,
                'genero': gen,
                'n': len(dados),
                'r_pearson': round(r, 3),
                'significativo': 'sim' if p < 0.05 else 'não'
            })

df_resultado = pd.DataFrame(resultado).sort_values('r_pearson', ascending=False)
print(df_resultado.to_string(index=False))

Evitando sobrecarga visual

A principal armadilha da análise multivariada é tentar mostrar tudo num único gráfico. Algumas diretrizes práticas.

Limite a três ou quatro variáveis por gráfico. Além disso, o gráfico geralmente se torna mais confuso do que esclarecedor.

Prefira painéis a canais extras. Em vez de codificar a quinta variável como forma dos pontos — que é difícil de distinguir — crie um painel separado para cada valor da quinta variável.

Use cor para a variável mais importante. Cor é o canal visual mais poderoso depois de posição. Reserve-a para a variável que você quer que o leitor note primeiro.

Destaque, não mostre tudo. Se você tem dez grupos mas quer mostrar que dois se diferenciam, coloque os oito outros em cinza claro e destaque os dois em cores fortes.

# Técnica de destaque: neutralizar grupos não relevantes
fig, ax = plt.subplots(figsize=(11, 6))

# Todos os departamentos em cinza claro
for dept in df['departamento'].unique():
    dados = df[df['departamento'] == dept]
    ax.scatter(dados['anos_experiencia'], dados['salario'],
               alpha=0.1, s=15, color='#CCCCCC', edgecolors='none')

# Destacar apenas TI e RH
destaques = {
    'TI': '#2166AC',
    'RH': '#D73027'
}

for dept, cor in destaques.items():
    dados = df[df['departamento'] == dept]
    ax.scatter(dados['anos_experiencia'], dados['salario'],
               alpha=0.6, s=25, color=cor, edgecolors='none', label=dept)

    # Linha de tendência apenas para os destaques
    slope, intercept, r, p, se = stats.linregress(
        dados['anos_experiencia'], dados['salario']
    )
    x_range = np.linspace(0, 40, 100)
    ax.plot(x_range, slope * x_range + intercept,
            color=cor, linewidth=2, alpha=0.8)

ax.set_xlabel('Anos de experiência')
ax.set_ylabel('Salário (R$)')
ax.set_title('TI vs. RH: comparação de trajetória salarial\n'
             '(demais departamentos em cinza)')
ax.legend(title='Departamento', fontsize=10)
ax.yaxis.set_major_formatter(ticker.FuncFormatter(
    lambda x, p: f'R$ {x/1000:.0f}k'
))

plt.tight_layout()
plt.show()

Resumo

Análise multivariada investiga como três ou mais variáveis se relacionam e, especialmente, como a relação entre X e Y muda dependendo de Z — interações. As ferramentas principais são: scatter com cor categórica ou numérica para uma terceira variável, bubble chart para uma quarta variável como tamanho, pair plot para ver todas as relações de uma vez, FacetGrid para a mesma relação em múltiplos grupos, heatmap de pivot table para duas categóricas e uma numérica, parallel coordinates para perfis multivariados, e técnica de destaque para focar a atenção nas comparações relevantes. A armadilha central é sobrecarga visual — mais de três ou quatro variáveis num único gráfico geralmente confunde mais do que esclarece. Prefira múltiplos gráficos simples a um único gráfico complexo.

Exercícios

  1. O que é uma interação entre variáveis na análise multivariada e por que detectá-la é importante? Dê um exemplo de uma interação que poderia aparecer em dados de RH.

    ✓ Resposta:

    Uma interação ocorre quando a relação entre duas variáveis X e Y é diferente dependendo do valor de uma terceira variável Z. Em vez de Z apenas deslocar os valores de Y para cima ou para baixo de forma uniforme — o que seria um efeito principal — Z modifica a própria natureza da relação entre X e Y.

    A importância de detectar interações é que elas revelam que uma análise agregada está mascarando realidades diferentes para subgrupos distintos. Uma conclusão como "experiência está positivamente associada a salário" pode ser verdadeira em média mas muito mais forte para um grupo e fraca ou inexistente para outro. Tomar decisões baseadas apenas na análise agregada pode ser inadequado para grupos específicos.

    Exemplo em dados de RH: a relação entre anos de experiência e salário pode ser muito mais forte no departamento de TI do que no departamento de RH. Em TI, profissionais seniores têm escassez de mercado e comandam prêmios salariais crescentes com a experiência. Em RH, a estrutura de cargos é mais rígida e o salário sobe menos com a experiência adicional. Se você analisar apenas a correlação geral experiência-salário, verá uma correlação moderada que é a média das duas realidades. Ao desagregar por departamento, você vê uma correlação forte em TI e fraca em RH — uma interação entre departamento e experiência na determinação do salário.

  2. Qual a diferença entre usar cor e usar painéis (FacetGrid) para representar uma terceira variável? Quando cada abordagem é preferível?

    ✓ Resposta:

    Usar cor mantém todos os dados no mesmo espaço visual, o que facilita comparações diretas entre grupos — você pode sobrepor as distribuições e ver exatamente onde elas se separam ou convergem. A desvantagem é que com muitos grupos ou pontos sobrepostos, as cores se misturam e a leitura fica difícil. Funciona bem com poucos grupos (dois a quatro) e quando você quer enfatizar as diferenças entre grupos.

    Usar painéis (FacetGrid) dá a cada grupo seu próprio espaço visual. A comparação entre grupos é menos direta — o olho precisa se mover entre painéis — mas cada painel é muito mais legível individualmente. Funciona bem com muitos grupos, quando os padrões intragrupo são complexos, ou quando os grupos têm tamanhos muito diferentes e os menores ficariam invisíveis sobrepostos aos maiores.

    Prefira cor quando: há dois a quatro grupos, os grupos têm tamanhos similares, você quer comparar os grupos diretamente e a sobreposição não é excessiva.

    Prefira painéis quando: há cinco ou mais grupos, você quer que cada grupo seja analisado em detalhe antes de ser comparado com outros, os dados são densos demais para sobreposição, ou a pergunta central é "como se parece cada grupo individualmente?" em vez de "como os grupos diferem entre si?"

    Uma estratégia eficaz combina as duas: usar painéis com a mesma escala nos eixos e adicionar uma linha de referência global em cada painel — isso mantém a legibilidade individual e ainda facilita a comparação entre painéis.

  3. Por que o pair plot é mais adequado para exploração do que para comunicação? O que você faria para transformar um pair plot exploratório num gráfico comunicável?

    ✓ Resposta:

    O pair plot é adequado para exploração porque permite ver todas as relações possíveis de uma vez, acelerando a fase de descoberta. Mas para comunicação tem duas limitações sérias.

    Primeira: excesso de informação. Um pair plot com cinco variáveis cria 25 painéis (5×5). Nenhum leitor consegue processar 25 gráficos em cinco segundos — a regra básica de visualizações comunicáveis. O leitor não sabe onde olhar e o gráfico não tem mensagem clara.

    Segunda: ausência de hierarquia. Todos os painéis têm o mesmo tamanho e peso visual, o que implica que todas as relações são igualmente importantes. Numa análise real, geralmente você descobriu que uma ou duas relações são centrais para a história que quer contar — mas o pair plot não deixa isso evidente.

    Para transformar num gráfico comunicável:

    Primeiro, identificar durante a exploração qual painel (qual par de variáveis) conta a história mais importante. Segundo, extrair apenas esse scatter e ampliá-lo. Terceiro, adicionar a terceira variável como cor se ela for relevante. Quarto, adicionar título descritivo, anotações nos pontos de interesse, e linha de tendência se aplicável. O resultado é um único gráfico claro que comunica a relação descoberta no pair plot, sem o ruído de todas as outras combinações.

  4. Escreva o código para criar um FacetGrid que mostre a distribuição de salários (histograma) para cada combinação de nível e gênero usando o dataset da aula, com a mesma escala no eixo X para todos os painéis.

    ✓ Resposta:
    import seaborn as sns
    import matplotlib.pyplot as plt
    import matplotlib.ticker as ticker
    
    g = sns.FacetGrid(
        df,
        row='nivel',
        col='genero',
        row_order=['Júnior', 'Pleno', 'Sênior', 'Gerente'],
        col_order=['M', 'F'],
        height=2.8,
        aspect=1.3,
        sharex=True,    # mesma escala X em todos os painéis
        sharey=False    # escala Y livre — grupos têm tamanhos diferentes
    )
    
    g.map_dataframe(
        sns.histplot,
        x='salario',
        bins=25,
        color='steelblue',
        edgecolor='white',
        linewidth=0.4
    )
    
    g.set_axis_labels('Salário (R$)', 'Frequência')
    g.set_titles(
        row_template='{row_name}',
        col_template='{col_name}'
    )
    
    for ax in g.axes.flat:
        ax.xaxis.set_major_formatter(ticker.FuncFormatter(
            lambda x, p: f'R$ {x/1000:.0f}k'
        ))
        ax.tick_params(axis='x', rotation=30)
    
    g.figure.suptitle(
        'Distribuição salarial por nível e gênero\n(escala X igual em todos os painéis)',
        y=1.02
    )
    
    plt.tight_layout()
    plt.show()
    

    O parâmetro sharex=True garante que todos os painéis usam a mesma escala no eixo X, tornando a comparação visual direta e honesta. sharey=False permite que o eixo Y se ajuste à frequência de cada grupo — grupos menores (Gerente) teriam barras invisíveis se compartilhassem o eixo Y com grupos maiores (Júnior).

  5. Você criou um scatter multivariado com cinco variáveis codificadas como: eixo X, eixo Y, cor, tamanho dos pontos e forma dos pontos. Um colega diz que o gráfico "mostra tudo". Por que isso é um problema e como você simplificaria?

    ✓ Resposta:

    "Mostrar tudo" é exatamente o problema. Um gráfico eficaz não mostra tudo — mostra uma coisa claramente. Quando você codifica cinco variáveis num único gráfico, você distribui a atenção do leitor por cinco dimensões simultaneamente, e nenhuma delas recebe atenção suficiente para ser processada com precisão.

    Os problemas específicos com cada canal extra:

    Tamanho dos pontos é difícil de comparar com precisão — o olho humano não discrimina bem diferenças de área quando os pontos são pequenos ou estão espalhados. Além disso, pontos grandes cobrem pontos pequenos, criando overplotting seletivo.

    Forma dos pontos é o canal mais fraco disponível. Com mais de quatro ou cinco formas distintas, elas se tornam indistinguíveis especialmente quando os pontos são pequenos ou sobrepostos.

    O resultado é um gráfico que requer um esforço cognitivo enorme para ser decodificado — o leitor precisa consultar três legendas (cor, tamanho, forma), identificar cada ponto nas cinco dimensões e então tentar sintetizar o padrão. Isso inverte o propósito de uma visualização, que é reduzir, não aumentar, o trabalho cognitivo.

    Simplificação para um relatório: identificar qual das cinco variáveis é mais importante para a mensagem central, construir um scatter com apenas essa variável como cor (além dos eixos X e Y), e criar gráficos separados para as outras variáveis se elas forem relevantes. Se realmente há cinco variáveis importantes, um FacetGrid com dois ou três painéis mais cor é muito mais legível do que um único gráfico com cinco canais.

Referências

  • Wilke, Claus O. Fundamentals of Data Visualization. O'Reilly, 2019. Capítulos 17 a 21 cobrem visualizações multivariadas, small multiples e compound figures. Disponível em clauswilke.com/dataviz
  • Wickham, Hadley. ggplot2: Elegant Graphics for Data Analysis, 3ª edição. Springer, 2016. Embora use R, o capítulo sobre facets é a referência conceitual para FacetGrid. Disponível em ggplot2-book.org.
  • Seaborn — Multi-plot grids: seaborn.pydata.org/tutorial/axis_grids.html Tutorial oficial do seaborn sobre FacetGrid, PairGrid e suas variações.
  • Friendly, Michael. "Corrgrams: Exploratory Displays for Correlation Matrices". The American Statistician, 2002. Artigo clássico sobre visualização de matrizes de correlação, com discussão de diferentes esquemas de codificação.
  • Inselberg, Alfred. Parallel Coordinates: Visual Multidimensional Geometry and Its Applications. Springer, 2009. O livro de referência sobre parallel coordinates, escrito pelo inventor da técnica.
  • Knaflic, Cole Nussbaumer. Storytelling with Data. Wiley, 2015. Capítulos sobre foco e eliminação de distrações são especialmente relevantes para evitar sobrecarga visual em análises multivariadas.