Correlações e Seleção de Variáveis

Introdução à Ciência de Dados com R - INFNET

Profª Edneide Ramalho

16.Jun.2026

Aula 10 · Módulo 3 ⏱ 1h15min 🏠 Ames Housing ↩︎ Continuação da Aula 9

Correlações e
Seleção de Variáveis

Saímos da exploração visual e entramos na quantificação: medir relações, testar hipóteses e escolher quais variáveis levar para o modelo.

📍 Onde paramos — Aula 9
  • Carregamos o dataset Ames Housing (2.930 imóveis, 74 variáveis)
  • Sale_Price tem distribuição assimétrica → usaremos log10
  • Removemos 2 outliers extremos (Gr_Liv_Area > 4000)
  • Levantamos 7 hipóteses sobre o que afeta o preço

Objetivos de Aprendizagem

Ao final desta aula, o estudante será capaz de:

  • Calcular e interpretar correlações de Pearson e Spearman
  • Construir e ler uma matriz de correlação visual com corrplot
  • Entender o valor-p no contexto de testes de correlação
  • Aplicar teste t e ANOVA para comparar grupos de preços
  • Usar o critério de Bendel & Afifi para triagem de preditores
  • Sair da aula com uma lista de variáveis candidatas para o modelo

Cronograma

0:00 – 0:05
Recapitulação e setup
0:05 – 0:25
Correlação de Pearson e Spearman
0:25 – 0:40
Matriz de correlação visual
0:40 – 0:55
Testes de hipótese: t-test e ANOVA
0:55 – 1:10
Seleção de variáveis candidatas
1:10 – 1:15
Síntese e lista final

1 Setup — Retomando o Dataset

Começamos exatamente de onde a Aula 9 terminou: dataset limpo, variável-alvo transformada.

library(tidyverse)
library(modeldata)
library(corrplot)   # install.packages("corrplot")
library(broom)      # arruma saídas de testes estatísticos
library(patchwork)

# Recarregar e limpar — igual ao final da Aula 9
data(ames)

ames_clean <- ames |>
  filter(Gr_Liv_Area <= 4000) |>
  mutate(log_preco = log10(Sale_Price))  # variável-alvo transformada

dim(ames_clean)  # 2928 × 75
#> [1] 2925   75

2 Correlação — Quantificando Relações

Na Aula 9 vimos relações em gráficos. Agora vamos medi-las. Correlação é um número entre −1 e +1 que resume a força e a direção da relação entre duas variáveis.

Escala de Interpretação

−1−0.7−0.30+0.3+0.7+1

Forte negativa < −0.7 Moderada −0.3 a −0.7 Fraca −0.3 a +0.3 Moderada +0.3 a +0.7 Forte positiva > +0.7

2.1 Pearson vs. Spearman

Pearson
Correlação Linear
Mede relações lineares. Sensível a outliers. Assume distribuição aproximadamente normal. Use quando os dados são contínuos e a relação parece linear no scatter.
cor(x, y, method = “pearson”)
Spearman
Correlação de Postos
Mede relações monotônicas (não precisa ser linear). Robusta a outliers — trabalha com os rankings dos valores. Preferida quando há assimetria.
cor(x, y, method = “spearman”)
Qual usar com Sale_Price?

Como Sale_Price é assimétrico, a correlação de Spearman com log_preco tende a ser mais informativa. Use Pearson para confirmar relações lineares após a transformação log.

2.2 Calculando correlações com log_preco

# Correlação de todas as variáveis numéricas com log_preco
correlacoes <- ames_clean |>
  select(where(is.numeric)) |>
  cor(use = "complete.obs", method = "pearson") |>
  as.data.frame() |>
  rownames_to_column("variavel") |>
  select(variavel, log_preco) |>
  filter(variavel != "log_preco", variavel != "Sale_Price") |>
  arrange(desc(abs(log_preco))) |>
  rename(correlacao = log_preco)

