# Instala solo los paquetes que falten (igual que en la actividad anterior) y luego los carga
paquetes <- c("tibble", "ggplot2", "class", "caret", "spdep", "rgeoda", "foreign",
"factoextra", "dendextend", "ClustGeo", "sp", "dplyr", "plotly",
"tigris", "tmap", "knitr", "sf", "tidyr")
faltan <- paquetes[!paquetes %in% rownames(installed.packages())]
if (length(faltan) > 0) install.packages(faltan, repos = "https://cloud.r-project.org")
library(tibble)
library(ggplot2)
library(class)
library(caret)
library(spdep)
library(rgeoda)
library(foreign)
library(factoextra)
library(dendextend)
library(ClustGeo)
library(sp)
library(dplyr)
library(plotly)
library(tigris)
library(tmap)
library(knitr)
# Adicionales
library(sf) # lectura y manejo de geometrías
library(tidyr) # pivot_longer para el EDA
Los archivos state_data(in).csv,
mexlatlong.shp (y sus archivos acompañantes
.dbf, .shx, .prj,
.cpg) deben estar en la carpeta indicada en
ruta. Cambien ruta a la carpeta donde
los tengan (debe terminar en /). Si el CSV tiene
otro nombre, cambien archivo_csv.
sf_use_s2(FALSE) # centroides y vecindades con geometría plana (evita errores de s2)
# Carpeta donde están el CSV y los archivos mexlatlong.*
ruta <- "C:/TEC 8 semestre/"
archivo_csv <- "state_data(in).csv"
# Verificación: debe listar al menos .shp, .shx y .dbf
list.files(ruta, pattern = "mexlatlong")
## [1] "mexlatlong.cpg" "mexlatlong.dbf" "mexlatlong.prj"
## [4] "mexlatlong.sbn" "mexlatlong.sbx" "mexlatlong.shp"
## [7] "mexlatlong.shx" "mexlatlong_filesss" "mexlatlong_filesss.zip"
mx_state <- read.csv(paste0(ruta, archivo_csv))
mx_state_map <- st_read(paste0(ruta, "mexlatlong.shp"))
## Reading layer `mexlatlong' from data source `C:\TEC 8 semestre\mexlatlong.shp' using driver `ESRI Shapefile'
## Simple feature collection with 32 features and 19 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -118.4042 ymin: 14.55055 xmax: -86.73862 ymax: 32.71846
## Geodetic CRS: WGS 84
head(mx_state)
## state year state_id crime_rate unemployment employment
## 1 Aguascalientes 2021 1057 6.75 0.04 0.97
## 2 Baja California 2021 2304 84.67 0.01 0.98
## 3 Baja California Sur 2021 2327 8.52 0.03 0.97
## 4 Campeche 2021 1086 9.22 0.02 0.98
## 5 Chiapas 2021 1182 10.01 0.05 0.97
## 6 Chihuahua 2021 888 71.98 0.04 0.97
## business_activity real_wage real_ave_month_income pop_density lq_primary
## 1 -1.90 361.02 5641.67 261.21 0.16
## 2 2.47 388.22 7599.62 53.19 0.47
## 3 -2.12 345.57 8660.90 11.27 0.73
## 4 -2.44 414.48 5357.29 16.42 0.73
## 5 -2.41 312.37 6581.28 77.16 1.56
## 6 -1.25 362.93 5708.75 15.30 0.64
## lq_secondary lq_tertiary new_fdi_real_mxn log_new_fdi_real_mxn region_a
## 1 1.24 1.00 1270.03 3.10 Bajio
## 2 1.62 0.86 20407.81 4.31 Norte
## 3 0.51 1.13 19022.55 4.28 Occidente
## 4 0.78 1.06 2390.64 3.38 Sur
## 5 0.88 0.99 189.58 2.28 Sur
## 6 1.97 0.80 7153.63 3.86 Norte
## region_b
## 1 2
## 2 3
## 3 4
## 4 5
## 5 5
## 6 3
glimpse(mx_state)
## Rows: 32
## Columns: 17
## $ state <chr> "Aguascalientes", "Baja California", "Baja Calif…
## $ year <int> 2021, 2021, 2021, 2021, 2021, 2021, 2021, 2021, …
## $ state_id <int> 1057, 2304, 2327, 1086, 1182, 888, 1114, 933, 11…
## $ crime_rate <dbl> 6.75, 84.67, 8.52, 9.22, 10.01, 71.98, 11.58, 4.…
## $ unemployment <dbl> 0.04, 0.01, 0.03, 0.02, 0.05, 0.04, 0.06, 0.04, …
## $ employment <dbl> 0.97, 0.98, 0.97, 0.98, 0.97, 0.97, 0.94, 0.94, …
## $ business_activity <dbl> -1.90, 2.47, -2.12, -2.44, -2.41, -1.25, -2.08, …
## $ real_wage <dbl> 361.02, 388.22, 345.57, 414.48, 312.37, 362.93, …
## $ real_ave_month_income <dbl> 5641.67, 7599.62, 8660.90, 5357.29, 6581.28, 570…
## $ pop_density <dbl> 261.21, 53.19, 11.27, 16.42, 77.16, 15.30, 6195.…
## $ lq_primary <dbl> 0.16, 0.47, 0.73, 0.73, 1.56, 0.64, 0.03, 0.17, …
## $ lq_secondary <dbl> 1.24, 1.62, 0.51, 0.78, 0.88, 1.97, 0.58, 1.55, …
## $ lq_tertiary <dbl> 1.00, 0.86, 1.13, 1.06, 0.99, 0.80, 1.14, 0.93, …
## $ new_fdi_real_mxn <dbl> 1270.03, 20407.81, 19022.55, 2390.64, 189.58, 71…
## $ log_new_fdi_real_mxn <dbl> 3.10, 4.31, 4.28, 3.38, 2.28, 3.86, 4.54, 3.80, …
## $ region_a <chr> "Bajio", "Norte", "Occidente", "Sur", "Sur", "No…
## $ region_b <int> 2, 3, 4, 5, 5, 3, 1, 3, 4, 4, 2, 5, 1, 4, 1, 4, …
glimpse(mx_state_map)
## Rows: 32
## Columns: 20
## $ OBJECTID <dbl> 888, 933, 976, 978, 998, 1004, 1026, 1034, 1051, 1057, 1058…
## $ FIPS_ADMIN <chr> "MX06", "MX07", "MX19", "MX28", "MX25", "MX10", "MX32", "MX…
## $ GMI_ADMIN <chr> "MEX-CHH", "MEX-CDZ", "MEX-NLE", "MEX-TML", "MEX-SIN", "MEX…
## $ ADMIN_NAME <chr> "Chihuahua", "Coahuila", "Nuevo Leon", "Tamaulipas", "Sinal…
## $ FIPS_CNTRY <chr> "MX", "MX", "MX", "MX", "MX", "MX", "MX", "MX", "MX", "MX",…
## $ GMI_CNTRY <chr> "MEX", "MEX", "MEX", "MEX", "MEX", "MEX", "MEX", "MEX", "ME…
## $ CNTRY_NAME <chr> "Mexico", "Mexico", "Mexico", "Mexico", "Mexico", "Mexico",…
## $ POP_ADMIN <dbl> 2656214, 2145539, 3370912, 2272724, 2397706, 1467826, 13836…
## $ TYPE_ENG <chr> "State", "State", "State", "State", "State", "State", "Stat…
## $ TYPE_LOC <chr> "Estado", "Estado", "Estado", "Estado", "Estado", "Estado",…
## $ SQKM <dbl> 247935.02, 150843.95, 65173.05, 79502.24, 57638.85, 120674.…
## $ SQMI <dbl> 95727.70, 58240.87, 25163.31, 30695.81, 22254.36, 46592.46,…
## $ COLOR_MAP <chr> "12", "2", "3", "11", "5", "4", "7", "1", "8", "4", "9", "1…
## $ Shape_Leng <dbl> 22.609277, 18.993090, 15.426171, 18.023144, 16.466051, 17.5…
## $ Shape_Area <dbl> 22.8909849, 13.7336548, 5.8446681, 7.0565626, 5.1455245, 10…
## $ OBJECTID_1 <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, …
## $ OBJECTID_2 <dbl> 888, 933, 976, 978, 998, 1004, 1026, 1034, 1051, 1057, 1058…
## $ longitude <dbl> -106.44431, -102.03356, -99.83125, -98.62181, -107.48280, -…
## $ latitude <dbl> 28.80303, 27.30662, 25.60105, 24.29819, 25.02179, 24.93519,…
## $ geometry <MULTIPOLYGON [°]> MULTIPOLYGON (((-103.6309 2..., MULTIPOLYGON (…
class(mx_state_map)
## [1] "sf" "data.frame"
Unión de la tabla con las geocercas (misma llave que en clase:
OBJECTID y state_id):
state_geodata <- geo_join(mx_state_map, mx_state, "OBJECTID", "state_id", how = "inner")
nrow(state_geodata) # debe ser 32
## [1] 32
sum(is.na(state_geodata$employment)) # debe ser 0
## [1] 0
# Versión SPDF (como en clase); el análisis usa el objeto sf, así que no es indispensable
state_geodataSPDF <- tryCatch(as(state_geodata, "Spatial"), error = function(e) NULL)
Todo el análisis se hace sobre el objeto unido
(state_geodata) para garantizar que el orden de los estados
sea el mismo en los datos, en la matriz de adyacencias
y en los mapas.
datos <- st_drop_geometry(state_geodata)
# Variables asignadas al Equipo 1
vars_eq1 <- c("crime_rate", "employment", "real_wage", "lq_secondary")
stopifnot(all(vars_eq1 %in% names(datos))) # si falla, revisa nombres duplicados tras el join
col_estado <- intersect(c("state", "state.y", "state.x"), names(datos))[1]
nombres <- datos[[col_estado]]
X <- datos[, vars_eq1]
rownames(X) <- nombres
nrow(X) # deben ser 32 estados
## [1] 32
kable(summary(X), caption = "Resumen estadístico de las variables del Equipo 1")
| crime_rate | employment | real_wage | lq_secondary | |
|---|---|---|---|---|
| Min. : 2.330 | Min. :0.9400 | Min. :274.9 | Min. :0.3700 | |
| 1st Qu.: 9.832 | 1st Qu.:0.9600 | 1st Qu.:307.4 | 1st Qu.:0.6475 | |
| Median : 18.210 | Median :0.9700 | Median :332.8 | Median :1.0350 | |
| Mean : 30.135 | Mean :0.9684 | Mean :340.8 | Mean :1.0097 | |
| 3rd Qu.: 43.788 | 3rd Qu.:0.9725 | 3rd Qu.:363.0 | 3rd Qu.:1.2500 | |
| Max. :108.260 | Max. :0.9900 | Max. :466.8 | Max. :1.9700 |
X_long <- X %>% pivot_longer(everything(), names_to = "variable", values_to = "valor")
ggplot(X_long, aes(valor)) +
geom_histogram(bins = 12, fill = "#2C7FB8", color = "white") +
facet_wrap(~variable, scales = "free") +
labs(title = "Histogramas de las variables socioeconómicas", x = NULL, y = "Frecuencia") +
theme_minimal()
ggplot(X_long, aes(x = variable, y = valor, fill = variable)) +
geom_boxplot(show.legend = FALSE) +
facet_wrap(~variable, scales = "free") +
labs(title = "Boxplots de las variables socioeconómicas", x = NULL, y = NULL) +
theme_minimal()
cm <- cor(X)
cm_long <- as.data.frame(as.table(cm))
ggplot(cm_long, aes(Var1, Var2, fill = Freq)) +
geom_tile(color = "white") +
geom_text(aes(label = round(Freq, 2))) +
scale_fill_gradient2(low = "#B2182B", mid = "white", high = "#2166AC", limits = c(-1, 1)) +
labs(title = "Matriz de correlaciones", x = NULL, y = NULL, fill = "r") +
theme_minimal()
Observaciones del EDA (completar al ver las
gráficas): escalas muy distintas entre variables
(real_wage en cientos vs. employment cercano a
1), por lo que es indispensable estandarizar antes de
calcular distancias; además hay valores atípicos (p. ej.
crime_rate muy alto en algunos estados).
Xs <- scale(X) # estandarización (media 0, desv. est. 1)
D0 <- dist(Xs, method = "euclidean") # matriz de disimilitudes socioeconómicas
hc0 <- hclust(D0, method = "complete") # Método de clusterización COMPLETO (Equipo 1)
k1 <- 4
cl0 <- cutree(hc0, k = k1)
table(cl0)
## cl0
## 1 2 3 4
## 2 3 26 1
fviz_dend(hc0, k = k1, cex = 0.7, k_colors = "jco",
color_labels_by_k = TRUE, rect = TRUE, rect_fill = TRUE, rect_border = "jco",
main = "Dendrograma socioeconómico (enlace completo, k = 4)",
ylab = "Distancia")
paleta <- c("1" = "#E41A1C", "2" = "#377EB8", "3" = "#4DAF4A",
"4" = "#984EA3", "5" = "#FF7F00", "6" = "#A65628")
mapa_cl <- function(cl, titulo) {
mapa <- state_geodata
mapa$cluster <- factor(cl)
ggplot(mapa) +
geom_sf(aes(fill = cluster), color = "white", linewidth = 0.3) +
scale_fill_manual(values = paleta, name = "Clúster") +
labs(title = titulo) +
theme_minimal()
}
mapa_cl(cl0, "Conglomerados socioeconómicos (enlace completo, k = 4)")
kable(aggregate(X, by = list(Clúster = cl0), FUN = mean) %>% mutate(across(where(is.numeric), ~ round(.x, 2))),
caption = "Perfil promedio (unidades originales) por clúster — solo socioeconómico, k = 4")
| Clúster | crime_rate | employment | real_wage | lq_secondary |
|---|---|---|---|---|
| 1 | 78.33 | 0.98 | 375.58 | 1.79 |
| 2 | 8.66 | 0.95 | 357.44 | 1.48 |
| 3 | 29.62 | 0.97 | 331.36 | 0.91 |
| 4 | 11.58 | 0.94 | 466.80 | 0.58 |
centroides <- st_centroid(st_geometry(state_geodata))
coords <- st_coordinates(centroides)
rownames(coords) <- nombres
knn4 <- knearneigh(coords, k = 4)
nb_knn4 <- knn2nb(knn4)
plot(st_geometry(state_geodata), border = "grey60", main = "4 vecinos más cercanos por estado (kNN, k = 4)")
plot(nb_knn4, coords, add = TRUE, col = "firebrick", lwd = 1.2, points = FALSE, arrows = TRUE, length = 0.05)
points(coords, pch = 19, cex = 0.7)
text(coords, labels = nombres, cex = 0.45, pos = 3)
Se define la adyacencia con vecindad tipo queen (estados que
comparten frontera). La matriz de adyacencias A es binaria;
la disimilitud geográfica es D1 = 1 - A (0 si son vecinos o
el mismo estado, 1 si no lo son).
nb_queen <- poly2nb(state_geodata, queen = TRUE)
summary(nb_queen)
## Neighbour list object:
## Number of regions: 32
## Number of nonzero links: 138
## Percentage nonzero weights: 13.47656
## Average number of links: 4.3125
## Link number distribution:
##
## 1 2 3 4 5 6 7 8 9
## 1 6 6 6 5 2 3 2 1
## 1 least connected region:
## 31 with 1 link
## 1 most connected region:
## 8 with 9 links
# Estados sin vecinos (islas), si los hubiera
nombres[card(nb_queen) == 0]
## character(0)
A <- nb2mat(nb_queen, style = "B", zero.policy = TRUE)
diag(A) <- 1
dimnames(A) <- list(nombres, nombres)
D1 <- as.dist(1 - A)
plot(st_geometry(state_geodata), border = "grey60", main = "Red de adyacencias (queen)")
plot(nb_queen, coords, add = TRUE, col = "steelblue", lwd = 1.5)
ClustGeo trabaja con D = (1 - α)·D0 + α·D1 usando Ward.
Como la asignación del Equipo 1 es enlace completo, se
aplica hclust(method = "complete") sobre la matriz
mezclada. Para que α sea interpretable, D0 y D1 se normalizan
dividiéndolas entre su máximo. Para cada α ∈ {0, 0.02, …, 1} se calcula
la proporción de inercia explicada (pseudo-inercia) respecto a D0
(Q0, homogeneidad socioeconómica) y a D1
(Q1, cohesión geográfica), ambas normalizadas. El α
óptimo es el punto donde ambas curvas se cruzan (equilibrio entre
ambas).
D0n <- as.dist(as.matrix(D0) / max(D0))
D1n <- as.dist(as.matrix(D1) / max(D1))
alphas <- seq(0, 1, by = 0.02)
# Proporción de inercia explicada de una partición con respecto a una matriz de distancias
Q_inercia <- function(D, part) {
Dm <- as.matrix(D)^2
n <- nrow(Dm)
Tt <- sum(Dm) / (2 * n)
W <- sum(sapply(unique(part), function(g) {
idx <- which(part == g)
sum(Dm[idx, idx]) / (2 * length(idx))
}))
1 - W / Tt
}
D_mezcla <- function(a) as.dist((1 - a) * as.matrix(D0n) + a * as.matrix(D1n))
analisis_alpha <- function(K) {
res <- lapply(alphas, function(a) {
part <- cutree(hclust(D_mezcla(a), method = "complete"), k = K)
c(alpha = a, Q0 = Q_inercia(D0n, part), Q1 = Q_inercia(D1n, part))
})
res <- as.data.frame(do.call(rbind, res))
res$Q0norm <- res$Q0 / res$Q0[1]
res$Q1norm <- res$Q1 / res$Q1[nrow(res)]
res
}
alpha_optimo <- function(res) res$alpha[which.min(abs(res$Q0norm - res$Q1norm))]
grafica_alpha <- function(res, K, a_opt) {
res %>%
select(alpha, Q0norm, Q1norm) %>%
pivot_longer(-alpha, names_to = "curva", values_to = "Q") %>%
ggplot(aes(alpha, Q, color = curva)) +
geom_line(linewidth = 1) + geom_point(size = 1) +
geom_vline(xintercept = a_opt, linetype = "dashed") +
scale_color_manual(values = c(Q0norm = "#377EB8", Q1norm = "#E41A1C"),
labels = c("Q0 (socioeconómica)", "Q1 (geográfica)")) +
labs(title = paste0("Elección de α (k = ", K, ") — α óptimo = ", a_opt),
x = "α", y = "Inercia explicada normalizada", color = NULL) +
theme_minimal()
}
res_k4 <- analisis_alpha(K = k1)
alpha_k4 <- alpha_optimo(res_k4)
alpha_k4
## [1] 0.34
grafica_alpha(res_k4, k1, alpha_k4)
kable(round(res_k4[, c("alpha", "Q0norm", "Q1norm")], 3), caption = "Q0 y Q1 normalizadas por valor de α (k = 4)")
| alpha | Q0norm | Q1norm |
|---|---|---|
| 0.00 | 1.000 | 0.493 |
| 0.02 | 1.167 | 0.640 |
| 0.04 | 1.167 | 0.640 |
| 0.06 | 1.167 | 0.640 |
| 0.08 | 1.199 | 0.834 |
| 0.10 | 1.199 | 0.834 |
| 0.12 | 1.199 | 0.834 |
| 0.14 | 1.199 | 0.834 |
| 0.16 | 1.199 | 0.834 |
| 0.18 | 1.199 | 0.834 |
| 0.20 | 1.167 | 0.640 |
| 0.22 | 1.167 | 0.640 |
| 0.24 | 1.190 | 0.962 |
| 0.26 | 1.190 | 0.962 |
| 0.28 | 1.106 | 1.255 |
| 0.30 | 1.106 | 1.255 |
| 0.32 | 1.106 | 1.255 |
| 0.34 | 0.953 | 1.102 |
| 0.36 | 0.953 | 1.102 |
| 0.38 | 0.953 | 1.102 |
| 0.40 | 0.953 | 1.102 |
| 0.42 | 0.953 | 1.102 |
| 0.44 | 0.953 | 1.102 |
| 0.46 | 0.953 | 1.102 |
| 0.48 | 0.953 | 1.102 |
| 0.50 | 0.953 | 1.102 |
| 0.52 | 0.953 | 1.102 |
| 0.54 | 0.953 | 1.102 |
| 0.56 | 0.953 | 1.102 |
| 0.58 | 0.953 | 1.102 |
| 0.60 | 0.953 | 1.102 |
| 0.62 | 0.953 | 1.102 |
| 0.64 | 0.953 | 1.102 |
| 0.66 | 0.953 | 1.102 |
| 0.68 | 0.953 | 1.102 |
| 0.70 | 0.953 | 1.102 |
| 0.72 | 0.953 | 1.102 |
| 0.74 | 0.953 | 1.102 |
| 0.76 | 0.953 | 1.102 |
| 0.78 | 0.953 | 1.102 |
| 0.80 | 0.953 | 1.102 |
| 0.82 | 0.953 | 1.102 |
| 0.84 | 0.953 | 1.102 |
| 0.86 | 0.953 | 1.102 |
| 0.88 | 0.953 | 1.102 |
| 0.90 | 0.953 | 1.102 |
| 0.92 | 0.953 | 1.102 |
| 0.94 | 0.953 | 1.102 |
| 0.96 | 0.953 | 1.102 |
| 0.98 | 0.953 | 1.102 |
| 1.00 | 0.157 | 1.000 |
Si al ver la gráfica prefieren otro α (por ejemplo, el punto donde Q0 todavía no cae mucho pero Q1 ya subió), cambien
alpha_k4en el chunk anterior y justifíquenlo en las conclusiones.
hc_mix4 <- hclust(D_mezcla(alpha_k4), method = "complete")
cl_mix4 <- cutree(hc_mix4, k = k1)
table(cl_mix4)
## cl_mix4
## 1 2 3 4
## 2 3 19 8
fviz_dend(hc_mix4, k = k1, cex = 0.7, k_colors = "jco",
color_labels_by_k = TRUE, rect = TRUE, rect_fill = TRUE, rect_border = "jco",
main = paste0("Dendrograma socioeconómico + geográfico (completo, k = 4, α = ", alpha_k4, ")"),
ylab = "Distancia")
mapa_cl(cl_mix4, paste0("Conglomerados socioeconómicos + geográficos (k = 4, α = ", alpha_k4, ")"))
Se repite todo el análisis con un nuevo número de conglomerados.
Cambien k2 a 3 o 5 según su decisión.
k2 <- 5
cl0_k2 <- cutree(hc0, k = k2)
table(cl0_k2)
## cl0_k2
## 1 2 3 4 5
## 2 3 19 7 1
fviz_dend(hc0, k = k2, cex = 0.7, k_colors = "jco",
color_labels_by_k = TRUE, rect = TRUE, rect_fill = TRUE, rect_border = "jco",
main = paste0("Dendrograma socioeconómico (enlace completo, k = ", k2, ")"),
ylab = "Distancia")
mapa_cl(cl0_k2, paste0("Conglomerados socioeconómicos (enlace completo, k = ", k2, ")"))
res_k2 <- analisis_alpha(K = k2)
alpha_k2 <- alpha_optimo(res_k2)
alpha_k2
## [1] 0.24
grafica_alpha(res_k2, k2, alpha_k2)
kable(round(res_k2[, c("alpha", "Q0norm", "Q1norm")], 3), caption = paste0("Q0 y Q1 normalizadas por valor de α (k = ", k2, ")"))
| alpha | Q0norm | Q1norm |
|---|---|---|
| 0.00 | 1.000 | 0.516 |
| 0.02 | 1.030 | 0.635 |
| 0.04 | 1.030 | 0.635 |
| 0.06 | 1.023 | 0.670 |
| 0.08 | 1.035 | 0.788 |
| 0.10 | 1.044 | 0.835 |
| 0.12 | 1.044 | 0.835 |
| 0.14 | 1.035 | 0.788 |
| 0.16 | 1.035 | 0.788 |
| 0.18 | 1.035 | 0.788 |
| 0.20 | 1.023 | 0.670 |
| 0.22 | 1.023 | 0.670 |
| 0.24 | 1.035 | 0.938 |
| 0.26 | 1.035 | 0.938 |
| 0.28 | 0.961 | 1.150 |
| 0.30 | 0.961 | 1.150 |
| 0.32 | 0.961 | 1.150 |
| 0.34 | 0.845 | 1.039 |
| 0.36 | 0.845 | 1.039 |
| 0.38 | 0.845 | 1.039 |
| 0.40 | 0.845 | 1.039 |
| 0.42 | 0.845 | 1.039 |
| 0.44 | 0.845 | 1.039 |
| 0.46 | 0.845 | 1.039 |
| 0.48 | 0.845 | 1.039 |
| 0.50 | 0.845 | 1.039 |
| 0.52 | 0.845 | 1.039 |
| 0.54 | 0.845 | 1.039 |
| 0.56 | 0.845 | 1.039 |
| 0.58 | 0.845 | 1.039 |
| 0.60 | 0.845 | 1.039 |
| 0.62 | 0.845 | 1.039 |
| 0.64 | 0.845 | 1.039 |
| 0.66 | 0.845 | 1.039 |
| 0.68 | 0.845 | 1.039 |
| 0.70 | 0.845 | 1.039 |
| 0.72 | 0.845 | 1.039 |
| 0.74 | 0.845 | 1.039 |
| 0.76 | 0.845 | 1.039 |
| 0.78 | 0.845 | 1.039 |
| 0.80 | 0.845 | 1.039 |
| 0.82 | 0.845 | 1.039 |
| 0.84 | 0.845 | 1.039 |
| 0.86 | 0.845 | 1.039 |
| 0.88 | 0.845 | 1.039 |
| 0.90 | 0.845 | 1.039 |
| 0.92 | 0.845 | 1.039 |
| 0.94 | 0.845 | 1.039 |
| 0.96 | 0.845 | 1.039 |
| 0.98 | 0.845 | 1.039 |
| 1.00 | 0.216 | 1.000 |
hc_mix2 <- hclust(D_mezcla(alpha_k2), method = "complete")
cl_mix2 <- cutree(hc_mix2, k = k2)
table(cl_mix2)
## cl_mix2
## 1 2 3 4 5
## 3 3 15 7 4
fviz_dend(hc_mix2, k = k2, cex = 0.7, k_colors = "jco",
color_labels_by_k = TRUE, rect = TRUE, rect_fill = TRUE, rect_border = "jco",
main = paste0("Dendrograma socioeconómico + geográfico (completo, k = ", k2, ", α = ", alpha_k2, ")"),
ylab = "Distancia")
mapa_cl(cl_mix2, paste0("Conglomerados socioeconómicos + geográficos (k = ", k2, ", α = ", alpha_k2, ")"))
# Proporción de pares de estados vecinos (queen) que quedaron en el mismo clúster
prop_vecinos_mismo <- function(cl) {
pares <- do.call(rbind, lapply(seq_along(nb_queen), function(i) {
if (nb_queen[[i]][1] == 0) NULL else cbind(i, nb_queen[[i]])
}))
pares <- pares[pares[, 1] < pares[, 2], , drop = FALSE]
mean(cl[pares[, 1]] == cl[pares[, 2]])
}
soluciones <- list(cl0, cl_mix4, cl0_k2, cl_mix2)
names(soluciones) <- c("Socioeconómico, k = 4",
"Socio + geográfico, k = 4",
paste0("Socioeconómico, k = ", k2),
paste0("Socio + geográfico, k = ", k2))
comparativa <- data.frame(
Solucion = names(soluciones),
Clusters = sapply(soluciones, function(p) length(unique(p))),
Q0_socioeconomica = sapply(soluciones, function(p) round(Q_inercia(D0n, p), 3)),
Q1_geografica = sapply(soluciones, function(p) round(Q_inercia(D1n, p), 3)),
Pct_vecinos_mismo_cluster = sapply(soluciones, function(p) round(100 * prop_vecinos_mismo(p), 1)),
row.names = NULL
)
kable(comparativa, caption = "Comparativa de las cuatro soluciones")
| Solucion | Clusters | Q0_socioeconomica | Q1_geografica | Pct_vecinos_mismo_cluster |
|---|---|---|---|---|
| Socioeconómico, k = 4 | 4 | 0.404 | 0.092 | 71.0 |
| Socio + geográfico, k = 4 | 4 | 0.385 | 0.206 | 59.4 |
| Socioeconómico, k = 5 | 5 | 0.559 | 0.131 | 44.9 |
| Socio + geográfico, k = 5 | 5 | 0.579 | 0.238 | 40.6 |
cat("Socioeconómico vs. mezcla (k = 4)\n"); print(table(Socio = cl0, Mezcla = cl_mix4))
## Socioeconómico vs. mezcla (k = 4)
## Mezcla
## Socio 1 2 3 4
## 1 1 0 0 1
## 2 0 2 1 0
## 3 1 0 18 7
## 4 0 1 0 0
cat("\nSocioeconómico vs. mezcla (k =", k2, ")\n"); print(table(Socio = cl0_k2, Mezcla = cl_mix2))
##
## Socioeconómico vs. mezcla (k = 5 )
## Mezcla
## Socio 1 2 3 4 5
## 1 2 0 0 0 0
## 2 0 2 0 1 0
## 3 0 0 13 6 0
## 4 1 0 2 0 4
## 5 0 1 0 0 0
kable(aggregate(X, by = list(Clúster = cl_mix4), FUN = mean) %>% mutate(across(where(is.numeric), ~ round(.x, 2))),
caption = "Perfil promedio por clúster — socio + geográfico, k = 4")
| Clúster | crime_rate | employment | real_wage | lq_secondary |
|---|---|---|---|---|
| 1 | 70.77 | 0.97 | 345.62 | 1.65 |
| 2 | 8.60 | 0.94 | 412.31 | 1.14 |
| 3 | 18.28 | 0.97 | 327.63 | 0.94 |
| 4 | 56.20 | 0.97 | 344.07 | 0.97 |
kable(aggregate(X, by = list(Clúster = cl_mix2), FUN = mean) %>% mutate(across(where(is.numeric), ~ round(.x, 2))),
caption = paste0("Perfil promedio por clúster — socio + geográfico, k = ", k2))
| Clúster | crime_rate | employment | real_wage | lq_secondary |
|---|---|---|---|---|
| 1 | 75.40 | 0.97 | 359.82 | 1.64 |
| 2 | 8.60 | 0.94 | 412.31 | 1.14 |
| 3 | 19.00 | 0.97 | 350.24 | 0.91 |
| 4 | 17.92 | 0.98 | 293.90 | 1.01 |
| 5 | 75.45 | 0.97 | 319.62 | 0.83 |
(Redactar después de ejecutar y ver sus resultados reales. Sugerencia de estructura:)
Pct_vecinos_mismo_cluster) y cuánto
cayó la homogeneidad socioeconómica (Q0). Estados que
cambiaron de clúster y por qué.