Aula 04 — Análise Bivariada

MBA Data Science · Análise Exploratória de Dados com R

Professora: Edneide Ramalho

26/03/2026

1 Objetivo da aula

  • Finalizar exercícios da aula anterior
  • Fazer Análise Bivariada

2 Pacotes

library(tidyverse)
library(janitor)
library(skimr)
library(naniar)
library(visdat)
library(DataExplorer)
library(patchwork)
library(scales)
library(knitr)
library(kableExtra)
library(gtsummary)
library(corrplot)
library(ggmosaic)

3 Dados Originais

ames_raw <- read_csv("dados/AmesHousing.csv") |> 
  clean_names()

4 Dados Limpos

# .RDS — recomendado para um objeto
# saveRDS(ames_clean, "dados_limpos.rds")

# Para carregar depois:
ames_clean <- readRDS("dados_limpos.rds")

5 Exercícios Práticos

📝 Exercício 1 — Inspecionar NAs em lot_frontage

Tarefa: Calcule a proporção de NAs em lot_frontage no dataset original (ames_raw). Em seguida, verifique se a proporção de NAs varia entre bairros (neighborhood). Qual bairro tem a maior proporção de NAs nessa variável?

# Proporção geral de NAs em lot_frontage
ames_raw |>
  summarise(pct_na = round(100*mean(is.na(lot_frontage)), 2))

# Proporção por bairro
ames_raw |>
  group_by(neighborhood) |>
  summarise(pct_na = mean(is.na(lot_frontage)),
            n      = n()) |>
  arrange(desc(pct_na))
  • Os bairros de GrnHill e Landmrk não tem essa variável, ou seja, tem todos os valores NA.

  • Dos bairros que tem esse valor preenchido, temos que ClearCr, NWAmes, Sawyer, Veenker, Gilbert são os que mais tem NA, com proporção de dados faltantes acima dos 30%. No total, 28 bairros apresentam alguma quantidade de dados faltantes para a variável lot_frontage.

Podemos ainda ampliar essa resposta, mostrando as estatísticas descritivas dessa variável para os Top 10 bairros com maior valor de NA.

ames_raw |> 
  group_by(neighborhood) |> 
  summarise(
    media = mean(lot_frontage, na.rm = TRUE),
    mediana = median(lot_frontage, na.rm = TRUE)
  ) |> 
  ungroup() |> 
  arrange(desc(mediana)) |> 
  kbl(col.names = c("Bairro", "Média", "Mediana"), 
      align = "lrr", 
      caption = "Resumo sobre Lot Frontage por bairro") |>
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Resumo sobre Lot Frontage por bairro
Bairro Média Mediana
NridgHt 84.18405 92.0
NoRidge 91.62963 89.0
Timber 81.15789 82.0
ClearCr 88.15000 80.5
NWAmes 81.51765 80.0
Veenker 72.00000 80.0
Mitchel 75.14444 74.0
NAmes 75.21067 73.0
Somerst 64.54938 72.5
Sawyer 74.55102 72.0
CollgCr 71.33636 70.0
Crawfor 69.95181 70.0
SawyerW 70.66981 67.0
Edwards 66.91011 65.0
Gilbert 74.20721 64.0
IDOTRR 62.24138 60.0
OldTown 61.77729 60.0
SWISU 59.06818 60.0
StoneBr 62.17391 60.0
BrkSide 55.78947 51.0
Blmngtn 46.90000 43.0
Greens 41.00000 40.0
Blueste 27.30000 24.0
NPkVill 28.14286 24.0
BrDale 21.50000 21.0
MeadowV 25.60606 21.0
GrnHill NaN NA
Landmrk NaN NA

📝 Exercício 2 — Distribuição de year_built

Tarefa: Crie um histograma da variável year_built usando ggplot2. Adicione uma linha vertical indicando a mediana. Interprete: a distribuição é assimétrica? Para qual lado?

ames_clean |>
  ggplot(aes(x = year_built)) +
  geom_histogram(bins = 40, fill = "#534AB7", alpha = 0.8, color = "white") +
  geom_vline(xintercept = median(ames_clean$year_built),
             color = "#C04828", linewidth = 1.2, linetype = "dashed") +
    geom_vline(xintercept = mean(ames_clean$year_built),
             color = "#0c0252", linewidth = 1, linetype = "dotted")+
  labs(title = "Distribuição do Ano de Construção",
       x = "Ano de Construção", y = "Frequência") +
  theme_minimal()