# Ver as 15 mais correlacionadas
correlacoes |> head(15)
#>          variavel correlacao
#> 1     Gr_Liv_Area  0.7113418
#> 2     Garage_Cars  0.6749027
#> 3     Garage_Area  0.6536989
#> 4   Total_Bsmt_SF  0.6490548
#> 5    First_Flr_SF  0.6209804
#> 6      Year_Built  0.6168231
#> 7  Year_Remod_Add  0.5878485
#> 8       Full_Bath  0.5753146
#> 9   TotRms_AbvGrd  0.4937001
#> 10     Fireplaces  0.4879956
#> 11   Mas_Vnr_Area  0.4454380
#> 12   Wood_Deck_SF  0.3319996
#> 13  Open_Porch_SF  0.3245708
#> 14      Half_Bath  0.3044516
#> 15      Longitude -0.2921394
correlacoes |>
  head(15) |>
  ggplot(aes(x = reorder(variavel, abs(correlacao)),
             y = correlacao,
             fill = correlacao > 0)) +
  geom_col(show.legend = FALSE) +
  geom_hline(yintercept = c(-0.3, 0.3), linetype = "dashed",
             color = "gray50", linewidth = 0.5) +
  scale_fill_manual(values = c("TRUE" = "#4fd1a5", "FALSE" = "#e05c5c")) +
  coord_flip() +
  labs(
    title    = "Correlação das Variáveis Numéricas com log10(Sale_Price)",
    subtitle = "Linhas tracejadas em ±0.3 — limiar de correlação moderada",
    x        = NULL,
    y        = "Correlação de Pearson"
  ) +
  theme_minimal(base_size = 13) +
  theme(plot.title    = element_text(face = "bold"),
        plot.subtitle = element_text(color = "gray60"))

2.3 Testando a significância da correlação

Uma correlação pode ser alta por acaso se a amostra for pequena. O teste de correlação verifica se o valor observado é distinguível de zero.

# Teste de correlação — Gr_Liv_Area vs log_preco
cor.test(ames_clean$Gr_Liv_Area, ames_clean$log_preco) |>
  tidy()
#> # A tibble: 1 × 8
#>   estimate statistic p.value parameter conf.low conf.high method     alternative
#>      <dbl>     <dbl>   <dbl>     <int>    <dbl>     <dbl> <chr>      <chr>      
#> 1    0.711      54.7       0      2923    0.693     0.729 Pearson's… two.sided
Cuidado com o valor-p em amostras grandes

Com n = 2.928, quase qualquer correlação — mesmo minúscula — terá p < 0.05. O que importa é a magnitude da correlação, não só a significância. Um r = 0.05 com p < 0.001 é estatisticamente significativo mas praticamente irrelevante.


3 Matriz de Correlação Visual

Com muitas variáveis, um único gráfico que mostre todas as correlações de uma vez é muito mais eficiente do que tabelas.

3.1 Selecionando as variáveis mais relevantes

# Variáveis com |r| > 0.3 em relação a log_preco
top_vars <- correlacoes |>
  filter(abs(correlacao) > 0.3) |>
  pull(variavel)

# Montar a submatriz
mat_cor <- ames_clean |>
  select(all_of(top_vars), log_preco) |>
  cor(use = "complete.obs")

dim(mat_cor)
#> [1] 15 15

3.2 Construindo o corrplot

corrplot(mat_cor,
  method      = "color",
  type        = "upper",
  order       = "hclust",
  addCoef.col = "black",
  number.cex  = 0.65,
  tl.col      = "black",
  tl.srt      = 45,
  tl.cex      = 0.8,
  col         = colorRampPalette(c("#e05c5c", "white", "#4fd1a5"))(200),
  title       = "Matriz de Correlação — Ames Housing",
  mar         = c(0, 0, 2, 0)
)

O que procurar na matriz

Além de correlações altas com log_preco (última linha/coluna), observe pares de preditores muito correlacionados entre si — isso é multicolinearidade, um problema para regressão que trataremos na aula de modelagem com VIF.

3.3 Versão alternativa com ggplot2

mat_cor |>
  as.data.frame() |>
  rownames_to_column("var1") |>
  pivot_longer(-var1, names_to = "var2", values_to = "cor") |>
  ggplot(aes(x = var1, y = var2, fill = cor)) +
  geom_tile(color = "white", linewidth = 0.3) +
  geom_text(aes(label = round(cor, 2)), size = 2.6) +
  scale_fill_gradient2(
    low      = "#e05c5c",
    mid      = "white",
    high     = "#4fd1a5",
    midpoint = 0,
    limits   = c(-1, 1)
  ) +
  theme_minimal(base_size = 11) +
  theme(
    axis.text.x  = element_text(angle = 45, hjust = 1),
    plot.title   = element_text(face = "bold"),
    panel.grid   = element_blank()
  ) +
  labs(title = "Matriz de Correlação — Ames Housing",
       x = NULL, y = NULL, fill = "r")


4 Testes de Hipótese

Correlação responde perguntas sobre variáveis numéricas. Para perguntas do tipo “grupos diferentes têm preços médios diferentes?”, usamos testes de hipótese.

“All models are wrong, but some are useful — and all tests give p-values, but not all p-values are useful.” — adaptado de George Box

