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
-
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.
-
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.
-
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.
-
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=Truegarante que todos os painéis usam a mesma escala no eixo X, tornando a comparação visual direta e honesta.sharey=Falsepermite 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). -
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.