Como a média é menor que a mediana, podemos observar que a distribuição é assimétrica à esquerda, ou seja, tem casas que foram construídas há muito tempo, antes de 1900, e isso puxa a média para a esquerda.

Vamos ver as casas mais antigas:

ames_clean |> 
  filter(year_built < 1900) |> 
  View()

📝 Exercício 3 — Preço médio por tipo de construção

Tarefa: Calcule a mediana do sale_price para cada categoria de bldg_type. Ordene do maior para o menor e crie um gráfico de barras horizontais. Qual tipo de imóvel tem a maior mediana de preço?

bldg_type: tipo de habitação

O tipo de habitação mais presente é o 1Fam, com 2420 registros. Mas, o tipo de casa com maior mediana de preço é o TwnhsE (casa geminada de esquina), com mediana de preço de US$ 180,000.

ames_clean |>
  group_by(bldg_type) |>
  summarise(
    mediana_preco = median(sale_price),
    n = n()
  ) |>
  ggplot(aes(x = reorder(bldg_type, mediana_preco), y = mediana_preco)) +
  geom_col(fill = "#0F6E56", alpha = 0.85, width = 0.6) +
  geom_text(aes(label = paste0(dollar(mediana_preco / 1000, prefix = "US$"), "k\n(n=", n, ")")),
            hjust = -0.1, size = 3, color = "#73726C") +
  coord_flip() +
  scale_y_continuous(labels = dollar_format(prefix = "US$", 
                                            scale = 1e-3, 
                                            suffix = "k"),
                     limits = c(0, 230000)) +
  labs(title = "Mediana do Preço por Tipo de Imóvel",
       x = NULL, y = "Mediana do Preço de Venda") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"),
        panel.grid.major.y = element_blank())

6 Análise bivariada

7 Mapa de Navegação: qual técnica usar?

Antes de qualquer análise bivariada, a primeira pergunta é sempre: “Quais são os tipos das duas variáveis que quero relacionar?”

Variável X Variável Y Técnica principal Função em R
Numérica Numérica Scatter plot + linha de tendência geom_point() + geom_smooth()
Numérica Numérica Correlação de Pearson ou Spearman cor() · cor.test() · corrplot()
Numérica Categórica Boxplot / violin plot por grupo geom_boxplot() · geom_violin()
Categórica Numérica Boxplot / violin plot por grupo geom_boxplot() · stat_summary()
Categórica Categórica Tabela de contingência / gráfico de mosaico table() · geom_mosaic() · geom_tile()
🗺️ Como usar este mapa

Nas seções seguintes, cada combinação de tipos terá sua própria seção com teoria, visualização e exercício. Você pode navegar diretamente pelo índice à esquerda.

8 Numérica vs. numérica

8.1 Intuição: o que é correlação?

Correlação mede a força e direção da relação linear entre duas variáveis numéricas. Mas antes de qualquer fórmula, olhe o scatter plot.

set.seed(42)
n <- 300

d_forte_pos  <- tibble(x = rnorm(n), y = x * 0.9 + rnorm(n, sd = 0.4),
                        tipo = "Correlação forte positiva\nr ≈ +0.90")
d_fraca_pos  <- tibble(x = rnorm(n), y = x * 0.4 + rnorm(n, sd = 0.9),
                        tipo = "Correlação fraca positiva\nr ≈ +0.40")
d_nula       <- tibble(x = rnorm(n), y = rnorm(n),
                        tipo = "Sem correlação linear\nr ≈ 0.00")
d_nao_linear <- tibble(x = seq(-3, 3, length.out = n),
                        y = x^2 + rnorm(n, sd = 0.5),
                        tipo = "Relação não-linear\nr ≈ 0.00 (mas há padrão!)")

bind_rows(d_forte_pos, d_fraca_pos, d_nula, d_nao_linear) |>
  ggplot(aes(x = x, y = y)) +
  geom_point(alpha = 0.35, color = "#185FA5", size = 1.2) +
  geom_smooth(method = "lm", se = FALSE, color = "#C04828",
              linewidth = 0.9, linetype = "dashed") +
  facet_wrap(~ tipo, scales = "free", ncol = 2) +
  labs(title = "Padrões de correlação",
       subtitle = "A linha vermelha é sempre a regressão linear",
       x = NULL, y = NULL) +
  theme_minimal(base_size = 12) +
  theme(plot.title    = element_text(face = "bold"),
        strip.text    = element_text(face = "bold", size = 10),
        panel.spacing = unit(1.2, "lines"))