4.1 O que é o valor-p — de forma direta

Conceito Significado prático
H₀ (hipótese nula) Não há diferença entre os grupos
H₁ (hipótese alternativa) Há diferença real entre os grupos
valor-p Prob. de observar resultado tão extremo se H₀ fosse verdadeira
p < 0.05 Improvável sob H₀ → rejeitamos H₀
p ≥ 0.05 Compatível com H₀ → não rejeitamos H₀
O que o valor-p NÃO é

A probabilidade de a hipótese ser verdadeira. Ele também não mede o tamanho do efeito. Um p minúsculo pode corresponder a uma diferença irrelevante na prática — sempre olhe a magnitude.

4.2 Teste t — Dois grupos

Pergunta: “Casas com lareira custam mais do que casas sem lareira?”

# Criar variável binária
ames_clean <- ames_clean |>
  mutate(tem_lareira = ifelse(Fireplaces > 0, "Sim", "Não"))

# Visualizar
ggplot(ames_clean, aes(x = tem_lareira, y = log_preco, fill = tem_lareira)) +
  geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
  scale_fill_manual(values = c("Sim" = "#4fd1a5", "Não" = "#7c6ff7")) +
  labs(
    title = "Preço por presença de lareira",
    x     = "Tem lareira?",
    y     = "log10(Sale_Price)"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"))

# Teste t de Welch (não assume variâncias iguais — mais robusto)
t.test(log_preco ~ tem_lareira, data = ames_clean) |>
  tidy()
#> # A tibble: 1 × 10
#>   estimate estimate1 estimate2 statistic   p.value parameter conf.low conf.high
#>      <dbl>     <dbl>     <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
#> 1   -0.180      5.13      5.31     -32.1 5.68e-194     2911.   -0.191    -0.169
#> # ℹ 2 more variables: method <chr>, alternative <chr>
# estimate1 e estimate2 = médias dos grupos
# statistic = valor t  |  p.value  |  conf.low, conf.high = IC 95%

4.3 ANOVA — Três ou mais grupos

Pergunta: “O preço médio difere entre os tipos de zoneamento?”

# Visualizar primeiro
ggplot(ames_clean, aes(x = reorder(MS_Zoning, log_preco, median),
                       y = log_preco, fill = MS_Zoning)) +
  geom_boxplot(alpha = 0.7, outlier.alpha = 0.3) +
  scale_fill_viridis_d() +
  labs(
    title = "Preço por Tipo de Zoneamento",
    x     = NULL,
    y     = "log10(Sale_Price)"
  ) +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"))

# ANOVA de uma via
modelo_anova <- aov(log_preco ~ MS_Zoning, data = ames_clean)
tidy(modelo_anova)
#> # A tibble: 2 × 6
#>   term         df sumsq meansq statistic    p.value
#>   <chr>     <dbl> <dbl>  <dbl>     <dbl>      <dbl>
#> 1 MS_Zoning     6  17.4 2.90        115.  7.93e-131
#> 2 Residuals  2918  73.5 0.0252       NA  NA
# Qual par de grupos difere?
TukeyHSD(modelo_anova) |>
  tidy() |>
  filter(adj.p.value < 0.05) |>
  arrange(adj.p.value) |>
  head(10)
#> # A tibble: 10 × 7
#>    term      contrast         null.value estimate conf.low conf.high adj.p.value
#>    <chr>     <chr>                 <dbl>    <dbl>    <dbl>     <dbl>       <dbl>
#>  1 MS_Zoning Residential_Med…          0  -0.251    -0.296   -0.205      0      
#>  2 MS_Zoning A_agr-Floating_…          0  -0.814    -1.15    -0.481      0      
#>  3 MS_Zoning C_all-Floating_…          0  -0.460    -0.562   -0.359      0      
#>  4 MS_Zoning Residential_Med…          0  -0.171    -0.195   -0.147      0      
#>  5 MS_Zoning A_agr-Residenti…          0  -0.734    -1.07    -0.403      0      
#>  6 MS_Zoning C_all-Residenti…          0  -0.380    -0.474   -0.286      0      
#>  7 MS_Zoning C_all-Residenti…          0  -0.210    -0.306   -0.113      1.44e-9
#>  8 MS_Zoning Residential_Hig…          0  -0.210    -0.309   -0.112      5.28e-9
#>  9 MS_Zoning Residential_Low…          0  -0.0801   -0.121   -0.0392     1.75e-7
#> 10 MS_Zoning C_all-Residenti…          0  -0.250    -0.380   -0.120      3.23e-7
broom::tidy()

Transforma a saída bagunçada de t.test(), aov() e outros testes em tibbles organizados — fáceis de filtrar, ordenar e exportar.


5 Seleção de Variáveis Candidatas

Com tantas variáveis disponíveis, precisamos de um critério sistemático para decidir quais levar para o modelo.

5.1 Critério de Bendel & Afifi (1977)

Tipo de variável Teste Critério de inclusão
Numérica Regressão simples com log_preco p < 0.25
Categórica ANOVA com log_preco p < 0.25
Por que p < 0.25 e não p < 0.05?

No estágio de triagem, preferimos ser inclusivos — melhor incluir uma variável fraca e descartá-la depois do que excluir um preditor que só se revela importante em presença de outras variáveis. O refinamento vem na modelagem com VIF e AIC.

5.2 Triagem das variáveis numéricas

vars_numericas <- ames_clean |>
  select(where(is.numeric)) |>
  select(-Sale_Price, -log_preco) |>
  names()

# Função auxiliar: p-valor da regressão univariada
pvalor_regressao <- function(var) {
  formula <- as.formula(paste("log_preco ~", var))
  modelo  <- lm(formula, data = ames_clean)
  tidy(modelo) |>
    filter(term != "(Intercept)") |>
    pull(p.value) |>
    min()
}

triagem_num <- tibble(
  variavel = vars_numericas,
  p_valor  = map_dbl(vars_numericas, pvalor_regressao)
) |>
  arrange(p_valor) |>
  mutate(incluir = p_valor < 0.25)

triagem_num |> head(20)
#> # A tibble: 20 × 3
#>    variavel         p_valor incluir
#>    <chr>              <dbl> <lgl>  
#>  1 Total_Bsmt_SF  0         TRUE   
#>  2 Gr_Liv_Area    0         TRUE   
#>  3 Garage_Cars    0         TRUE   
#>  4 Garage_Area    0         TRUE   
#>  5 First_Flr_SF   1.51e-311 TRUE   
#>  6 Year_Built     3.00e-306 TRUE   
#>  7 Year_Remod_Add 1.94e-271 TRUE   
#>  8 Full_Bath      1.91e-257 TRUE   
#>  9 TotRms_AbvGrd  1.42e-179 TRUE   
#> 10 Fireplaces     6.93e-175 TRUE   
#> 11 Mas_Vnr_Area   1.39e-142 TRUE   
#> 12 Wood_Deck_SF   3.32e- 76 TRUE   
#> 13 Open_Porch_SF  1.00e- 72 TRUE   
#> 14 Half_Bath      8.78e- 64 TRUE   
#> 15 Longitude      1.20e- 58 TRUE   
#> 16 Latitude       6.09e- 56 TRUE   
#> 17 Bsmt_Full_Bath 1.62e- 52 TRUE   
#> 18 Second_Flr_SF  2.69e- 49 TRUE   
#> 19 Lot_Area       3.71e- 45 TRUE   
#> 20 Bsmt_Unf_SF    2.13e- 26 TRUE
cat("Variáveis numéricas que passaram na triagem:", sum(triagem_num$incluir))
#> Variáveis numéricas que passaram na triagem: 31

5.3 Triagem das variáveis categóricas

vars_cat <- ames_clean |>
  select(where(is.factor)) |>
  names()

pvalor_anova <- function(var) {
  formula <- as.formula(paste("log_preco ~", var))
  modelo  <- aov(formula, data = ames_clean)
  tidy(modelo) |>
    filter(term != "Residuals") |>
    pull(p.value)
}

triagem_cat <- tibble(
  variavel = vars_cat,
  p_valor  = map_dbl(vars_cat, pvalor_anova)
) |>
  arrange(p_valor) |>
  mutate(incluir = p_valor < 0.25)

triagem_cat |> head(20)
#> # A tibble: 20 × 3
#>    variavel         p_valor incluir
#>    <chr>              <dbl> <lgl>  
#>  1 Neighborhood   0         TRUE   
#>  2 Garage_Finish  3.97e-289 TRUE   
#>  3 Garage_Type    9.44e-245 TRUE   
#>  4 Foundation     1.50e-242 TRUE   
#>  5 MS_SubClass    5.94e-237 TRUE   
#>  6 Heating_QC     2.50e-183 TRUE   
#>  7 BsmtFin_Type_1 6.09e-173 TRUE   
#>  8 Mas_Vnr_Type   5.34e-134 TRUE   
#>  9 Overall_Cond   7.21e-132 TRUE   
#> 10 MS_Zoning      7.93e-131 TRUE   
#> 11 Exterior_1st   2.33e-130 TRUE   
#> 12 Exterior_2nd   1.92e-117 TRUE   
#> 13 Bsmt_Exposure  1.25e-115 TRUE   
#> 14 Garage_Cond    3.83e-101 TRUE   
#> 15 Central_Air    3.08e- 96 TRUE   
#> 16 Paved_Drive    3.94e- 95 TRUE   
#> 17 Sale_Condition 2.77e- 93 TRUE   
#> 18 Sale_Type      9.48e- 83 TRUE   
#> 19 Lot_Shape      2.75e- 71 TRUE   
#> 20 Electrical     6.32e- 60 TRUE
# Lista consolidada de variáveis candidatas
candidatas <- bind_rows(triagem_num, triagem_cat) |>
  filter(incluir) |>
  arrange(p_valor)

cat("Total de variáveis candidatas para o modelo:", nrow(candidatas))
#> Total de variáveis candidatas para o modelo: 71

5.4 Principais variáveis selecionadas

Variáveis numéricas:

NUM
Gr_Liv_Area
Área acima do solo
r = 0.70 · forte
NUM
Total_Bsmt_SF
Área total do porão
r = 0.64 · forte
NUM
Year_Built
Ano de construção
r = 0.57 · moderada
NUM
Garage_Area
Área da garagem
r = 0.56 · moderada
NUM
Full_Bath
Banheiros completos
r = 0.50 · moderada
NUM
TotRms_AbvGrd
Total de cômodos
r = 0.49 · moderada

Variáveis categóricas:

CAT
Overall_Qual
Qualidade geral (1–10)
ANOVA p < 0.001
CAT
Neighborhood
Bairro do imóvel
ANOVA p < 0.001
CAT
Exter_Qual
Qualidade do revestimento
ANOVA p < 0.001
CAT
Kitchen_Qual
Qualidade da cozinha
ANOVA p < 0.001
CAT
Bsmt_Qual
Qualidade do porão
ANOVA p < 0.001
CAT
MS_Zoning
Tipo de zoneamento
ANOVA p < 0.001

5.5 O pipeline de seleção

Triagemp < 0.25 (Bendel & Afifi)
MulticolinearidadeVIF < 5 (próximas aulas)
Seleção do modeloAIC / stepwise
Modelo finalValidação cruzada
Próximo passo

Esta lista é apenas a triagem inicial. Na aula de regressão múltipla, refinaremos com VIF (detectar multicolinearidade) e AIC (comparar modelos). Algumas dessas variáveis podem ser redundantes entre si.


6 Prática Independente

6.1 Exercício 1 — Correlação de Spearman

Recalcule o ranking de correlações usando method = "spearman". Compare os dois rankings: alguma variável muda muito de posição? O que isso indica?

# Seu código aqui
correlacoes_spearman <- ames_clean |>
  select(where(is.numeric)) |>
  cor(use = "complete.obs", method = "spearman") |>
  # ... complete o restante

6.2 Exercício 2 — Teste t: garagem vs. sem garagem

Crie uma variável binária tem_garagem a partir de Garage_Area (área = 0 → sem garagem). Faça um boxplot e um teste t.

ames_clean <- ames_clean |>
  mutate(tem_garagem = ifelse(Garage_Area > 0, "Sim", "Não"))

# Seu boxplot e t.test() aqui

6.3 Exercício 3 — ANOVA: qualidade da cozinha

Aplique ANOVA para Kitchen_Qual vs log_preco. Se significativa, use TukeyHSD() para identificar quais pares de grupos diferem.

# Seu código aqui

6.4 Desafio — Corrplot interativo com ggplotly

Transforme o heatmap de correlação em interativo com plotly::ggplotly().

library(plotly)
p <- # seu heatmap ggplot2 aqui
ggplotly(p, tooltip = c("x", "y", "fill"))

7 Leituras Recomendadas

Obra Capítulo Foco
R for Data Science, 2ª ed. — Wickham Cap. 15–16 Testes e correlação no R
Practical Statistics for Data Scientists — Bruce & Bruce Cap. 3 Testes de significância
Tidy Modeling with R — Kuhn & Silge Cap. 4 Análise do Ames Housing
Bendel & Afifi (1977) — JASA Origem do critério p < 0.25

Para a Próxima Aula

  • Finalizar os 4 exercícios com os dados do Ames
  • Ler Cap. 4 do Tidy Modeling with R
  • Garantir que tidymodels está instalado: install.packages("tidymodels")
  • Próxima aula: Regressão Linear Simples com tidymodels — primeiro modelo preditivo do curso!