Este notebook apresenta uma jornada completa de análise espacial com foco em padrões socioeconômicos simulados para Porto Alegre (RS). A partir de uma grade hexagonal urbana, dados de frequência de eventos e renda são atribuídos de forma controlada e contextual, permitindo explorar relações geográficas entre variáveis.
Este notebook serve como instrumento didático e técnico para pesquisadores, estudantes, analistas urbanos e profissionais da área de geoinformação. A abordagem permite explorar efeitos espaciais com profundidade, simulando dados com propósito interpretativo, e oferecendo recursos para replicação em outros contextos territoriais.
library(dplyr)
library(gt)
library(sf)
library(spdep)
library(geobr)
library(ggplot2)
library(mapview)
library(RColorBrewer)
library(sfdep)
library(spatialreg)
library(biscale)
library(purrr)
library(GWmodel)
library(moments)
🧭 Resumo da Jornada Analítica Espacial | |||
📌 Etapa | 🎯 Objetivo | 🛠️ Técnica Utilizada | 💡 Insight Gerado |
---|---|---|---|
🧱 Construção da grade hexagonal | Representar o espaço urbano de Porto Alegre | st_make_grid() + st_intersects() | Base geográfica para modelagem espacial |
🎲 Simulação de dados com hotspots | Criar variáveis com padrões espacialmente realistas | Funções de distância + exp(-d/λ) | Hotspots e variação territorial simulada |
📈 Modelos SAR/SEM/SAC | Estimar efeito global da renda sobre frequência | lagsarlm(), errorsarlm(), spatialreg | Modelo global pode mascarar nuances locais |
📡 Regressão GWR | Estimar coeficiente local da renda em cada hexágono | bw.gwr() + gwr.basic() | GWR revela heterogeneidade espacial |
🔍 Visualização e interpretação | Destacar extremos e discrepâncias entre modelos | ggplot2, mapview, gt | Áreas onde SAR subestima o efeito da renda |
🧠 Implicações estatísticas | Avaliar impacto das variações espaciais na análise | Skewness, dispersão, comparação GWR − SAR | GWR detecta extremos que SAR suaviza |
poa <- read_municipality(code_muni = 4314902, year = 2020)
poa_utm <- st_transform(poa, crs = 31982)
hex_grid <- st_make_grid(poa_utm, cellsize = 1000, square = FALSE)
hex_sf <- st_sf(id = 1:length(hex_grid), geometry = hex_grid)
hex_sf <- hex_sf[st_intersects(hex_sf, poa_utm, sparse = FALSE), ]
coords_df <- data.frame(
id = 1:6,
lon = c(-51.2853, -51.1869, -51.1757, -51.1576, -51.2541, -51.1847),
lat = c(-30.0144, -30.0250, -30.0258, -30.0214, -30.1007, -30.0455)
)
pts_sf <- st_as_sf(coords_df, coords = c("lon","lat"), crs = 4326)
pts_sf <- st_transform(pts_sf, crs = 31982)
hex_centroid <- st_centroid(hex_sf)
dist_matrix <- st_distance(hex_centroid, pts_sf)
min_dist <- apply(dist_matrix, 1, min) # menor distância por hexágono
set.seed(123)
hex_sf$frequencia <- round(120 * exp(-min_dist / 1500)) +
sample(1:30, nrow(hex_sf), replace = TRUE)
ggplot(hex_sf) +
geom_sf(aes(fill = frequencia), color = "gray40") +
scale_fill_viridis_c(option = "C") +
labs(
title = "Frequência Simulada com Hotspots",
subtitle = "Valores mais altos próximos às coordenadas fornecidas",
fill = "Frequência"
) +
theme_minimal()
#st_crs(hex_sf)
hex_sf <- st_transform(hex_sf, crs = 4326)
summary(st_is_valid(hex_sf))
## Mode TRUE
## logical 678
#hex_sf <- st_make_valid(hex_sf)
names(hex_sf)[names(hex_sf) == "frequencia"] <- "frequencia"
mapview(hex_sf, zcol = "frequencia")
plot(hex_sf["frequencia"], main = "Hexágonos com frequencia")
nb <- poly2nb(as(hex_sf, "Spatial"), queen = TRUE)
listw <- nb2listw(nb, style = "W", zero.policy = TRUE)
moran.test(hex_sf$frequencia, listw, zero.policy = TRUE)
##
## Moran I test under randomisation
##
## data: hex_sf$frequencia
## weights: listw
## n reduced by no-neighbour observations
##
## Moran I statistic standard deviate = 29.138, p-value < 2.2e-16
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic Expectation Variance
## 0.6810788515 -0.0014792899 0.0005487168
lisa <- localmoran(hex_sf$frequencia, listw, zero.policy = TRUE)
hex_sf$lisa_I <- lisa[,1]
hex_sf$lisa_p <- lisa[,5]
media_freq <- mean(hex_sf$frequencia)
hex_sf$cluster <- ifelse(
hex_sf$frequencia > media_freq & hex_sf$lisa_I > 0, "Alta-Alta",
ifelse(hex_sf$frequencia < media_freq & hex_sf$lisa_I > 0, "Baixa-Baixa",
ifelse(hex_sf$frequencia > media_freq & hex_sf$lisa_I < 0, "Alta-Baixa",
ifelse(hex_sf$frequencia < media_freq & hex_sf$lisa_I < 0, "Baixa-Alta", "Não Significativo")))
)
ggplot(hex_sf) +
geom_sf(aes(fill = cluster), color = "gray30") +
scale_fill_brewer(palette = "Set1") +
theme_minimal() +
labs(title = "Mapa LISA com Hexágonos",
fill = "Cluster Espacial")
Mapa de hexágonos com:
frequencia de eventos
renda média da população
Permitindo investigar se áreas com alta frequencia de eventos estão
próximas de áreas com alta ou baixa renda.
Supõe duas variáveis no hex_sf: frequencia e renda
set.seed(123)
hex_centroid <- st_centroid(hex_sf)
pts_sf <- st_transform(pts_sf, st_crs(hex_centroid))
dist_matrix_renda <- st_distance(hex_centroid, pts_sf)
min_dist_renda <- apply(dist_matrix_renda, 1, min)
hex_sf$renda <- round(1500 * exp(-min_dist_renda / 1500)) +
sample(100:300, nrow(hex_sf), replace = TRUE)
hex_sf$renda = hex_sf$renda * hex_sf$frequencia
cor(hex_sf$renda, hex_sf$frequencia)
## [1] 0.8921704
Cria a matriz de pesos espaciais como antes
nb <- poly2nb(as(hex_sf, "Spatial"), queen = TRUE)
Verifica número de vizinhos por polígono
num_vizinhos <- card(nb)
Filtra apenas os hexágonos com pelo menos 1 vizinho
hex_sf <- hex_sf[num_vizinhos > 0, ]
nb <- subset(nb, num_vizinhos > 0)
listw <- nb2listw(nb, style = "W", zero.policy = TRUE)
Calcula o LISA bivariado: frequencia vs renda
# Usando sfdep::local_moran_bv()
lisa_biv <- local_moran_bv(
x = hex_sf$frequencia,
y = hex_sf$renda,
nb = nb,
wt = listw$weights,
nsim = 499
)
lisa_biv agora é um data.frame com $Ib e $p_sim
hex_sf$lisa_biv_I <- lisa_biv$Ib
hex_sf$lisa_biv_p <- lisa_biv$p_sim
Quando há suspeita de que a frequencia é influenciada por variáveis como renda, densidade populacional, etc., mas com dependência espacial, é melhor usar um modelo de regressão espacial.
📊 Tabela Comparativa de Modelos de Regressão Espacial | ||||
🔍 Modelo | 📐 Lag na Y | 📶 Erro Espacial | 🛠️ Função | 📘 Nome Técnico |
---|---|---|---|---|
SAR | ✅ | ❌ | lagsarlm() | Lag Espacial |
SEM | ❌ | ✅ | errorsarlm() | Erro Espacial |
SAC | ✅ | ✅ | sacsarlm() | Combinado (SARAR) |
(modelo_sar <- lagsarlm(frequencia ~ renda, data = hex_sf, listw = listw, zero.policy = TRUE)) |> summary()
##
## Call:lagsarlm(formula = frequencia ~ renda, data = hex_sf, listw = listw,
## zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -24.10216 -6.17984 0.49074 6.14984 19.77829
##
## Type: lag
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.2011e+01 7.4242e-01 16.178 < 2.2e-16
## renda 6.4499e-04 2.7728e-05 23.262 < 2.2e-16
##
## Rho: 0.22042, LR test value: 38.205, p-value: 6.3704e-10
## Asymptotic standard error: 0.037917
## z-value: 5.8132, p-value: 6.1275e-09
## Wald statistic: 33.794, p-value: 6.1275e-09
##
## Log likelihood: -2346.834 for lag model
## ML residual variance (sigma squared): 59.477, (sigma: 7.7121)
## Number of observations: 677
## Number of parameters estimated: 4
## AIC: 4701.7, (AIC for lm: 4737.9)
## LM test for residual autocorrelation
## test value: 0.013213, p-value: 0.90849
O modelo SEM considera que os erros da regressão estão autocorrelacionados espacialmente, ou seja, há dependência espacial nos resíduos.
summary(
modelo_sem <- errorsarlm(frequencia ~ renda, data = hex_sf, listw = listw, zero.policy = TRUE)
)
##
## Call:errorsarlm(formula = frequencia ~ renda, data = hex_sf, listw = listw,
## zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -31.72974 -6.10205 0.56253 6.12617 20.34556
##
## Type: error
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.6049e+01 4.6491e-01 34.520 < 2.2e-16
## renda 7.6492e-04 1.8932e-05 40.403 < 2.2e-16
##
## Lambda: 0.29846, LR test value: 29.174, p-value: 6.6144e-08
## Asymptotic standard error: 0.053936
## z-value: 5.5336, p-value: 3.1371e-08
## Wald statistic: 30.621, p-value: 3.1371e-08
##
## Log likelihood: -2351.349 for error model
## ML residual variance (sigma squared): 59.769, (sigma: 7.731)
## Number of observations: 677
## Number of parameters estimated: 4
## AIC: 4710.7, (AIC for lm: 4737.9)
O modelo SAC (também chamado SARAR) combina:
Lag espacial na variável dependente (efeito de vizinhos sobre o valor
observado)
Erro espacial autocorrelacionado (como no SEM)
summary(
modelo_sac <- sacsarlm(frequencia ~ renda, data = hex_sf, listw = listw, zero.policy = TRUE)
)
##
## Call:sacsarlm(formula = frequencia ~ renda, data = hex_sf, listw = listw,
## zero.policy = TRUE)
##
## Residuals:
## Min 1Q Median 3Q Max
## -23.54233 -6.22402 0.43718 6.13449 19.70200
##
## Type: sac
## Coefficients: (asymptotic standard errors)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.1790e+01 9.3980e-01 12.545 < 2.2e-16
## renda 6.3684e-04 3.4785e-05 18.308 < 2.2e-16
##
## Rho: 0.23318
## Asymptotic standard error: 0.050556
## z-value: 4.6122, p-value: 3.9842e-06
## Lambda: -0.025304
## Asymptotic standard error: 0.084459
## z-value: -0.2996, p-value: 0.76448
##
## LR test value: 38.239, p-value: 4.9717e-09
##
## Log likelihood: -2346.816 for sac model
## ML residual variance (sigma squared): 59.397, (sigma: 7.7069)
## Number of observations: 677
## Number of parameters estimated: 5
## AIC: 4703.6, (AIC for lm: 4737.9)
Transformamos frequencia e renda em classes (ex: baixa, média, alta)
usando quantis:
Classificar em 3 categorias usando quantis
summary(hex_sf)
## id geometry frequencia lisa_I
## Min. : 21.0 POLYGON :677 Min. : 1.00 Min. :-0.424589
## 1st Qu.: 415.0 epsg:4326 : 0 1st Qu.: 12.00 1st Qu.:-0.007362
## Median : 630.0 +proj=long...: 0 Median : 21.00 Median : 0.099809
## Mean : 621.5 Mean : 23.78 Mean : 0.682085
## 3rd Qu.: 820.0 3rd Qu.: 29.00 3rd Qu.: 0.323980
## Max. :1281.0 Max. :121.00 Max. :19.088614
## lisa_p cluster renda lisa_biv_I
## Min. :0.0000 Length:677 Min. : 103 Min. :-0.181536
## 1st Qu.:0.1663 Class :character 1st Qu.: 2688 1st Qu.:-0.004036
## Median :0.3833 Mode :character Median : 4484 Median : 0.097162
## Mean :0.4129 Mean : 10132 Mean : 0.706905
## 3rd Qu.:0.6377 3rd Qu.: 7830 3rd Qu.: 0.276650
## Max. :0.9985 Max. :177000 Max. :23.141492
## lisa_biv_p
## Min. :0.0000
## 1st Qu.:0.0420
## Median :0.1400
## Mean :0.1845
## 3rd Qu.:0.3160
## Max. :0.5000
quantile(hex_sf$renda)
## 0% 25% 50% 75% 100%
## 103 2688 4484 7830 177000
hex_sf <- hex_sf %>%
mutate(
freq_cat = ntile(frequencia, 3),
renda_cat = ntile(renda, 3),
bi_class = paste0(freq_cat, "-", renda_cat)
)
quantile(hex_sf$renda_cat)
## 0% 25% 50% 75% 100%
## 1 1 2 3 3
Utilizando o pacote biscale para aplicar uma paleta que mistura duas escalas:
# Classificar com biscale
hex_sf <- bi_class(hex_sf, x = frequencia, y = renda, style = "quantile", dim = 3)
ggplot(hex_sf) +
geom_sf(aes(fill = bi_class), color = "gray30") +
bi_scale_fill(pal = "DkBlue", dim = 3) +
labs(title = "Mapa Bivariado: frequencia vs Renda",
fill = "frequencia × Renda") +
theme_minimal()
mapView(hex_sf, zcol="bi_class")
📌 Interpretação dos Quadrantes Bivariados | ||
🔀 Quadrante | 🧭 Significado | 🔣 Ícone |
---|---|---|
1-1 | Baixa frequencia e baixa renda | ⬇️⬇️ |
1-3 | Baixa frequencia e alta renda (outlier) | ⬇️⬆️ |
3-1 | Alta frequencia e baixa renda (outlier) | ⬆️⬇️ |
3-3 | Alta frequencia e alta renda | ⬆️⬆️ |
significativos = hex_sf %>%
filter(lisa_p < 0.05) %>%
select(id, frequencia, renda, lisa_I, cluster, lisa_p)
significativos
## Simple feature collection with 80 features and 6 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: -51.27259 ymin: -30.21987 xmax: -51.08063 ymax: -29.99062
## Geodetic CRS: WGS 84
## First 10 features:
## id frequencia renda lisa_I cluster lisa_p
## 1 187 74 57646 9.4042464 Alta-Alta 8.161872e-09
## 2 209 49 37436 3.8722981 Alta-Alta 5.527719e-08
## 3 210 88 97768 14.4342524 Alta-Alta 3.719880e-12
## 4 215 36 20736 0.6070451 Alta-Alta 4.951020e-02
## 5 230 50 30050 1.9173439 Alta-Alta 9.583912e-03
## 6 231 109 126767 13.4904515 Alta-Alta 2.606603e-12
## 7 253 53 38107 3.6559479 Alta-Alta 5.454328e-08
## 8 254 98 123480 13.5153840 Alta-Alta 2.744103e-13
## 9 255 41 26117 1.4350265 Alta-Alta 1.077834e-02
## 10 274 40 20000 0.9429464 Alta-Alta 1.167916e-02
## geometry
## 1 POLYGON ((-51.2674 -30.1103...
## 2 POLYGON ((-51.26223 -30.118...
## 3 POLYGON ((-51.26219 -30.102...
## 4 POLYGON ((-51.26199 -30.024...
## 5 POLYGON ((-51.25706 -30.125...
## 6 POLYGON ((-51.25702 -30.110...
## 7 POLYGON ((-51.25185 -30.118...
## 8 POLYGON ((-51.25181 -30.102...
## 9 POLYGON ((-51.25178 -30.086...
## 10 POLYGON ((-51.24668 -30.125...
mapview(significativos, zcol="cluster")
significativos_bivariado = hex_sf %>%
filter(lisa_biv_p < 0.05) %>%
select(id, frequencia, renda, lisa_biv_I,lisa_biv_p, bi_class)
mapview(significativos_bivariado, zcol="bi_class")
🧭 Como Interpretar Cada Hexágono | ||
📦 Coluna | 🧬 Significado | 🔍 Interpretação |
---|---|---|
frequencia | Valor da variável principal (ex: número de eventos) | Quanto maior o valor, maior a concentração de eventos naquele hexágono |
renda | Valor da variável secundária (ex: renda média) | Usada para comparação cruzada com frequencia (ex: áreas ricas vs. pobres) |
lisa_I | Índice de Moran Local (autocorrelação univariada) | Se positivo e significativo, indica agrupamento com vizinhos similares |
lisa_p | Significância estatística da autocorrelação local (p < 0.05 é significativo) | Permite identificar clusters significativos ou padrões aleatórios |
cluster | Classificação do padrão espacial: Alta-Alta, Baixa-Baixa, outliers ou neutro | Mostra se o hexágono pertence a um cluster ou é um outlier espacial |
lisa_biv_I | Índice de autocorrelação espacial entre duas variáveis | Se positivo e significativo, há correlação local entre frequencia e renda |
lisa_biv_p | Significância do índice bivariado entre frequencia e renda | Se < 0.05, o padrão entre frequencia e renda é estatisticamente relevante |
Selecionar Hexágonos com Cluster Alta-Alta e P-Valor Significativo
(LISA uni e bivariado)
Filtro: cluster Alta-Alta com p < 0.05 tanto para lisa univariado
quanto bivariado
hex_aa_sig <- hex_sf %>%
filter(
cluster == "Alta-Alta",
lisa_p < 0.05,
lisa_biv_p < 0.05
)
Visualiza os hexágonos selecionados
mapview(hex_aa_sig, zcol = "frequencia")
Pegando os vizinhos do hexágono Alta-Alta
Pegar os índices das linhas (não os IDs) dos hexágonos Alta-Alta
linhas_alta <- which(hex_sf$id %in% hex_aa_sig$id)
Coletar todos vizinhos corretamente
vizinhos_idx <- linhas_alta %>%
map(~ nb[[.x]]) %>%
unlist() %>%
unique()
Remover os próprios hexágonos Alta-Alta
vizinhos_idx <- setdiff(vizinhos_idx, linhas_alta)
Extrair os vizinhos da camada
hex_vizinhos <- hex_sf[vizinhos_idx, ]
Vizinhos
mapview(hex_vizinhos, zcol = "frequencia")
Hot-spots com vizinhos
mapview(hex_vizinhos, zcol = "frequencia") +
mapview(hex_aa_sig, col.regions = "red")
SAR/SEM/SAC usam a estrutura de vizinhança (matriz W) para modelar o
efeito espacial.Ambos tentam incorporar a dimensão espacial na modelagem
estatística. GWR usa pesos geográficos (kernels) que variam conforme a
posição no espaço.
SAR/SEM/SAC são “modelos espaciais globais”, enquanto GWR é um “modelo
espacial local” — ideal para quando os efeitos variam
geograficamente.
🌐 Diferenças e Relações entre GWR e SAR/SEM/SAC | ||
📊 Característica | 📦 SAR / SEM / SAC | 🗺️ GWR |
---|---|---|
🔗 Tipo de Dependência Espacial | Global, via matriz de vizinhança (listw) | Local, via kernel de distância |
📐 Estimação dos Coeficientes | Coeficiente único por variável (modelo global) | Coeficientes que variam por localidade |
🧠 Complexidade Computacional | Moderada (paramétrico) | Alta (ajuste local em cada polígono) |
📦 Pacotes Comuns | `spdep`, `spatialreg` | `GWmodel`, `spgwr`, `mgwr` |
🎯 Tipo de Modelo | Modelos espaciais globais | Modelos espaciais locais |
🗺️ Quando Utilizar | Quando há padrão global de autocorrelação | Quando há heterogeneidade espacial dos efeitos |
#devtools::install_github("GWmodel-Lab/GWmodel3")
#library(GWmodel3)
hex_coords <- st_coordinates(st_centroid(hex_sf))
hex_df <- hex_sf %>%
st_drop_geometry() %>%
mutate(X = hex_coords[,1], Y = hex_coords[,2])
formula_gwr <- frequencia ~ renda
bw.gwr() primeiro encontra o tamanho ideal de vizinhança
bw <- bw.gwr(formula_gwr, data = hex_sf, adaptive = TRUE)
## Adaptive bandwidth: 426 CV score: 34329.31
## Adaptive bandwidth: 271 CV score: 29805.6
## Adaptive bandwidth: 175 CV score: 26737.94
## Adaptive bandwidth: 115 CV score: 24125.55
## Adaptive bandwidth: 79 CV score: 21650.34
## Adaptive bandwidth: 55 CV score: 19595.08
## Adaptive bandwidth: 42 CV score: 18736.77
## Adaptive bandwidth: 32 CV score: 18061.83
## Adaptive bandwidth: 28 CV score: 18042.92
## Adaptive bandwidth: 23 CV score: 18107.42
## Adaptive bandwidth: 28 CV score: 18042.92
Com esse bw, se alimenta a gwr.basic() para estimar os coeficientes locais
Analogia simples
bw.gwr() é como escolher o alcance do radar 🛰️ (quantos hexágonos em
volta cada ponto considera).
gwr.basic() é como fazer a leitura personalizada para cada local 🗺️
(calculando os efeitos da variável em cada região).
modelo_gwr <- gwr.basic(formula_gwr, data = hex_sf, bw = bw, adaptive = TRUE)
summary(modelo_gwr)
## Length Class Mode
## GW.arguments 11 -none- list
## GW.diagnostic 8 -none- list
## lm 14 lm list
## SDF 13 sf list
## timings 5 -none- list
## this.call 5 -none- call
## Ftests 0 -none- list
tabela_gwr_funcoes <- tibble::tibble(
Função = c("bw.gwr()", "gwr.basic()"),
Finalidade = c(
"Selecionar a largura de banda ótima (quantos vizinhos considerar)",
"Aplicar a regressão GWR com os dados e a largura de banda definida"
),
Entrada = c(
"Fórmula, dados, coordenadas; opções de kernel e adaptatividade",
"Fórmula, dados, coordenadas, bandwidth; opções de kernel"
),
Retorno = c(
"Valor numérico de bandwidth ideal",
"Objeto com coeficientes locais, estatísticas e predições"
),
Analogia = c(
"Ajusta o alcance do radar espacial 🛰️",
"Faz a leitura local personalizada em cada hexágono 📡"
)
)
# Monta a tabela com gt
tabela_gwr_funcoes %>%
gt() %>%
tab_header(
title = "🧠 Comparação entre `bw.gwr()` e `gwr.basic()` no GWmodel"
) %>%
cols_label(
Função = "🛠️ Função",
Finalidade = "🎯 Finalidade",
Entrada = "📥 Entradas",
Retorno = "📤 Retorno",
Analogia = "💡 Analogia"
)
🧠 Comparação entre `bw.gwr()` e `gwr.basic()` no GWmodel | ||||
🛠️ Função | 🎯 Finalidade | 📥 Entradas | 📤 Retorno | 💡 Analogia |
---|---|---|---|---|
bw.gwr() | Selecionar a largura de banda ótima (quantos vizinhos considerar) | Fórmula, dados, coordenadas; opções de kernel e adaptatividade | Valor numérico de bandwidth ideal | Ajusta o alcance do radar espacial 🛰️ |
gwr.basic() | Aplicar a regressão GWR com os dados e a largura de banda definida | Fórmula, dados, coordenadas, bandwidth; opções de kernel | Objeto com coeficientes locais, estatísticas e predições | Faz a leitura local personalizada em cada hexágono 📡 |
hex_sf$coef_renda <- modelo_gwr$SDF$renda
Valores positivos indicam que a renda está associada a maior
frequência de eventos naquele local.
Valores negativos indicam que a renda está associada a menor frequência
— ou seja, pode haver um padrão inverso.
ggplot(hex_sf) +
geom_sf(aes(fill = coef_renda), color = "gray30") +
scale_fill_viridis_c(option = "C") +
labs(title = "Coeficiente Local da Renda (GWR)",
fill = "Efeito da Renda") +
theme_minimal()
#coef_sar <- summary(modelo_sar)$coefficients["renda", "Estimate"]
#coef_sem <- summary(modelo_sem)$coefficients["renda", "Estimate"]
#coef_sac <- summary(modelo_sac)$coefficients["renda", "Estimate"]
coef_sar <- summary(modelo_sar)$coefficients["renda"]
coef_sem <- summary(modelo_sem)$coefficients["renda"]
coef_sac <- summary(modelo_sac)$coefficients["renda"]
Resumo dos coeficientes locais (mínimo, máximo, média)
📡 Coeficientes Locais da Renda (GWR) | |
Estatística | GWR |
---|---|
Min | 0.0004745822 |
Média | 0.002668102 |
Max | 0.005093496 |
25 maiores coeficientes
top25_gwr <- hex_sf %>%
arrange(desc(coef_renda)) %>%
slice_head(n = 25) %>%
st_drop_geometry() %>%
select(ID_top = id, Renda_top = coef_renda)
25 menores coeficientes
bottom25_gwr <- hex_sf %>%
arrange(coef_renda) %>%
slice_head(n = 25) %>%
st_drop_geometry() %>%
select(ID_bottom = id, Renda_bottom = coef_renda)
Combina as duas tabelas lado a lado
gwr_extremos <- bind_cols(top25_gwr, bottom25_gwr)
📊 Extremos dos Coeficientes Locais da Renda (GWR) | |||
🔺 Top 25 – ID | 🔺 Coef. Local Renda | 🔻 Bottom 25 – ID | 🔻 Coef. Local Renda |
---|---|---|---|
242 | 0.00509 | 544 | 0.00047 |
286 | 0.00504 | 567 | 0.00048 |
264 | 0.00503 | 588 | 0.00048 |
330 | 0.00496 | 566 | 0.00049 |
308 | 0.00492 | 611 | 0.00049 |
241 | 0.00492 | 543 | 0.00049 |
352 | 0.00487 | 632 | 0.00049 |
646 | 0.00481 | 522 | 0.00050 |
220 | 0.00481 | 655 | 0.00051 |
577 | 0.00480 | 610 | 0.00051 |
198 | 0.00479 | 523 | 0.00052 |
285 | 0.00478 | 676 | 0.00052 |
511 | 0.00476 | 587 | 0.00052 |
667 | 0.00475 | 500 | 0.00053 |
533 | 0.00473 | 589 | 0.00053 |
329 | 0.00471 | 654 | 0.00055 |
555 | 0.00467 | 565 | 0.00055 |
690 | 0.00466 | 499 | 0.00055 |
800 | 0.00463 | 699 | 0.00055 |
840 | 0.00461 | 521 | 0.00055 |
600 | 0.00455 | 545 | 0.00055 |
373 | 0.00452 | 633 | 0.00055 |
863 | 0.00451 | 478 | 0.00055 |
1281 | 0.00450 | 677 | 0.00056 |
1259 | 0.00450 | 298 | 0.00057 |
Quantis
quantile(hex_sf$coef_renda)
## 0% 25% 50% 75% 100%
## 0.0004745822 0.0015889106 0.0029120698 0.0036203441 0.0050934956
Se os coeficientes da GWR tiverem ampla variação (ex: de negativo a
positivo), isso revela heterogeneidade espacial — ou seja, o efeito da
renda não é uniforme em Porto Alegre.
Os modelos SAR/SEM/SAC, por serem globais, estimam um valor médio que
pode mascarar efeitos locais fortes ou inversos.
🗺️ Etapas para Visualização dos “Hotspots GWR”
1. Criar classificação baseada no coeficiente GWR
# hex_sf <- hex_sf %>%
# mutate(
# efeito_renda_gwr = case_when(
# coef_renda > quantile(coef_renda, 0.9, na.rm = TRUE) ~ "Efeito Forte Positivo",
# coef_renda < quantile(coef_renda, 0.1, na.rm = TRUE) ~ "Efeito Fraco Positivo",
# TRUE ~ "Efeito Moderado"
# )
# )
hex_sf <- hex_sf %>%
mutate(
efeito_renda_gwr = case_when(
coef_renda > quantile(coef_renda, 0.85, na.rm = TRUE) ~ "Efeito Forte Positivo",
coef_renda < quantile(coef_renda, 0.15, na.rm = TRUE) ~ "Efeito Fraco Positivo",
TRUE ~ "Efeito Moderado"
)
)
ggplot(hex_sf) +
geom_sf(aes(fill = efeito_renda_gwr), color = "gray50") +
scale_fill_manual(values = c(
"Efeito Forte Positivo" = "#2166AC", # azul escuro
"Efeito Fraco Positivo" = "#B2182B", # vermelho escuro
"Efeito Moderado" = "gray80"
)) +
labs(
title = "Mapa GWR: Efeito Local da Renda sobre Frequência",
fill = "Força do Efeito"
) +
theme_minimal()
mapView(hex_sf, zcol="efeito_renda_gwr")
Porque a associação estatística entre renda e frequência depende da
distribuição local, e o GWR capta como a variação da renda afeta a
variação da frequência.
Exemplo hipotético:
- Um hexágono tem renda alta e frequência alta → entra no quadrante
3-3.
- Mas nas redondezas, hexágonos vizinhos com renda ainda mais alta não
apresentam aumentos proporcionais na frequência.
- Nesse cenário, a GWR pode detectar um coeficiente negativo, ou seja:
em áreas com mais renda, a frequência tende a cair.
Isso não contradiz os valores altos absolutos, mas indica um padrão
inverso na associação local.
hex_sf$coef_renda <- modelo_gwr$SDF$renda
# Gera a variável de sinal (positivo, negativo, nulo)
hex_sf <- hex_sf %>%
mutate(
sinal_gwr = case_when(
coef_renda > 0 ~ "positivo",
coef_renda < 0 ~ "negativo",
TRUE ~ "nulo"
),
classe_cruzada = paste0(bi_class, "_", sinal_gwr)
)
# Conta combinações
tabela_cruzada <- hex_sf %>%
count(bi_class, sinal_gwr) %>%
rename(Classe_Bivariada = bi_class, Sinal_GWR = sinal_gwr, Hexágonos = n)
tabela_cruzada$geometry = NULL
# Exibe com gt
tabela_cruzada[,1:3] %>%
gt() %>%
tab_header(title = "📌 Cruzamento entre Classe Bivariada e Sinal do Efeito da Renda (GWR)") %>%
cols_label(
Classe_Bivariada = "🔀 Classe Bivariada (frequência × renda)",
Sinal_GWR = "📈 Sinal do Coef. GWR",
Hexágonos = "🔢 Nº de Hexágonos"
) %>%
fmt_number(columns = Hexágonos, decimals = 0)
📌 Cruzamento entre Classe Bivariada e Sinal do Efeito da Renda (GWR) | ||
🔀 Classe Bivariada (frequência × renda) | 📈 Sinal do Coef. GWR | 🔢 Nº de Hexágonos |
---|---|---|
1-1 | positivo | 196 |
1-2 | positivo | 44 |
2-1 | positivo | 29 |
2-2 | positivo | 144 |
2-3 | positivo | 46 |
3-1 | positivo | 1 |
3-2 | positivo | 37 |
3-3 | positivo | 180 |
Criar uma nova variável que combine o quadrante bivariado com o sinal do coeficiente GWR. Assim fica fácil ver onde existe “associação alta-alta com efeito inverso”.
hex_sf %>%
filter(bi_class == "3-3") %>%
select(id, frequencia, renda, coef_renda) %>%
arrange(coef_renda)
## Simple feature collection with 180 features and 4 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: -51.29837 ymin: -30.25121 xmax: -51.04428 ymax: -29.95918
## Geodetic CRS: WGS 84
## First 10 features:
## id frequencia renda coef_renda geometry
## 1 544 121 172183 0.0004745822 POLYGON ((-51.18422 -30.032...
## 2 567 100 100700 0.0004793492 POLYGON ((-51.17902 -30.024...
## 3 588 106 150626 0.0004815490 POLYGON ((-51.17385 -30.032...
## 4 566 76 74404 0.0004881768 POLYGON ((-51.17905 -30.040...
## 5 611 69 66585 0.0004929488 POLYGON ((-51.16865 -30.024...
## 6 543 120 177000 0.0004933899 POLYGON ((-51.18425 -30.047...
## 7 632 85 86445 0.0004938524 POLYGON ((-51.16348 -30.032...
## 8 522 68 66096 0.0004993409 POLYGON ((-51.18942 -30.040...
## 9 655 118 171926 0.0005084106 POLYGON ((-51.15828 -30.024...
## 10 610 59 50563 0.0005092856 POLYGON ((-51.16868 -30.040...
Criar uma nova variável que combine o quadrante bivariado com o sinal do coeficiente GWR. Assim fica fácil ver onde existe “alta-alta com efeito inverso”:
hex_sf <- hex_sf %>%
mutate(
sinal_gwr = case_when(
coef_renda > 0 ~ "positivo",
coef_renda < 0 ~ "negativo",
TRUE ~ "nulo"
),
classe_cruzada = paste0(bi_class, "_", sinal_gwr)
)
ggplot(hex_sf) +
geom_sf(aes(fill = classe_cruzada), color = "gray30") +
scale_fill_manual(
values = c(
"3-3_negativo" = "#b2182b",
"3-3_positivo" = "#2166ac",
"1-1_positivo" = "#67a9cf",
"1-1_negativo" = "#ef8a62"
# adicione outras combinações se quiser
)
) +
labs(
title = "🌐 Mapa de Conflito: Onde a Associação é paralela e o Efeito é forte",
fill = "Classe Cruzada"
) +
theme_minimal()
mapView(hex_sf, zcol="classe_cruzada")
ggplot(hex_sf, aes(x = coef_renda)) +
geom_histogram(bins = 30, fill = "#67a9cf", color = "white") +
geom_vline(xintercept = coef_sar, linetype = "dashed", color = "red", size = 1.2) +
labs(
title = "Distribuição dos Coeficientes Locais (GWR)",
x = "Coeficiente GWR da Renda",
y = "Número de Hexágonos"
) +
theme_minimal()
A linha vermelha mostra o valor global estimado pelo SAR.
Se a distribuição GWR for bem dispersa em torno dele, significa que o
modelo global “apaga” nuances locais!
O que significa a linha vermelha deslocada para a esquerda? A linha
vermelha representa o coeficiente global da renda estimado via SAR. Ela
estando mais à esquerda sugere que o modelo SAR estimou um efeito da
renda mais fraco ou menos positivo, comparado à maioria dos coeficientes
locais.
Isso pode indicar que:
Há zonas com efeito positivo intenso da renda, que puxam a cauda direita
da distribuição GWR.
O efeito da renda não é uniforme — algumas áreas possuem associação
muito maior entre renda e frequência.
O que significa a cauda longa à direita na distribuição GWR?
Uma cauda direita mais pronunciada sugere que existem hexágonos onde o
coeficiente local de renda é especialmente elevado, ou seja, a renda
está # muito fortemente associada à frequência naquele local.
Isso pode estar ligado a zonas de alta renda com densidade alta de
eventos — ou padrões sociais/geográficos que amplificam o impacto da
renda ali.
hex_sf %>%
filter(coef_renda > quantile(coef_renda, 0.95, na.rm = TRUE))
## Simple feature collection with 34 features and 15 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: -51.27212 ymin: -30.21994 xmax: -51.00272 ymax: -29.92794
## Geodetic CRS: WGS 84
## First 10 features:
## id geometry frequencia lisa_I lisa_p
## 1 198 POLYGON ((-51.26694 -29.938... 11 0.70168933 0.09238787
## 2 220 POLYGON ((-51.26178 -29.946... 10 0.52802168 0.09577714
## 3 241 POLYGON ((-51.25662 -29.954... 26 -0.07613455 0.13996313
## 4 242 POLYGON ((-51.25658 -29.938... 2 0.80573464 0.18922025
## 5 264 POLYGON ((-51.25142 -29.946... 6 0.46253032 0.25736731
## 6 285 POLYGON ((-51.24626 -29.954... 9 0.28949091 0.39364851
## 7 286 POLYGON ((-51.24622 -29.938... 22 0.06819416 0.17230032
## 8 307 POLYGON ((-51.2411 -29.9618... 20 0.12022807 0.16625800
## 9 308 POLYGON ((-51.24106 -29.946... 25 -0.05198395 0.06809727
## 10 329 POLYGON ((-51.23589 -29.954... 3 0.66302039 0.16477123
## cluster renda lisa_biv_I lisa_biv_p freq_cat renda_cat bi_class
## 1 Baixa-Baixa 1199 0.30087518 0.026 1 1 1-1
## 2 Baixa-Baixa 2590 0.29701948 0.012 1 1 1-1
## 3 Alta-Baixa 5148 -0.04720336 0.012 2 2 2-2
## 4 Baixa-Baixa 510 0.48262458 0.020 1 1 1-1
## 5 Baixa-Baixa 1818 0.35547729 0.036 1 1 1-1
## 6 Baixa-Baixa 2322 0.28217087 0.060 1 1 1-1
## 7 Baixa-Baixa 3366 0.03857014 0.036 2 2 2-2
## 8 Baixa-Baixa 5320 0.07983730 0.022 2 2 2-2
## 9 Alta-Baixa 4150 -0.02681766 0.010 2 2 2-2
## 10 Baixa-Baixa 534 0.40774225 0.048 1 1 1-1
## coef_renda efeito_renda_gwr sinal_gwr classe_cruzada
## 1 0.004788730 Efeito Forte Positivo positivo 1-1_positivo
## 2 0.004807184 Efeito Forte Positivo positivo 1-1_positivo
## 3 0.004921306 Efeito Forte Positivo positivo 2-2_positivo
## 4 0.005093496 Efeito Forte Positivo positivo 1-1_positivo
## 5 0.005028641 Efeito Forte Positivo positivo 1-1_positivo
## 6 0.004778180 Efeito Forte Positivo positivo 1-1_positivo
## 7 0.005038281 Efeito Forte Positivo positivo 2-2_positivo
## 8 0.004438433 Efeito Forte Positivo positivo 2-2_positivo
## 9 0.004923139 Efeito Forte Positivo positivo 2-2_positivo
## 10 0.004706058 Efeito Forte Positivo positivo 1-1_positivo
hex_sf <- hex_sf %>%
mutate(dif_coef = coef_renda - coef_sar)
dif_coef para ver em quais regiões o SAR subestimou ou superestimou o efeito:
ggplot(hex_sf) +
geom_sf(aes(fill = dif_coef), color = "gray20") +
scale_fill_gradient2(
low = "#2166AC", mid = "white", high = "#B2182B",
midpoint = 0,
name = "Diferença GWR − SAR"
) +
labs(
title = "🗺️ Diferença entre Coeficientes Locais (GWR) e Global (SAR)",
subtitle = "Regiões onde GWR supera SAR indicam subestimação global",
caption = paste("Valor global SAR:", round(coef_sar, 5))
) +
theme_minimal()
Essas regiões podem ser prioritárias em estudos mais refinados, pois mostram onde os modelos globais “apagam” nuances locais importantes.
🔍 Top 10 Hexágonos onde GWR supera SAR | ||
ID | Renda_GWR | dif_coef |
---|---|---|
242 | 0.00509 | 0.00445 |
286 | 0.00504 | 0.00439 |
264 | 0.00503 | 0.00438 |
330 | 0.00496 | 0.00431 |
308 | 0.00492 | 0.00428 |
241 | 0.00492 | 0.00428 |
352 | 0.00487 | 0.00422 |
646 | 0.00481 | 0.00417 |
220 | 0.00481 | 0.00416 |
577 | 0.00480 | 0.00415 |
Valores de skewness > 0 confirmam a cauda longa à direita. Isso reforça que o SAR não captura bem os extremos.
skewness(hex_sf$coef_renda, na.rm = TRUE)
## [1] -0.300166
📊 Implicações Estatísticas e Espaciais da Distribuição GWR vs SAR | ||
🔍 Observação | 📐 Implicação Estatística | 🗺️ Significado Espacial |
---|---|---|
Linha vermelha deslocada à esquerda do pico GWR | SAR subestima o efeito positivo da renda em boa parte da cidade | Hotspots locais são mais intensos do que o SAR detecta |
Distribuição GWR com cauda longa à direita | Existem locais com forte associação positiva entre renda e frequência | Alguns bairros têm padrões muito distintos dos demais |
Grande dispersão nos coeficientes GWR | Alto desvio padrão indica variação espacial real | Recomenda-se análise local (GWR) para capturar os extremos |
Coeficientes locais positivos e negativos | Sugere presença de heterogeneidade espacial | Políticas públicas podem precisar de abordagens regionalizadas |
SAR retorna valor médio único | Modelo global ignora variações locais importantes | SAR é útil para visão geral, mas GWR mostra nuance |