⚠️ Armadilha clássica n°1: correlação zero ≠ sem relação

O 4° painel acima tem correlação de Pearson próxima de zero — mas claramente há uma relação quadrática. Sempre visualize antes de calcular. A correlação de Pearson só captura relações lineares.

8.2 Correlação de Pearson: teoria

\[r = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i-\bar{x})^2 \cdot \sum(y_i-\bar{y})^2}}\]

Interpretação prática (regra geral — não absoluta):

|r| Força
0,00 – 0,19 Desprezível
0,20 – 0,39 Fraca
0,40 – 0,59 Moderada
0,60 – 0,79 Forte
0,80 – 1,00 Muito forte

Pressupostos de Pearson: - Ambas as variáveis aproximadamente normais - Relação linear - Sem outliers extremos influentes

Quando esses pressupostos não valem → use Spearman (baseado em ranks).

8.2.1 Área habitável vs. preço de venda

ames_clean |>
  ggplot(aes(x = gr_liv_area, y = sale_price,
             color = as.integer(overall_qual))) +
  geom_point(alpha = 0.4, size = 1.5) +
  geom_smooth(method = "lm", se = TRUE,
              color = "#C04828", fill = "#C04828",
              alpha = 0.15, linewidth = 1) +
  scale_color_gradient(low = "#B5D4F4", high = "#0C447C",
                       name = "Qualidade\ngeral") +
  scale_x_continuous(labels = comma) +
  scale_y_continuous(labels = dollar_format(prefix = "US$", scale = 1e-3,
                                             suffix = "k")) +
  labs(title = "Área habitável vs. Preço de venda",
       subtitle = "Cor mais escura = qualidade geral mais alta",
       x = "Área habitável (sqft)", y = "Preço de venda") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"))

Vamos calular a correlação:

# Correlação pontual
r_pearson <- cor(ames_clean$gr_liv_area, ames_clean$sale_price,
                 method = "pearson")
r_spearman <- cor(ames_clean$gr_liv_area, ames_clean$sale_price,
                  method = "spearman")

r_pearson
[1] 0.7194627
r_spearman
[1] 0.7233735

Temos uma correlação positiva e forte. Então, podemos pensar que quanto maior a área habitável, maior o preço de venda da casa.

8.2.2 Heatmap de correlação geral

vars_num <- ames_clean |>
  select(sale_price, gr_liv_area, total_bsmt_sf, x1st_flr_sf,
         garage_area, lot_area, year_built, year_remod_add,
         overall_qual_int = overall_qual) |>
  mutate(overall_qual_int = as.integer(overall_qual_int))

vars_num <- ames_clean |>
  select(sale_price, gr_liv_area, total_bsmt_sf, x1st_flr_sf,
         garage_area, lot_area, year_built, year_remod_add,
         overall_qual_int = overall_qual) |>
  mutate(overall_qual_int = as.integer(overall_qual_int))

M <- cor(vars_num, use = "complete.obs")

corrplot(M,
         method    = "color",
         type      = "upper",
         order     = "hclust",
         tl.cex    = 0.85,
         tl.col    = "#1A1A18",
         addCoef.col = "#1A1A18",
         number.cex  = 0.72,
         col         = colorRampPalette(c("#C04828", "white", "#185FA5"))(200),
         diag        = FALSE,
         mar         = c(0, 0, 1, 0),
         title       = "Correlações de Pearson — Ames Housing")

As variáveis com maiores correlações com sales price são:

  • gr_liv_area

  • gr_liv_area

  • overall_qual_int

  • total_bsmt_sf

  • garage_area

  • year_built

  • x1st_flr_sf

9 Numérica × Categórica

9.1 Intuição: comparar distribuições entre grupos

Quando uma variável é numérica e a outra é categórica, a pergunta é: “A distribuição da variável numérica muda entre os grupos?”

As ferramentas principais são boxplot, violin plot e stat_summary().

9.2 Preço por tipo de construção (bldg_type)

ames_clean |>
  mutate(bldg_type = fct_reorder(bldg_type,
                                  sale_price, .fun = median)) |>
  ggplot(aes(x = bldg_type, y = sale_price, fill = bldg_type)) +
  geom_boxplot(alpha = 0.75, outlier.alpha = 0.4,
               outlier.size = 1.2, width = 0.55,
               show.legend = FALSE) +
  stat_summary(fun = mean, geom = "point",
               shape = 18, size = 3, color = "#C04828",
               show.legend = FALSE) +
  scale_fill_manual(values = c("#B5D4F4","#85B7EB","#378ADD",
                                "#185FA5","#0C447C")) +
  scale_y_continuous(labels = dollar_format(prefix = "US$",
                                             scale = 1e-3, suffix = "k")) +
  labs(title = "Preço de venda por tipo de imóvel",
       subtitle = "Losango vermelho = média · Linha horizontal = mediana · Caixas ordenadas por mediana",
       x = "Tipo de imóvel", y = "Preço de venda") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"))

9.3 Violin plot: ver a forma completa

ames_clean |>
  ggplot(aes(x = overall_qual,
             y = sale_price,
             fill = overall_qual)) +
  geom_violin(alpha = 0.55, color = NA, show.legend = FALSE) +
  geom_boxplot(width = 0.12, outlier.shape = NA,
               fill = "white", alpha = 0.7,
               show.legend = FALSE) +
  scale_fill_manual(values = colorRampPalette(
    c("#B5D4F4", "#185FA5", "#042C53"))(10)) +
  scale_y_continuous(labels = dollar_format(prefix = "US$",
                                             scale = 1e-3, suffix = "k")) +
  labs(title = "Preço de venda por qualidade geral",
       subtitle = "Violin mostra a forma completa da distribuição · Boxplot interno mostra os quartis",
       x = "Qualidade geral (1 = muito ruim → 10 = excelente)",
       y = "Preço de venda") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"))

9.4 Grouped summaries: tabela de estatísticas por grupo

ames_clean |>
  group_by(bldg_type) |>
  summarise(
    n           = n(),
    mediana     = median(sale_price),
    media       = mean(sale_price),
    dp          = sd(sale_price),
    cv          = as.character(round(dp / media, 3)),
    q1          = quantile(sale_price, 0.25),
    q3          = quantile(sale_price, 0.75)
  ) |>
  arrange(desc(mediana)) |>
  mutate(across(c(mediana, media, dp, q1, q3),
                ~ dollar(., prefix = "US$", big.mark = ","))) |>
  kbl(col.names = c("Tipo", "n", "Mediana", "Média",
                     "DP", "CV", "Q1", "Q3"),
      align = "lrrrrrrrr") |>
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = TRUE)
Tipo n Mediana Média DP CV Q1 Q3
TwnhsE 233 US$180,000 US$192,312 US$66,191.74 0.344 US$145,000 US$222,000
1Fam 2420 US$165,000 US$184,356 US$81,295.99 0.441 US$130,000 US$220,000
Duplex 109 US$136,905 US$139,809 US$39,498.97 0.283 US$118,858 US$153,337
Twnhs 101 US$130,000 US$135,934 US$41,938.93 0.309 US$100,500 US$170,000
2fmCon 62 US$122,250 US$125,582 US$31,089.24 0.248 US$106,562 US$140,000
  • Outra maneira de fazer (mais resumida):
ames_clean |> 
  select(sale_price, bldg_type) |> 
  tbl_summary(by = bldg_type)
Characteristic 1Fam
N = 2,420
1
2fmCon
N = 62
1
Duplex
N = 109
1
Twnhs
N = 101
1
TwnhsE
N = 233
1
sale_price 165,000 (130,000, 220,000) 122,250 (106,250, 140,000) 136,905 (118,858, 153,337) 130,000 (100,500, 170,000) 180,000 (145,000, 222,000)
1 Median (Q1, Q3)

9.5 Comparação de médias por bairro

ames_clean |>
  group_by(neighborhood) |>
  summarise(
    mediana = median(sale_price),
    n       = n()
  ) |>
  slice_max(mediana, n = 15) |>
  mutate(neighborhood = fct_reorder(neighborhood, mediana)) |>
  ggplot(aes(x = neighborhood, y = mediana)) +
  geom_col(aes(fill = mediana), width = 0.7,
           show.legend = FALSE) +
  geom_text(aes(label = paste0(dollar(mediana / 1000,
                                       prefix = "US$"), "k\nn=", n)),
            hjust = -0.1, size = 3, color = "#5C5B57") +
  coord_flip() +
  scale_fill_gradient(low = "#85B7EB", high = "#0C447C") +
  scale_y_continuous(labels = dollar_format(prefix = "US$",
                                             scale = 1e-3, suffix = "k"),
                     limits = c(0, 360000),
                     expand = c(0, 0)) +
  labs(title = "Mediana do preço por bairro — top 15",
       subtitle = "Bairros ordenados por mediana de preço de venda",
       x = NULL, y = "Mediana do preço de venda") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"),
        panel.grid.major.y = element_blank())

10 Categórica × Categórica

10.1 Intuição: há associação entre dois grupos?

Quando ambas as variáveis são categóricas, queremos saber se a distribuição de uma muda em função da outra — ou seja, se as categorias são independentes ou associadas.

As ferramentas são: tabela de contingência, gráfico de barras empilhadas e gráfico de mosaico.

10.2 Tabela de contingência

ames_clean |> 
  count(sale_condition) |> 
  arrange(desc(n))
# A tibble: 6 × 2
  sale_condition     n
  <chr>          <int>
1 Normal          2412
2 Partial          242
3 Abnorml          189
4 Family            46
5 Alloca            24
6 AdjLand           12

Por simplicidade, vamos usar as 3 categorias mais frequentes:

# sale_condition × bldg_type
tab <- ames_clean |>
  filter(sale_condition %in% c("Normal", "Abnorml", "Partial")) |>
  count(bldg_type, sale_condition) |>
  pivot_wider(names_from = sale_condition, values_from = n,
              values_fill = 0) |>
  column_to_rownames("bldg_type")

tab |>
  as.data.frame() |>
  rownames_to_column("Tipo de imóvel") |>
  kbl(caption = "Contagem: tipo de imóvel × condição de venda",
      align = "lrrr") |>
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE) |>
  add_header_above(c(" " = 1, "Condição de venda" = 3))
Contagem: tipo de imóvel × condição de venda
Condição de venda
Tipo de imóvel Abnorml Normal Partial
1Fam 157 2001 204
2fmCon 8 52 1
Duplex 10 78 0
Twnhs 6 93 1
TwnhsE 8 188 36
# Proporções por linha (dentro de cada tipo de imóvel)
prop.table(as.matrix(tab), margin = 2) |>
  round(3) |>
  as.data.frame() |>
  rownames_to_column("Tipo de imóvel") |>
  kbl(caption = "Proporção por linha: condição de venda dentro de cada tipo",
      align = "lrrr",
      digits = 3) |>
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE) |>
  add_header_above(c(" " = 1, "Condição de venda" = 3))
Proporção por linha: condição de venda dentro de cada tipo
Condição de venda
Tipo de imóvel Abnorml Normal Partial
1Fam 0.831 0.830 0.843
2fmCon 0.042 0.022 0.004
Duplex 0.053 0.032 0.000
Twnhs 0.032 0.039 0.004
TwnhsE 0.042 0.078 0.149
📋 Proporção por linha vs. por coluna
  • Proporção por linha (margin = 1): “dentro de cada tipo, qual a distribuição das condições de venda?” — útil quando X é a variável explicativa
  • Proporção por coluna (margin = 2): “dentro de cada condição, qual a distribuição dos tipos?” — útil quando Y é a variável explicativa
  • Proporção geral (margin = NULL): cada célula como % do total — útil para comparar com o esperado sob independência

10.3 Gráfico de barras empilhadas (proporcional)

10.3.1 Condição de venda vs. tipo de imóvel

ames_clean |>
  filter(sale_condition %in% c("Normal", "Abnorml", "Partial")) |>
  count(bldg_type, sale_condition) |>
  group_by(bldg_type) |>
  mutate(pct = n / sum(n)) |>
  ungroup() |>
  # Reordena bldg_type pela proporção de "Normal"
  mutate(bldg_type = fct_reorder(
    bldg_type, 
    pct * (sale_condition == "Normal"),  # só conta o valor de Normal
    .fun = sum
  )) |>
  ggplot(aes(x = bldg_type, y = pct, fill = sale_condition)) +
  geom_col(position = "fill", width = 0.65, alpha = 0.85) +
  geom_text(aes(label = percent(pct, accuracy = 1)),
            position = position_fill(vjust = 0.5),
            size = 3.2, color = "white", fontface = "bold") +
  scale_fill_manual(values = c("#185FA5", "#C04828", "#3B6D11"),
                    name = "Condição de venda") +
  scale_y_continuous(labels = percent_format()) +
  labs(title = "Condição de venda por tipo de imóvel",
       subtitle = "Barras proporcional a 100% — permite comparar a composição entre grupos",
       x = "Tipo de imóvel", y = "Proporção") +
  theme_minimal(base_size = 12) +
  theme(plot.title   = element_text(face = "bold"),
        legend.position = "top")

10.3.2 qualidade exterior × qualidade do porão

ames_clean |>
  filter(bsmt_qual != "None") |>
  mutate(
    exter_qual = factor(exter_qual,
                        levels = c("Po","Fa","TA","Gd","Ex"),
                        labels = c("Ruim","Regular","Médio","Bom","Excelente")),
    bsmt_qual  = factor(bsmt_qual,
                        levels = c("Po","Fa","TA","Gd","Ex"),
                        labels = c("Ruim","Regular","Médio","Bom","Excelente"))
  ) |> 
  count(bsmt_qual, exter_qual) |> 
  group_by(bsmt_qual) |>
  mutate(pct = n / sum(n)) |> 
  ungroup() |> 
  ggplot(aes(x = bsmt_qual, y = pct, fill = exter_qual)) +
  geom_col(position = "fill", width = 0.65, alpha = 0.85) +
  # geom_text(aes(label = percent(pct, accuracy = 1)),
  #           position = position_fill(vjust = 0.5),
            # size = 3.2, color = "white", fontface = "bold") +
  scale_fill_manual(values = c("#8f8d88", "#C04828", "#3B6D11", "#facf41", "#185FA5"),
                    name = "Condição Externa") +
  scale_y_continuous(labels = percent_format()) +
  labs(title = "Condição do porão por condição externa",
       x = "Qualidade do porão", y = "Proporção") +
  theme_minimal(base_size = 12) +
  theme(plot.title   = element_text(face = "bold"),
        legend.position = "top")

# Tabela de contingencia
ames_clean |>
  filter(bsmt_qual != "None") |>
  mutate(
    exter_qual = factor(exter_qual,
                        levels = c("Po","Fa","TA","Gd","Ex"),
                        labels = c("Ruim","Regular","Médio","Bom","Excelente")),
    bsmt_qual  = factor(bsmt_qual,
                        levels = c("Po","Fa","TA","Gd","Ex"),
                        labels = c("Ruim","Regular","Médio","Bom","Excelente"))
  ) |> 
  count(bsmt_qual, exter_qual) |>
  group_by(bsmt_qual) |>
  mutate(pct = round(100 * n / sum(n), 2)) |>
  ungroup() |> 
  select(-n) |> 
  pivot_wider(names_from = bsmt_qual, 
              values_from = pct, values_fill = 0) |> 
  rename(`Qualidade externa` = exter_qual) |> 
  kbl(caption = "Qual. do porão vs. qual. externa",
      align = "lrrrrr") |>
  kable_styling(bootstrap_options = c("striped", "hover"),
                full_width = FALSE) |>
  add_header_above(c(" " = 1, "Qualidade do porão" = 5))
Qual. do porão vs. qual. externa
Qualidade do porão
Qualidade externa Ruim Regular Médio Bom Excelente
Médio 100 81.82 91.97 37.41 6.72
Regular 0 7.95 1.64 0.16 0.00
Bom 0 10.23 6.08 61.53 58.50
Excelente 0 0.00 0.31 0.90 34.78
⚠️ Armadilha clássica n°3: confundir associação com causalidade em dados observacionais

Imóveis com qualidade exterior “Excelente” tendem a ter porão de qualidade “Bom” ou “Excelente” também — mas isso não quer dizer que reformar a fachada causa melhora no porão. Ambos refletem o mesmo fator latente: nível geral do imóvel (e do bairro onde está inserido).

11 Exercícios Práticos

11.1 📝 Exercício 1 — Correlação entre ano de construção e preço

Tarefa: Calcule a correlação de Pearson e Spearman entre year_built e sale_price. Em seguida, crie um scatter plot com linha de tendência. A relação é linear? Que tipo de correlação é mais adequado neste caso?

11.2 📝 Exercício 2 — Distribuição do preço por condição de venda

Tarefa: Crie um boxplot comparando a distribuição de sale_price entre as categorias de sale_condition. Ordene as categorias pela mediana. Qual condição apresenta maior variabilidade (maior IQR)?

11.3 📝 Exercício 3 — Tabela de contingência e visualização

Tarefa: Construa uma tabela de contingência entre house_style (estilo da casa) e exter_qual (qualidade exterior). Em seguida, crie um gráfico de barras empilhadas proporcional mostrando a distribuição de qualidade exterior dentro de cada estilo. Qual estilo tem maior proporção de imóveis com qualidade “Excelente”?