Victor Hugo Perilla Martinez
Profesor: Delia Ortega Lenis
# -------------------------------------------------------------------------
# Carga de la base de datos
# -------------------------------------------------------------------------
data("vivienda")
# Copia de trabajo para conservar intacta la base original
inmuebles <- vivienda
#str(inmuebles)En este informe se desarrolla una evaluación holística de la oferta inmobiliaria urbana mediante técnicas de análisis multivariado. La base contiene información de localización, características físicas y precio comercial de las viviendas. Más allá de limitarse a describir cada variable de manera aislada, se realiza un estudio simultaneo de sus relaciones:
El desarrollo se hace en base a lo aprendido en el curso: comprensión y depuración de los datos, análisis exploratorio, matriz de correlaciones, análisis de componentes principales, análisis de conglomerados, análisis de correspondencia y comunicación de resultados.
Una empresa inmobiliaria necesita comprender la estructura del mercado de viviendas urbanas para orientar decisiones de compra, venta, valoración, focalización comercial y diseño de portafolio. La oferta no depende únicamente del precio, intervienen otras características del predio, tales como: el área construida, el número de habitaciones, los baños, número de parqueaderos, el estrato socioeconómico, el tipo de vivienda y la ubicación.
Por lo cual, el problema se analiza con un enfoque multivariado, porque varias variables actúan de manera simultánea y pueden presentar relaciones, redundancias y patrones de agrupación que no serían visibles mediante análisis univariados separados.
¿Qué dimensiones, segmentos y asociaciones caracterizan la
oferta inmobiliaria urbana registrada en la base de datos
vivienda, y cómo pueden estos resultados permitir
decisiones estratégicas de la empresa?
Analizar la estructura multivariada de la oferta inmobiliaria urbana mediante componentes principales, conglomerados y correspondencias, con el fin de identificar patrones relevantes para la toma de decisiones.
cat(
"La base contiene", format(nrow(inmuebles), big.mark = "."),
"registros y", ncol(inmuebles), "variables."
)## La base contiene 8.322 registros y 13 variables.
diccionario <- tibble::tribble(
~Variable, ~Tipo, ~Descripción, ~Rol_analítico,
"id", "Identificador", "Código del inmueble", "No se utiliza para calcular distancias",
"zona", "Categórica nominal", "Zona urbana de ubicación", "Segmentación y correspondencia",
"piso", "Categórica/ordinal", "Piso del inmueble; puede contener NA", "Descripción complementaria",
"estrato", "Ordinal", "Estrato socioeconómico", "Correspondencia y caracterización de segmentos",
"preciom", "Cuantitativa", "Precio comercial del inmueble", "PCA, conglomerados y análisis de valor",
"areaconst", "Cuantitativa", "Área construida", "PCA y conglomerados",
"parqueaderos", "Cuantitativa discreta", "Número de parqueaderos", "PCA y conglomerados",
"banios", "Cuantitativa discreta", "Número de baños", "PCA y conglomerados",
"habitaciones", "Cuantitativa discreta", "Número de habitaciones", "PCA y conglomerados",
"tipo", "Categórica nominal", "Tipo de vivienda", "Correspondencia y perfil de segmentos",
"barrio", "Categórica nominal", "Barrio de ubicación", "Correspondencia y localización",
"longitud", "Cuantitativa geográfica", "Coordenada longitudinal", "Visualización espacial",
"latitud", "Cuantitativa geográfica", "Coordenada latitudinal", "Visualización espacial"
)
tabla_html(diccionario, "Diccionario analítico de variables", digitos = 2)| Variable | Tipo | Descripción | Rol_analítico |
|---|---|---|---|
| id | Identificador | Código del inmueble | No se utiliza para calcular distancias |
| zona | Categórica nominal | Zona urbana de ubicación | Segmentación y correspondencia |
| piso | Categórica/ordinal | Piso del inmueble; puede contener NA | Descripción complementaria |
| estrato | Ordinal | Estrato socioeconómico | Correspondencia y caracterización de segmentos |
| preciom | Cuantitativa | Precio comercial del inmueble | PCA, conglomerados y análisis de valor |
| areaconst | Cuantitativa | Área construida | PCA y conglomerados |
| parqueaderos | Cuantitativa discreta | Número de parqueaderos | PCA y conglomerados |
| banios | Cuantitativa discreta | Número de baños | PCA y conglomerados |
| habitaciones | Cuantitativa discreta | Número de habitaciones | PCA y conglomerados |
| tipo | Categórica nominal | Tipo de vivienda | Correspondencia y perfil de segmentos |
| barrio | Categórica nominal | Barrio de ubicación | Correspondencia y localización |
| longitud | Cuantitativa geográfica | Coordenada longitudinal | Visualización espacial |
| latitud | Cuantitativa geográfica | Coordenada latitudinal | Visualización espacial |
Se verifican valores faltantes, duplicados, rangos imposibles (outliers) y consistencia básica. Esta fase es indispensable porque las técnicas multivariadas son sensibles a errores de registro, escalas distintas y observaciones incompletas.
calidad <- tibble(
variable = names(inmuebles),
clase = sapply(inmuebles, function(x) paste(class(x), collapse = "/")),
faltantes = sapply(inmuebles, function(x) sum(is.na(x))),
porcentaje_faltantes = sapply(inmuebles, function(x) mean(is.na(x)) * 100),
valores_unicos = sapply(inmuebles, dplyr::n_distinct)
)
tabla_html(calidad, "Diagnóstico de calidad por variable", digitos = 2)| variable | clase | faltantes | porcentaje_faltantes | valores_unicos |
|---|---|---|---|---|
| id | numeric | 3 | 0.04 | 8320 |
| zona | character | 3 | 0.04 | 6 |
| piso | character | 2638 | 31.70 | 13 |
| estrato | numeric | 3 | 0.04 | 5 |
| preciom | numeric | 2 | 0.02 | 540 |
| areaconst | numeric | 3 | 0.04 | 653 |
| parqueaderos | numeric | 1605 | 19.29 | 11 |
| banios | numeric | 3 | 0.04 | 12 |
| habitaciones | numeric | 3 | 0.04 | 12 |
| tipo | character | 3 | 0.04 | 3 |
| barrio | character | 3 | 0.04 | 437 |
| longitud | numeric | 3 | 0.04 | 2929 |
| latitud | numeric | 3 | 0.04 | 3680 |
duplicados_id <- sum(duplicated(inmuebles$id))
duplicados_fila <- sum(duplicated(inmuebles))
tibble(
verificacion = c("Identificadores duplicados", "Filas completamente duplicadas"),
cantidad = c(duplicados_id, duplicados_fila)
) |>
tabla_html("Control de duplicados", digitos = 0)| verificacion | cantidad |
|---|---|
| Identificadores duplicados | 2 |
| Filas completamente duplicadas | 1 |
Inmuebles con id faltante:
inmuebles_id_na <- inmuebles %>%
filter(is.na(id))
tabla_html(
inmuebles_id_na,
"Registros con ID faltante",
digitos = 2
)| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
|
|
|
|
|
|
|
|
tabla_piso_tipo_pct <- inmuebles %>%
mutate(
Piso_faltante = ifelse(
is.na(piso),
"Faltante",
"Disponible"
)
) %>%
count(tipo, Piso_faltante) %>%
group_by(tipo) %>%
mutate(
Porcentaje = round(
n / sum(n) * 100,
2
)
) %>%
ungroup() %>%
rename(
Tipo_inmueble = tipo,
Cantidad = n
)
tabla_html(
tabla_piso_tipo_pct,
"Análisis de valores faltantes en piso según tipo de inmueble",
digitos = 2
)| Tipo_inmueble | Piso_faltante | Cantidad | Porcentaje |
|---|---|---|---|
| Apartamento | Disponible | 3719 | 72.92 |
| Apartamento | Faltante | 1381 | 27.08 |
| Casa | Disponible | 1965 | 61.04 |
| Casa | Faltante | 1254 | 38.96 |
|
|
Faltante | 3 | 100.00 |
Los valores faltantes de la variable piso se presentan tanto en apartamentos (27.08%) como en casas (38.95%). Aunque la proporción es mayor en casas, la ausencia también ocurre en apartamentos, por lo cual no se puede asumir que los valores faltantes representan únicamente inmuebles donde la variable no aplica. En consecuencia, los valores faltantes deben tratarse como información no disponible, y en nuestro puede descartarse la variable de análisis.
Por otro lado parqueaderos puede imputarse con 0 debido a que se observa que en los datos aportados el mínimo es 1, por lo cual el dato faltante puede asumirse en 0 ya que si es un dato no reportado debido a que no existe parqueadero.
Antes de continuar lo primero que se hará es eliminar los datos duplicados y datos con id faltante, estos coinciden con los faltantes de otras variables.
# Resumen antes de eliminar duplicados y registros sin ID
resumen_antes <- data.frame(
Indicador = c(
"Número de registros",
"IDs faltantes (NA)",
"IDs duplicados",
"Filas completamente duplicadas"
),
Cantidad = c(
nrow(inmuebles),
sum(is.na(inmuebles$id)),
sum(duplicated(inmuebles$id)),
sum(duplicated(inmuebles))
)
)
# 1. Eliminar registros sin ID
inmuebles_limpio <- inmuebles %>%
filter(!is.na(id))
# 2. Eliminar filas completamente duplicadas
inmuebles_limpio <- inmuebles_limpio %>%
distinct()
# 3. Eliminar registros con ID repetido
# Conserva la primera aparición del ID
inmuebles_limpio <- inmuebles_limpio %>%
distinct(id, .keep_all = TRUE)
# Resumen después de limpieza
resumen_despues <- data.frame(
Indicador = c(
"Número de registros",
"IDs faltantes (NA)",
"IDs duplicados",
"Filas completamente duplicadas"
),
Cantidad = c(
nrow(inmuebles_limpio),
sum(is.na(inmuebles_limpio$id)),
sum(duplicated(inmuebles_limpio$id)),
sum(duplicated(inmuebles_limpio))
)
)
# Consolidar tabla
tabla_duplicados <- rbind(
data.frame(
Momento = "Antes",
resumen_antes
),
data.frame(
Momento = "Después",
resumen_despues
)
)
tabla_html(
tabla_duplicados,
"Resumen del tratamiento de identificadores y registros duplicados",
digitos = 0
)| Momento | Indicador | Cantidad |
|---|---|---|
| Antes | Número de registros | 8322 |
| Antes | IDs faltantes (NA) | 3 |
| Antes | IDs duplicados | 2 |
| Antes | Filas completamente duplicadas | 1 |
| Después | Número de registros | 8319 |
| Después | IDs faltantes (NA) | 0 |
| Después | IDs duplicados | 0 |
| Después | Filas completamente duplicadas | 0 |
# Copia de la base
inmuebles_imp <- inmuebles_limpio
# Cantidad de faltantes antes
faltantes_antes <- sum(is.na(inmuebles_imp$parqueaderos))
# Imputación: NA = 0 parqueaderos
inmuebles_imp$parqueaderos[
is.na(inmuebles_imp$parqueaderos)
] <- 0
# Cantidad de faltantes después
faltantes_despues <- sum(is.na(inmuebles_imp$parqueaderos))
# Tabla resumen de imputación
tabla_imputacion <- data.frame(
Variable = "parqueaderos",
Faltantes_antes = faltantes_antes,
Valores_imputados = faltantes_antes - faltantes_despues,
Faltantes_despues = faltantes_despues,
Metodo = "Recodificación NA a 0",
Justificacion = "Se asume que ausencia de registro indica inexistencia de parqueaderos"
)
tabla_html(
tabla_imputacion,
"Resumen de imputación de parqueaderos",
digitos = 2
)| Variable | Faltantes_antes | Valores_imputados | Faltantes_despues | Metodo | Justificacion |
|---|---|---|---|---|---|
| parqueaderos | 1602 | 1602 | 0 | Recodificación NA a 0 | Se asume que ausencia de registro indica inexistencia de parqueaderos |
Podemos ver el resultado de la limpieza:
calidad <- tibble(
variable = names(inmuebles_imp),
clase = sapply(inmuebles_imp, function(x) paste(class(x), collapse = "/")),
faltantes = sapply(inmuebles_imp, function(x) sum(is.na(x))),
porcentaje_faltantes = sapply(inmuebles_imp, function(x) mean(is.na(x)) * 100),
valores_unicos = sapply(inmuebles_imp, dplyr::n_distinct)
)
tabla_html(calidad, "Diagnóstico de calidad por variable", digitos = 2)| variable | clase | faltantes | porcentaje_faltantes | valores_unicos |
|---|---|---|---|---|
| id | numeric | 0 | 0.00 | 8319 |
| zona | character | 0 | 0.00 | 5 |
| piso | character | 2635 | 31.67 | 13 |
| estrato | numeric | 0 | 0.00 | 4 |
| preciom | numeric | 0 | 0.00 | 539 |
| areaconst | numeric | 0 | 0.00 | 652 |
| parqueaderos | numeric | 0 | 0.00 | 11 |
| banios | numeric | 0 | 0.00 | 11 |
| habitaciones | numeric | 0 | 0.00 | 11 |
| tipo | character | 0 | 0.00 | 2 |
| barrio | character | 0 | 0.00 | 436 |
| longitud | numeric | 0 | 0.00 | 2928 |
| latitud | numeric | 0 | 0.00 | 3679 |
Para el análisis cuantitativo se seleccionan las variables que describen directamente el tamaño, dotación, nivel socioeconómico y valor del inmueble:
preciomareaconstparqueaderosbanioshabitacionesestratoEl identificador no se usa como variable explicativa. Las coordenadas
se reservan para visualización espacial, porque incluirlas en el PCA o
en el conglomerado mezclaría proximidad geográfica con características
físicas y comerciales. La variable piso presentó un 31,7 %
de valores faltantes y debido a que este porcentaje es elevado y no
existe evidencia suficiente para realizar una imputación confiable, la
variable se excluyó de las técnicas multivariadas. Sin embargo, se
conservó para el análisis descriptivo con el fin de evitar introducir
sesgos en los resultados.
vars_cuant <- c(
"preciom", "areaconst", "parqueaderos",
"banios", "habitaciones", "estrato"
)
datos_completos <- inmuebles_imp |>
mutate(.fila_original = row_number()) |>
mutate(across(all_of(vars_cuant), as.numeric)) |>
filter(if_all(all_of(vars_cuant), is.finite)) |>
drop_na(all_of(vars_cuant))
datos_modelo <- datos_completos |>
select(all_of(vars_cuant))
filas_modelo <- datos_completos$.fila_original
n_excluidos <- nrow(inmuebles_imp) - nrow(datos_modelo)
cat(
"Registros completos empleados en los métodos cuantitativos:",
format(nrow(datos_modelo), big.mark = ".")
)## Registros completos empleados en los métodos cuantitativos: 8.319
resumen_cuant <- purrr::map_dfr(
vars_cuant,
function(v) {
x <- datos_modelo[[v]]
tibble(
variable = v,
n = length(x),
media = mean(x),
desviacion = sd(x),
minimo = min(x),
q1 = quantile(x, 0.25),
mediana = median(x),
q3 = quantile(x, 0.75),
maximo = max(x),
coef_variacion = sd(x) / mean(x),
asimetria = mean((x - mean(x))^3) / sd(x)^3
)
}
)
tabla_html(
resumen_cuant,
"Estadísticos descriptivos de las variables cuantitativas",
digitos = 2
)| variable | n | media | desviacion | minimo | q1 | mediana | q3 | maximo | coef_variacion | asimetria |
|---|---|---|---|---|---|---|---|---|---|---|
| preciom | 8319 | 433.90 | 328.67 | 58 | 220 | 330 | 540 | 1999 | 0.76 | 1.85 |
| areaconst | 8319 | 174.93 | 142.96 | 30 | 80 | 123 | 229 | 1745 | 0.82 | 2.69 |
| parqueaderos | 8319 | 1.48 | 1.24 | 0 | 1 | 1 | 2 | 10 | 0.84 | 1.65 |
| banios | 8319 | 3.11 | 1.43 | 0 | 2 | 3 | 4 | 10 | 0.46 | 0.93 |
| habitaciones | 8319 | 3.61 | 1.46 | 0 | 3 | 3 | 4 | 10 | 0.40 | 1.63 |
| estrato | 8319 | 4.63 | 1.03 | 3 | 4 | 5 | 5 | 6 | 0.22 | -0.18 |
La media y la mediana se verifican para detectar asimetrías. El coeficiente de variación permite comparar dispersión relativa entre variables medidas en escalas distintas. Una diferencia amplia entre media y mediana, acompañada de asimetría positiva, sugiere que unas pocas propiedades de gran tamaño o alto precio extienden la cola derecha de la distribución.
datos_largos <- datos_modelo |>
pivot_longer(
cols = everything(),
names_to = "variable",
values_to = "valor"
)
ggplot(datos_largos, aes(x = valor)) +
geom_histogram(bins = 35, fill = "#F28E2B", color = "white", alpha = 0.85) +
facet_wrap(~ variable, scales = "free", ncol = 2) +
labs(
title = "Distribución de las variables cuantitativas",
subtitle = "Las escalas se presentan de forma independiente para facilitar la lectura",
x = NULL,
y = "Frecuencia"
) +
theme_minimal(base_size = 12)precio_tipo <- inmuebles_imp |>
filter(!is.na(tipo), !is.na(preciom)) |>
group_by(tipo) |>
summarise(
oferta = n(),
precio_mediano = median(preciom, na.rm = TRUE),
precio_promedio = mean(preciom, na.rm = TRUE),
area_mediana = median(areaconst, na.rm = TRUE),
.groups = "drop"
) |>
arrange(desc(precio_mediano))
tabla_html(
precio_tipo,
"Resumen de precio por tipo de vivienda",
digitos = 2
)| tipo | oferta | precio_mediano | precio_promedio | area_mediana |
|---|---|---|---|---|
| Casa | 3219 | 430 | 539.99 | 240 |
| Apartamento | 5100 | 279 | 366.94 | 90 |
ggplot(
inmuebles_imp |> filter(!is.na(tipo), !is.na(preciom)),
aes(x = forcats::fct_reorder(tipo, preciom, median, na.rm = TRUE), y = preciom)
) +
geom_boxplot(fill = "#F28E2B", alpha = 0.75, outlier.alpha = 0.25) +
coord_flip() +
scale_y_continuous(labels = scales::label_number(big.mark = ".", decimal.mark = ",")) +
labs(
title = "Distribución del precio por tipo de vivienda",
x = "Tipo de vivienda",
y = "Precio"
) +
theme_minimal(base_size = 12)precio_zona <- inmuebles_imp |>
filter(!is.na(zona), !is.na(preciom)) |>
group_by(zona) |>
summarise(
oferta = n(),
precio_mediano = median(preciom),
precio_promedio = mean(preciom),
area_mediana = median(areaconst, na.rm = TRUE),
.groups = "drop"
) |>
arrange(desc(precio_mediano))
tabla_html(precio_zona, "Oferta y precio por zona", digitos = 2)| zona | oferta | precio_mediano | precio_promedio | area_mediana |
|---|---|---|---|---|
| Zona Oeste | 1198 | 580 | 677.58 | 165.5 |
| Zona Sur | 4726 | 320 | 426.52 | 113.0 |
| Zona Norte | 1920 | 300 | 345.61 | 107.0 |
| Zona Centro | 124 | 297 | 309.69 | 160.0 |
| Zona Oriente | 351 | 210 | 228.53 | 160.0 |
ggplot(
inmuebles |> filter(!is.na(zona), !is.na(preciom)),
aes(
x = forcats::fct_reorder(zona, preciom, median, na.rm = TRUE),
y = preciom
)
) +
geom_boxplot(
fill = "#4E79A7",
alpha = 0.75,
outlier.alpha = 0.25
) +
coord_flip() +
scale_y_continuous(
labels = scales::label_number(
big.mark = ".",
decimal.mark = ","
)
) +
labs(
title = "Distribución del precio por zona",
x = "Zona",
y = "Precio"
) +
theme_minimal(base_size = 12)Antes de construir la matriz de correlaciones se revisó la naturaleza
de las variables, su variabilidad y el comportamiento gráfico de sus
relaciones. Esta verificación es necesaria porque el hecho de que una
variable esté almacenada en R como numeric no implica
necesariamente que sea cuantitativa en términos estadísticos.
diagnostico_cor <- tibble(
variable = names(datos_modelo),
clase = sapply(
datos_modelo,
function(x) paste(class(x), collapse = "/")
),
valores_unicos = sapply(
datos_modelo,
dplyr::n_distinct
),
faltantes = sapply(
datos_modelo,
function(x) sum(is.na(x))
),
desviacion_estandar = sapply(
datos_modelo,
function(x) {
if (is.numeric(x)) sd(x, na.rm = TRUE) else NA_real_
}
)
)
tabla_html(
diagnostico_cor,
"Verificación de las variables para el análisis de correlación",
digitos = 3
)| variable | clase | valores_unicos | faltantes | desviacion_estandar |
|---|---|---|---|---|
| preciom | numeric | 539 | 0 | 328.665 |
| areaconst | numeric | 652 | 0 | 142.964 |
| parqueaderos | numeric | 11 | 0 | 1.243 |
| banios | numeric | 11 | 0 | 1.428 |
| habitaciones | numeric | 11 | 0 | 1.460 |
| estrato | numeric | 4 | 0 | 1.029 |
Las variables preciom y areaconst son
cuantitativas continuas, mientras que parqueaderos,
banios y habitaciones corresponden a variables
cuantitativas discretas de conteo. Por su parte, estrato,
aunque se encuentra almacenada numéricamente, es conceptualmente una
variable ordinal, pues sus valores representan categorías
socioeconómicas ordenadas.
Todas las variables presentan variabilidad y no se observan valores faltantes en el conjunto utilizado para este análisis.
GGally::ggpairs(
datos_modelo,
lower = list(
continuous = GGally::wrap(
"smooth",
alpha = 0.4,
size = 0.3
)
),
diag = list(
continuous = "densityDiag"
),
upper = list(
continuous = "cor"
)
)La exploración gráfica evidencia que preciom y
areaconst presentan distribuciones asimétricas hacia la
derecha. Las variables parqueaderos, banios y
habitaciones, al ser conteos discretos, generan
concentraciones de observaciones en valores enteros. No obstante, los
diagramas permiten identificar tendencias aproximadamente monotónicas y,
en varios casos, lineales entre las variables.
Debido a estas características, se utilizará la correlación de
Pearson para describir las asociaciones lineales entre las variables
cuantitativas. La variable estrato se analizará
adicionalmente mediante el coeficiente de Spearman, debido a su
naturaleza ordinal.
La matriz de covarianzas permite analizar la variabilidad conjunta de las variables cuantitativas, aunque sus magnitudes dependen de las unidades de medida. La matriz de correlaciones de Pearson estandariza estas relaciones en una escala comprendida entre -1 y 1, permitiendo comparar la intensidad y dirección de las asociaciones lineales.
Para este análisis se consideran como variables cuantitativas
preciom, areaconst, parqueaderos,
banios y habitaciones.
datos_pearson <- datos_modelo |>
dplyr::select(
preciom,
areaconst,
parqueaderos,
banios,
habitaciones
)matriz_cov <- cov(
datos_pearson,
use = "complete.obs"
)
tabla_html(
as.data.frame(matriz_cov) |>
tibble::rownames_to_column("Variable"),
"Matriz de varianzas-covarianzas",
digitos = 2
)| Variable | preciom | areaconst | parqueaderos | banios | habitaciones |
|---|---|---|---|---|---|
| preciom | 108020.70 | 32296.82 | 261.40 | 314.10 | 126.68 |
| areaconst | 32296.82 | 20438.74 | 85.73 | 132.40 | 107.86 |
| parqueaderos | 261.40 | 85.73 | 1.55 | 0.93 | 0.36 |
| banios | 314.10 | 132.40 | 0.93 | 2.04 | 1.23 |
| habitaciones | 126.68 | 107.86 | 0.36 | 1.23 | 2.13 |
matriz_cor <- cor(
datos_pearson,
use = "complete.obs",
method = "pearson"
)
tabla_html(
as.data.frame(matriz_cor) |>
tibble::rownames_to_column("Variable"),
"Matriz de correlaciones de Pearson",
digitos = 3
)| Variable | preciom | areaconst | parqueaderos | banios | habitaciones |
|---|---|---|---|---|---|
| preciom | 1.000 | 0.687 | 0.640 | 0.669 | 0.264 |
| areaconst | 0.687 | 1.000 | 0.482 | 0.648 | 0.517 |
| parqueaderos | 0.640 | 0.482 | 1.000 | 0.523 | 0.199 |
| banios | 0.669 | 0.648 | 0.523 | 1.000 | 0.590 |
| habitaciones | 0.264 | 0.517 | 0.199 | 0.590 | 1.000 |
corrplot::corrplot(
matriz_cor,
method = "ellipse",
type = "upper",
addCoef.col = "black",
tl.col = "black",
tl.srt = 35,
number.cex = 0.75,
diag = FALSE
)pares_cor <- as.data.frame(as.table(matriz_cor)) |>
dplyr::rename(
variable_1 = Var1,
variable_2 = Var2,
correlacion = Freq
) |>
dplyr::filter(
as.character(variable_1) <
as.character(variable_2)
) |>
dplyr::mutate(
magnitud = abs(correlacion)
) |>
dplyr::arrange(desc(magnitud))
tabla_html(
head(pares_cor, 10),
"Diez relaciones lineales de mayor magnitud",
digitos = 3
)| variable_1 | variable_2 | correlacion | magnitud |
|---|---|---|---|
| areaconst | preciom | 0.687 | 0.687 |
| banios | preciom | 0.669 | 0.669 |
| areaconst | banios | 0.648 | 0.648 |
| parqueaderos | preciom | 0.640 | 0.640 |
| banios | habitaciones | 0.590 | 0.590 |
| banios | parqueaderos | 0.523 | 0.523 |
| areaconst | habitaciones | 0.517 | 0.517 |
| areaconst | parqueaderos | 0.482 | 0.482 |
| habitaciones | preciom | 0.264 | 0.264 |
| habitaciones | parqueaderos | 0.199 | 0.199 |
Tomando como referencia práctica una magnitud absoluta de 0,70 para identificar asociaciones lineales altas, se verifica si existen pares de variables que superen dicho valor.
cor_altas <- pares_cor |>
dplyr::filter(magnitud >= 0.70)
if (nrow(cor_altas) == 0) {
cat(
"No se identificaron correlaciones de Pearson con ",
"magnitud absoluta igual o superior a 0,70. ",
"Bajo el criterio adoptado, no se evidencia una alta ",
"redundancia lineal entre las variables cuantitativas."
)
} else {
tabla_html(
cor_altas,
"Correlaciones con magnitud absoluta igual o superior a 0,70",
digitos = 3
)
}## No se identificaron correlaciones de Pearson con magnitud absoluta igual o superior a 0,70. Bajo el criterio adoptado, no se evidencia una alta redundancia lineal entre las variables cuantitativas.
Debido a que estrato es una variable ordinal, sus
asociaciones se complementan mediante el coeficiente de correlación de
Spearman. Este coeficiente se basa en los rangos de las observaciones y
resulta apropiado para evaluar asociaciones monotónicas cuando
intervienen variables ordinales.
cor_spearman <- sapply(
datos_modelo |>
dplyr::select(-estrato),
function(x) {
cor(
x,
datos_modelo$estrato,
use = "complete.obs",
method = "spearman"
)
}
)
tabla_spearman <- tibble(
variable = names(cor_spearman),
correlacion_spearman = as.numeric(cor_spearman),
magnitud = abs(as.numeric(cor_spearman))
) |>
dplyr::arrange(desc(magnitud))
tabla_html(
tabla_spearman,
"Correlación de Spearman entre estrato y las demás variables",
digitos = 3
)| variable | correlacion_spearman | magnitud |
|---|---|---|
| preciom | 0.710 | 0.710 |
| parqueaderos | 0.586 | 0.586 |
| banios | 0.471 | 0.471 |
| areaconst | 0.395 | 0.395 |
| habitaciones | 0.040 | 0.040 |
La correlación no implica causalidad. En este análisis, los
coeficientes se utilizan para describir el grado de asociación entre las
variables y detectar posibles relaciones fuertes que puedan indicar
información redundante. El valor de 0,70 se adopta como un criterio
práctico de referencia y no como un límite estadístico universal, en
este caso preciom y estrato.
El Análisis de Componentes Principales permite resumir la variabilidad conjunta de varias variables cuantitativas mediante nuevas dimensiones no correlacionadas entre sí. Cada componente corresponde a una combinación lineal de las variables originales y se ordena de acuerdo con la cantidad de variabilidad que explica.
Para este análisis se utilizan preciom,
areaconst, parqueaderos, banios y
habitaciones. La variable estrato no se
incorpora como variable activa del ACP porque, aunque se encuentra
almacenada numéricamente, representa categorías ordinales. En
consecuencia, se conserva como variable externa para apoyar
posteriormente la interpretación de los componentes y para el análisis
de correspondencias.
Las variables cuantitativas presentan unidades y escalas diferentes. Por esta razón, el ACP se realiza con centrado y estandarización, evitando que el precio o el área construida dominen los componentes únicamente por tener magnitudes numéricas mayores.
datosPca <- datos_modelo |>
dplyr::transmute(
Precio = preciom,
AreaConstruida = areaconst,
Parqueaderos = parqueaderos,
Banios = banios,
Habitaciones = habitaciones
)
datosPcaZ <- scale(datosPca)
verificacionEscala <- tibble(
Variable = colnames(datosPcaZ),
MediaEstandarizada = apply(datosPcaZ, 2, mean),
DesviacionEstandarizada = apply(datosPcaZ, 2, sd)
)
tabla_html(
verificacionEscala,
"Verificación de la estandarización para el ACP",
digitos = 4
)| Variable | MediaEstandarizada | DesviacionEstandarizada |
|---|---|---|
| Precio | 0 | 1 |
| AreaConstruida | 0 | 1 |
| Parqueaderos | 0 | 1 |
| Banios | 0 | 1 |
| Habitaciones | 0 | 1 |
La media de cada variable estandarizada debe ser aproximadamente cero y su desviación estándar aproximadamente uno. Esta transformación permite comparar las variables en una escala común.
Los autovalores representan la variabilidad explicada por cada componente. A partir de ellos se calcula tanto el porcentaje de varianza explicada individual como el porcentaje acumulado.
autovaloresPca <- modeloPca$sdev^2
varianzaPca <- autovaloresPca / sum(autovaloresPca) * 100
varianzaAcumuladaPca <- cumsum(varianzaPca)
tablaVarianzaPca <- tibble(
Componente = paste0("CP", seq_along(autovaloresPca)),
Autovalor = autovaloresPca,
VarianzaExplicada = varianzaPca,
VarianzaAcumulada = varianzaAcumuladaPca
)
tabla_html(
tablaVarianzaPca,
"Autovalores y porcentaje de varianza explicada",
digitos = 2
)| Componente | Autovalor | VarianzaExplicada | VarianzaAcumulada |
|---|---|---|---|
| CP1 | 3.13 | 62.57 | 62.57 |
| CP2 | 0.92 | 18.36 | 80.92 |
| CP3 | 0.44 | 8.70 | 89.63 |
| CP4 | 0.32 | 6.49 | 96.12 |
| CP5 | 0.19 | 3.88 | 100.00 |
factoextra::fviz_eig(
modeloPca,
addlabels = TRUE
) +
labs(
title = "Gráfico de sedimentación del ACP",
subtitle = "Porcentaje de variabilidad explicado por cada componente",
x = "Componente",
y = "Varianza Explicada (%)"
) +
theme_minimal(base_size = 12)k70Pca <- which(varianzaAcumuladaPca >= 70)[1]
k80Pca <- which(varianzaAcumuladaPca >= 80)[1]
kKaiserPca <- sum(autovaloresPca > 1)
cat(
paste0(
"Los dos primeros componentes explican **",
round(sum(varianzaPca[1:2]), 1),
"%** de la variabilidad total. Para alcanzar al menos el 70% se requieren **",
k70Pca,
" componentes**, y para alcanzar al menos el 80% se requieren **",
k80Pca,
" componentes**. Según el criterio de Kaiser, se retienen **",
kKaiserPca,
" componentes** con autovalor mayor que uno."
)
)Los dos primeros componentes explican 80.9% de la variabilidad total. Para alcanzar al menos el 70% se requieren 2 componentes, y para alcanzar al menos el 80% se requieren 2 componentes. Según el criterio de Kaiser, se retienen 1 componentes con autovalor mayor que uno.
La selección final no se basa únicamente en la posibilidad de representar dos dimensiones. Se consideran conjuntamente la varianza acumulada, el comportamiento del gráfico de sedimentación y el criterio de Kaiser. Los dos primeros componentes se utilizan para la representación gráfica, mientras que la cantidad de componentes necesaria para resumir la información se interpreta a partir de los porcentajes anteriores.
Las cargas permiten identificar qué variables tienen mayor peso en cada componente y, por tanto, ayudan a asignar un significado estadístico a las nuevas dimensiones.
cargasPca <- as.data.frame(modeloPca$rotation) |>
tibble::rownames_to_column("Variable")
tabla_html(
cargasPca,
"Cargas de las variables en los componentes principales",
digitos = 3
)| Variable | PC1 | PC2 | PC3 | PC4 | PC5 |
|---|---|---|---|---|---|
| Precio | 0.479 | 0.360 | -0.359 | 0.264 | 0.666 |
| AreaConstruida | 0.484 | -0.085 | -0.562 | -0.538 | -0.392 |
| Parqueaderos | 0.408 | 0.520 | 0.661 | -0.346 | -0.081 |
| Banios | 0.496 | -0.157 | 0.121 | 0.695 | -0.482 |
| Habitaciones | 0.352 | -0.754 | 0.323 | -0.196 | 0.406 |
factoextra::fviz_pca_var(
modeloPca,
col.var = "contrib",
repel = TRUE
) +
labs(
title = "Círculo de correlaciones del ACP",
subtitle = "Dirección y contribución de las variables",
x = paste0("CP1 (", round(varianzaPca[1], 1), "%)"),
y = paste0("CP2 (", round(varianzaPca[2], 1), "%)")
) +
theme_minimal(base_size = 12)variablesDominantesPca <- cargasPca |>
dplyr::select(Variable, PC1, PC2) |>
dplyr::mutate(
MagnitudPC1 = abs(PC1),
MagnitudPC2 = abs(PC2)
)
topPc1 <- variablesDominantesPca |>
dplyr::arrange(desc(MagnitudPC1)) |>
dplyr::slice_head(n = 3) |>
dplyr::select(Variable, PC1, MagnitudPC1) |>
dplyr::rename(
Componente1 = PC1,
MagnitudComponente1 = MagnitudPC1
)
topPc2 <- variablesDominantesPca |>
dplyr::arrange(desc(MagnitudPC2)) |>
dplyr::slice_head(n = 3) |>
dplyr::select(Variable, PC2, MagnitudPC2) |>
dplyr::rename(
Componente2 = PC2,
MagnitudComponente2 = MagnitudPC2
)
tabla_html(
topPc1,
"Variables con mayor peso absoluto en el Componente 1",
digitos = 3
)| Variable | Componente1 | MagnitudComponente1 |
|---|---|---|
| Banios | 0.496 | 0.496 |
| AreaConstruida | 0.484 | 0.484 |
| Precio | 0.479 | 0.479 |
| Variable | Componente2 | MagnitudComponente2 |
|---|---|---|
| Habitaciones | -0.754 | 0.754 |
| Parqueaderos | 0.520 | 0.520 |
| Precio | 0.360 | 0.360 |
La representación de los individuos en el plano CP1-CP2 permite reconocer patrones, gradientes y posibles agrupaciones. El estrato se incorpora únicamente como variable descriptiva externa: no interviene en el cálculo de los componentes.
scoresPca <- as.data.frame(modeloPca$x) |>
dplyr::mutate(
Estrato = factor(datos_modelo$estrato, ordered = TRUE)
)
ggplot(
scoresPca,
aes(x = PC1, y = PC2, color = Estrato)
) +
geom_point(alpha = 0.55, size = 1.4) +
geom_hline(yintercept = 0, linetype = 2) +
geom_vline(xintercept = 0, linetype = 2) +
labs(
title = "Inmuebles en el plano de los dos primeros componentes",
subtitle = "El estrato se utiliza únicamente para caracterizar la distribución de los individuos",
x = paste0("CP1 (", round(varianzaPca[1], 1), "%)"),
y = paste0("CP2 (", round(varianzaPca[2], 1), "%)"),
color = "Estrato"
) +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")Siguiendo la lógica del ejemplo desarrollado en el curso, se revisan los inmuebles ubicados en los extremos de los dos primeros componentes. Esta comparación ayuda a interpretar qué características originales explican las posiciones más alejadas del centro.
indicesExtremosPca <- unique(c(
order(modeloPca$x[, 1])[1:3],
order(modeloPca$x[, 1], decreasing = TRUE)[1:3],
order(modeloPca$x[, 2])[1:3],
order(modeloPca$x[, 2], decreasing = TRUE)[1:3]
))
extremosPca <- datos_completos[indicesExtremosPca, ] |>
dplyr::mutate(
CP1 = modeloPca$x[indicesExtremosPca, 1],
CP2 = modeloPca$x[indicesExtremosPca, 2]
) |>
dplyr::transmute(
Id = id,
Zona = zona,
Barrio = barrio,
Tipo = tipo,
Estrato = estrato,
Precio = preciom,
AreaConstruida = areaconst,
Habitaciones = habitaciones,
Banios = banios,
Parqueaderos = parqueaderos,
CP1 = CP1,
CP2 = CP2
) |>
dplyr::arrange(CP1)
tabla_html(
extremosPca,
"Inmuebles extremos en los dos primeros componentes",
digitos = 2
)| Id | Zona | Barrio | Tipo | Estrato | Precio | AreaConstruida | Habitaciones | Banios | Parqueaderos | CP1 | CP2 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 7011 | Zona Sur | el refugio | Apartamento | 4 | 160 | 76 | 0 | 0 | 0 | -3.17 | 1.34 |
| 7522 | Zona Sur | meléndez | Apartamento | 3 | 148 | 87 | 0 | 0 | 0 | -3.15 | 1.32 |
| 69 | Zona Oriente | jose manuel marroquín | Casa | 3 | 165 | 80 | 0 | 0 | 0 | -3.15 | 1.35 |
| 5908 | Zona Sur | el limonar | Apartamento | 5 | 950 | 280 | 0 | 0 | 10 | 1.95 | 6.27 |
| 6811 | Zona Sur | urbanización tequendama | Casa | 5 | 1650 | 600 | 0 | 0 | 6 | 2.74 | 5.17 |
| 5741 | Zona Sur | el cedro | Casa | 4 | 500 | 500 | 10 | 7 | 0 | 3.60 | -4.47 |
| 4597 | Zona Sur | bretaña | Casa | 3 | 470 | 366 | 10 | 10 | 0 | 4.15 | -4.75 |
| 4508 | Zona Centro | alameda | Casa | 4 | 610 | 750 | 10 | 8 | 0 | 4.96 | -4.61 |
| 7505 | Zona Sur | pance | Casa | 6 | 1600 | 330 | 3 | 5 | 10 | 5.53 | 4.85 |
| 3652 | Zona Sur | pance | Casa | 6 | 1900 | 850 | 6 | 7 | 8 | 8.49 | 2.27 |
| 5396 | Zona Sur | parcelaciones pance | Casa | 6 | 1600 | 1600 | 6 | 6 | 3 | 8.60 | -0.49 |
| 5684 | Zona Sur | ciudad jardín | Casa | 6 | 1800 | 1586 | 5 | 4 | 10 | 10.21 | 3.40 |
variablePrincipalPc1 <- topPc1$Variable[1]
variablePrincipalPc2 <- topPc2$Variable[1]
cat(
paste0(
"El primer componente está principalmente caracterizado por **",
variablePrincipalPc1,
"**, mientras que el segundo presenta su mayor peso absoluto en **",
variablePrincipalPc2,
"**. Los dos primeros componentes explican conjuntamente **",
round(sum(varianzaPca[1:2]), 1),
"%** de la variabilidad total. "
)
)El primer componente está principalmente caracterizado por Banios, mientras que el segundo presenta su mayor peso absoluto en Habitaciones. Los dos primeros componentes explican conjuntamente 80.9% de la variabilidad total.
if (sum(varianzaPca[1:2]) >= 70) {
cat(
"La representación bidimensional conserva una proporción alta ",
"de la información y resulta adecuada para sintetizar visualmente ",
"la estructura principal de los datos."
)
} else if (sum(varianzaPca[1:2]) >= 50) {
cat(
"La representación bidimensional es útil para explorar la ",
"estructura principal, aunque una parte relevante de la ",
"variabilidad queda contenida en componentes posteriores."
)
} else {
cat(
"La representación bidimensional debe interpretarse con cautela, ",
"porque conserva menos de la mitad de la variabilidad total."
)
}La representación bidimensional conserva una proporción alta de la información y resulta adecuada para sintetizar visualmente la estructura principal de los datos. El ACP no debería interpretarse únicamente como un mecanismo para reducir cinco variables a un número menor de componentes. Su principal aporte en este análisis consiste en revelar que las características físicas y económicas de los inmuebles no varían de manera completamente independiente, sino que existe una estructura común. Precio, área construida, número de baños, habitaciones y parqueaderos describen diferentes atributos de una vivienda, pero parte de su variabilidad responde a una misma lógica: los inmuebles de mayor dimensión o dotación tienden a presentar simultáneamente valores mayores en varias de estas características. En consecuencia, los primeros componentes condensan un patrón general de escala, capacidad y valor del inmueble, mientras que los componentes posteriores recogen diferencias más específicas entre viviendas que no quedan explicadas por ese patrón dominante.
Desde una perspectiva inmobiliaria, el primer componente puede interpretarse como una medida general de las características de una vivienda, teniendo en cuenta aspectos como su tamaño, precio y dotación. Esto permite identificar viviendas que, en conjunto, presentan características superiores o inferiores al promedio de la muestra. El segundo componente aporta una diferencia adicional, ya que permite distinguir viviendas que pueden ser similares en términos generales, pero que tienen diferentes combinaciones de espacio, dotación y precio. De esta manera, el ACP permite encontrar patrones entre varias características que serían más difíciles de observar al analizarlas por separado.
También es importante no interpretar la reducción dimensional como una eliminación de variables “poco importantes”. Los componentes son combinaciones lineales de todas las variables consideradas; por tanto, reducir dimensiones significa representar una proporción importante de variabilidad mediante nuevos ejes sintéticos, no demostrar que las variables originales restantes carezcan de valor. La información que queda en componentes posteriores representa variabilidad más específica y, dependiendo del objetivo analítico, podría seguir siendo relevante.
Finalmente, la utilidad del ACP en este problema no se limita a producir una visualización bidimensional. Su resultado permite concluir que la oferta inmobiliaria posee una estructura continua: existen inmuebles que se desplazan gradualmente desde configuraciones más pequeñas y menos dotadas hacia configuraciones de mayor tamaño, equipamiento y valor. Esto será importante al interpretar posteriormente K-Means, porque los conglomerados no necesariamente representan poblaciones naturalmente separadas, sino segmentaciones discretas de una estructura que en parte es continua.
El análisis de conglomerados busca identificar grupos de inmuebles homogéneos internamente y diferentes entre sí. En este informe se utiliza K-Means, un procedimiento no jerárquico que asigna cada observación al centroide más cercano y actualiza iterativamente los centroides con el objetivo de reducir la suma de cuadrados dentro de los conglomerados.
K-Means trabaja con distancia euclidiana y es sensible a las
diferencias de escala. Por ello se utilizan las mismas cinco variables
cuantitativas del ACP y se estandarizan antes del agrupamiento.
Estrato queda fuera del algoritmo porque es ordinal;
posteriormente se emplea para caracterizar los segmentos obtenidos.
datosKmeans <- datosPca
datosKmeansZ <- scale(datosKmeans)
verificacionKmeans <- tibble(
Variable = colnames(datosKmeansZ),
MediaEstandarizada = apply(datosKmeansZ, 2, mean),
DesviacionEstandarizada = apply(datosKmeansZ, 2, sd)
)
tabla_html(
verificacionKmeans,
"Verificación de la estandarización para K-Means",
digitos = 4
)| Variable | MediaEstandarizada | DesviacionEstandarizada |
|---|---|---|
| Precio | 0 | 1 |
| AreaConstruida | 0 | 1 |
| Parqueaderos | 0 | 1 |
| Banios | 0 | 1 |
| Habitaciones | 0 | 1 |
Se utiliza algorithm = "Hartigan-Wong", que corresponde
al algoritmo predeterminado de stats::kmeans() en R. La
documentación oficial de R señala además que múltiples arranques
aleatorios mediante nstart > 1 suelen ser recomendables.
Como K-Means puede converger a soluciones diferentes según la
inicialización de los centroides, en este análisis se utiliza
nstart = 50. Cada ajuste conserva la solución con menor
suma de cuadrados dentro de los conglomerados entre los arranques
evaluados. También se fija una semilla para que el resultado pueda
reproducirse.
El parámetro iter.max = 100 establece un límite
suficientemente amplio para las iteraciones del algoritmo. La estrategia
adoptada en R consiste en combinar el algoritmo Hartigan-Wong con
múltiples inicializaciones aleatorias.
Para cada valor de k se calcula la suma total de
cuadrados dentro de los conglomerados (TotWithinSS). A
medida que aumenta el número de grupos, esta cantidad disminuye. El
método del codo busca el punto a partir del cual añadir nuevos
conglomerados produce una reducción marginal cada vez menor.
ajustesKmeans <- vector("list", kMaximo)
wssKmeans <- numeric(kMaximo)
for (k in seq_len(kMaximo)) {
set.seed(semillaKmeans + k)
ajustesKmeans[[k]] <- kmeans(
datosKmeansZ,
centers = k,
nstart = nstartKmeans,
iter.max = iterMaxKmeans,
algorithm = "Hartigan-Wong"
)
wssKmeans[k] <- ajustesKmeans[[k]]$tot.withinss
}
tablaCodo <- tibble(
K = seq_len(kMaximo),
TotWithinSS = wssKmeans
)
tabla_html(
tablaCodo,
"Suma de cuadrados dentro de los conglomerados para cada K",
digitos = 2
)| K | TotWithinSS |
|---|---|
| 1 | 41590.00 |
| 2 | 24256.33 |
| 3 | 19299.38 |
| 4 | 16114.85 |
| 5 | 14506.98 |
| 6 | 13467.35 |
| 7 | 12581.08 |
| 8 | 11891.54 |
| 9 | 11295.50 |
| 10 | 10714.08 |
ggplot(tablaCodo, aes(x = K, y = TotWithinSS)) +
geom_line(linewidth = 0.8) +
geom_point(size = 2.4) +
scale_x_continuous(breaks = tablaCodo$K) +
labs(
title = "Método del codo para seleccionar el número de conglomerados",
subtitle = "La reducción de la variabilidad interna se evalúa para K entre 1 y 10",
x = "Número de Conglomerados K",
y = "Suma de Cuadrados Dentro de los Conglomerados"
) +
theme_minimal(base_size = 12)Para complementar la inspección visual del codo se calcula una
aproximación discreta de la segunda derivada. Para cada valor interior
de k, la segunda diferencia compara el cambio de pendiente
de la curva de WSS. Un valor alto indica una curvatura más marcada y,
por tanto, un posible punto de codo.
segundaDiferencia <- rep(NA_real_, kMaximo)
for (k in 2:(kMaximo - 1)) {
segundaDiferencia[k] <-
wssKmeans[k + 1] - 2 * wssKmeans[k] + wssKmeans[k - 1]
}
kOptimo <- which.max(replace(segundaDiferencia, is.na(segundaDiferencia), -Inf))
tablaCurvatura <- tibble(
K = seq_len(kMaximo),
TotWithinSS = wssKmeans,
SegundaDiferencia = segundaDiferencia,
Seleccionado = ifelse(seq_len(kMaximo) == kOptimo, "Sí", "No")
)
tabla_html(
tablaCurvatura,
"Evaluación del codo mediante segunda diferencia",
digitos = 3
)| K | TotWithinSS | SegundaDiferencia | Seleccionado |
|---|---|---|---|
| 1 | 41590.00 |
|
No |
| 2 | 24256.33 | 12376.722 | Sí |
| 3 | 19299.38 | 1772.415 | No |
| 4 | 16114.85 | 1576.668 | No |
| 5 | 14506.98 | 568.228 | No |
| 6 | 13467.35 | 153.372 | No |
| 7 | 12581.08 | 196.723 | No |
| 8 | 11891.54 | 93.507 | No |
| 9 | 11295.50 | 14.613 | No |
| 10 | 10714.08 |
|
No |
ggplot(
tablaCurvatura |> dplyr::filter(!is.na(SegundaDiferencia)),
aes(x = K, y = SegundaDiferencia)
) +
geom_line(linewidth = 0.8) +
geom_point(size = 2.4) +
geom_vline(xintercept = kOptimo, linetype = 2) +
annotate(
"text",
x = kOptimo,
y = max(segundaDiferencia, na.rm = TRUE),
label = paste0("K = ", kOptimo),
vjust = -0.8
) +
scale_x_continuous(breaks = 2:(kMaximo - 1)) +
labs(
title = "Segunda diferencia de la curva del codo",
subtitle = "El máximo cambio de curvatura se utiliza como criterio reproducible de selección",
x = "Número de Conglomerados K",
y = "Segunda Diferencia"
) +
theme_minimal(base_size = 12)ggplot(tablaCodo, aes(x = K, y = TotWithinSS)) +
geom_line(linewidth = 0.8) +
geom_point(size = 2.4) +
geom_vline(xintercept = kOptimo, linetype = 2) +
geom_point(
data = tablaCodo |> dplyr::filter(K == kOptimo),
size = 4
) +
annotate(
"text",
x = kOptimo,
y = tablaCodo$TotWithinSS[kOptimo],
label = paste0("K seleccionado = ", kOptimo),
hjust = -0.08,
vjust = -1
) +
scale_x_continuous(breaks = tablaCodo$K) +
labs(
title = "Selección del número de conglomerados",
subtitle = "El codo se identifica mediante la máxima segunda diferencia",
x = "Número de Conglomerados K",
y = "Suma de Cuadrados Dentro de los Conglomerados"
) +
theme_minimal(base_size = 12)El coeficiente de silueta se utiliza como una comprobación complementaria. Valores más altos indican que, en promedio, las observaciones están mejor cohesionadas con su propio conglomerado y más separadas de los demás. Este criterio no reemplaza la selección mediante codo y segunda diferencia; permite valorar si la partición propuesta presenta una estructura interna razonable.
distanciaKmeans <- dist(datosKmeansZ, method = "euclidean")
kEvaluables <- 2:kMaximo
silhouettePromedio <- numeric(length(kEvaluables))
for (i in seq_along(kEvaluables)) {
k <- kEvaluables[i]
silhouettePromedio[i] <- mean(
cluster::silhouette(
ajustesKmeans[[k]]$cluster,
distanciaKmeans
)[, "sil_width"]
)
}
tablaSilhouette <- tibble(
K = kEvaluables,
SilhouettePromedio = silhouettePromedio
)
kMejorSilhouette <- tablaSilhouette$K[
which.max(tablaSilhouette$SilhouettePromedio)
]
tabla_html(
tablaSilhouette,
"Coeficiente de silueta promedio para cada K",
digitos = 3
)| K | SilhouettePromedio |
|---|---|
| 2 | 0.457 |
| 3 | 0.379 |
| 4 | 0.350 |
| 5 | 0.326 |
| 6 | 0.256 |
| 7 | 0.259 |
| 8 | 0.261 |
| 9 | 0.257 |
| 10 | 0.278 |
ggplot(tablaSilhouette, aes(x = K, y = SilhouettePromedio)) +
geom_line(linewidth = 0.8) +
geom_point(size = 2.4) +
geom_vline(xintercept = kOptimo, linetype = 2) +
scale_x_continuous(breaks = tablaSilhouette$K) +
labs(
title = "Coeficiente de silueta por número de conglomerados",
subtitle = paste0(
"La línea vertical corresponde al K seleccionado por codo y segunda diferencia: ",
kOptimo
),
x = "Número de Conglomerados K",
y = "Silueta Promedio"
) +
theme_minimal(base_size = 12)cat(
paste0(
"El método del codo complementado con la segunda diferencia selecciona **K = ",
kOptimo,
"**. El mayor coeficiente de silueta se observa en **K = ",
kMejorSilhouette,
"**. "
)
)El método del codo complementado con la segunda diferencia selecciona K = 2. El mayor coeficiente de silueta se observa en K = 2.
if (kOptimo == kMejorSilhouette) {
cat("Ambos criterios coinciden, lo que fortalece la evidencia a favor de esta partición.")
} else {
cat("Los criterios no coinciden exactamente. Se conserva K definido por el criterio principal del estudio —codo y segunda diferencia— y la silueta se utiliza como elemento adicional para valorar la calidad de la solución.")
}Ambos criterios coinciden, lo que fortalece la evidencia a favor de esta partición.
modeloKmeans <- ajustesKmeans[[kOptimo]]
resultadoKmeans <- datos_completos |>
dplyr::mutate(
Cluster = factor(modeloKmeans$cluster),
EstratoFactor = factor(estrato, ordered = TRUE)
)calidadKmeans <- tibble(
Indicador = c(
"Número de conglomerados",
"Suma de cuadrados dentro de conglomerados",
"Suma de cuadrados entre conglomerados",
"Proporción de variabilidad entre conglomerados",
"Silueta promedio del K seleccionado"
),
Valor = c(
kOptimo,
modeloKmeans$tot.withinss,
modeloKmeans$betweenss,
modeloKmeans$betweenss / modeloKmeans$totss,
tablaSilhouette$SilhouettePromedio[tablaSilhouette$K == kOptimo]
)
)
tabla_html(
calidadKmeans,
"Indicadores del modelo K-Means seleccionado",
digitos = 3
)| Indicador | Valor |
|---|---|
| Número de conglomerados | 2.000 |
| Suma de cuadrados dentro de conglomerados | 24256.330 |
| Suma de cuadrados entre conglomerados | 17333.670 |
| Proporción de variabilidad entre conglomerados | 0.417 |
| Silueta promedio del K seleccionado | 0.457 |
Los centroides estandarizados muestran la posición relativa de cada grupo respecto al promedio general. Valores positivos indican niveles superiores al promedio y valores negativos indican niveles inferiores.
centroidesKmeans <- as.data.frame(modeloKmeans$centers) |>
tibble::rownames_to_column("Cluster")
tabla_html(
centroidesKmeans,
"Centroides estandarizados de los conglomerados",
digitos = 2
)| Cluster | Precio | AreaConstruida | Parqueaderos | Banios | Habitaciones |
|---|---|---|---|---|---|
| 1 | -0.45 | -0.45 | -0.34 | -0.48 | -0.36 |
| 2 | 1.06 | 1.05 | 0.81 | 1.14 | 0.84 |
tamanosKmeans <- resultadoKmeans |>
dplyr::count(Cluster, name = "Cantidad") |>
dplyr::mutate(
Porcentaje = Cantidad / sum(Cantidad) * 100
)
tabla_html(
tamanosKmeans,
"Tamaño de los conglomerados",
digitos = 2
)| Cluster | Cantidad | Porcentaje |
|---|---|---|
| 1 | 5835 | 70.14 |
| 2 | 2484 | 29.86 |
Para facilitar la interpretación empresarial, los conglomerados se describen en las unidades originales de las variables y no solamente mediante puntuaciones estandarizadas.
perfilKmeans <- resultadoKmeans |>
dplyr::group_by(Cluster) |>
dplyr::summarise(
Cantidad = dplyr::n(),
PrecioMediano = median(preciom),
PrecioPromedio = mean(preciom),
AreaConstruidaMediana = median(areaconst),
ParqueaderosPromedio = mean(parqueaderos),
BaniosPromedio = mean(banios),
HabitacionesPromedio = mean(habitaciones),
.groups = "drop"
)
tabla_html(
perfilKmeans,
"Perfil de los conglomerados en unidades originales",
digitos = 2
)| Cluster | Cantidad | PrecioMediano | PrecioPromedio | AreaConstruidaMediana | ParqueaderosPromedio | BaniosPromedio | HabitacionesPromedio |
|---|---|---|---|---|---|---|---|
| 1 | 5835 | 260 | 285.10 | 93 | 1.05 | 2.42 | 3.08 |
| 2 | 2484 | 695 | 783.44 | 296 | 2.49 | 4.74 | 4.84 |
El estrato no interviene en la construcción de los conglomerados. Después de obtener los grupos, se analiza su distribución para determinar si los segmentos presentan perfiles socioeconómicos diferenciados.
tablaEstratoCluster <- resultadoKmeans |>
dplyr::count(Cluster, EstratoFactor, name = "Cantidad") |>
dplyr::group_by(Cluster) |>
dplyr::mutate(
Porcentaje = Cantidad / sum(Cantidad) * 100
) |>
dplyr::ungroup() |>
dplyr::rename(Estrato = EstratoFactor)
tabla_html(
tablaEstratoCluster,
"Distribución del estrato dentro de cada conglomerado",
digitos = 2
)| Cluster | Estrato | Cantidad | Porcentaje |
|---|---|---|---|
| 1 | 3 | 1206 | 20.67 |
| 1 | 4 | 1835 | 31.45 |
| 1 | 5 | 2037 | 34.91 |
| 1 | 6 | 757 | 12.97 |
| 2 | 3 | 247 | 9.94 |
| 2 | 4 | 294 | 11.84 |
| 2 | 5 | 713 | 28.70 |
| 2 | 6 | 1230 | 49.52 |
ggplot(
tablaEstratoCluster,
aes(x = Cluster, y = Cantidad, fill = Estrato)
) +
geom_col(position = "fill") +
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Composición de estrato por conglomerado",
x = "Conglomerado",
y = "Participación dentro del conglomerado",
fill = "Estrato"
) +
theme_minimal(base_size = 12)K-Means se calcula utilizando las cinco variables estandarizadas. El plano CP1-CP2 se emplea únicamente como recurso de visualización para observar la separación de los grupos en dos dimensiones.
scoresCluster <- as.data.frame(modeloPca$x[, 1:2]) |>
dplyr::mutate(Cluster = factor(modeloKmeans$cluster))
centrosVisuales <- scoresCluster |>
dplyr::group_by(Cluster) |>
dplyr::summarise(
PC1 = mean(PC1),
PC2 = mean(PC2),
.groups = "drop"
)
ggplot(scoresCluster, aes(x = PC1, y = PC2, color = Cluster)) +
geom_point(alpha = 0.5, size = 1.4) +
geom_point(
data = centrosVisuales,
aes(x = PC1, y = PC2),
size = 5,
shape = 4,
stroke = 1.4
) +
labs(
title = "Conglomerados K-Means representados sobre el plano del ACP",
subtitle = "Los grupos se calcularon en cinco dimensiones estandarizadas; CP1 y CP2 se usan solo para visualización",
x = paste0("CP1 (", round(varianzaPca[1], 1), "%)"),
y = paste0("CP2 (", round(varianzaPca[2], 1), "%)"),
color = "Conglomerado"
) +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")clusterMayorPrecio <- perfilKmeans$Cluster[which.max(perfilKmeans$PrecioMediano)]
clusterMayorArea <- perfilKmeans$Cluster[which.max(perfilKmeans$AreaConstruidaMediana)]
clusterMenorPrecio <- perfilKmeans$Cluster[which.min(perfilKmeans$PrecioMediano)]
cat(
paste0(
"El conglomerado **", clusterMayorPrecio,
"** presenta el mayor precio mediano, mientras que el conglomerado **",
clusterMenorPrecio,
"** presenta el menor. El conglomerado **",
clusterMayorArea,
"** concentra la mayor área construida mediana. "
)
)El conglomerado 2 presenta el mayor precio mediano, mientras que el conglomerado 1 presenta el menor. El conglomerado 2 concentra la mayor área construida mediana.
cat(
"Estas diferencias permiten interpretar los conglomerados como perfiles de oferta inmobiliaria y no como categorías previamente conocidas. La distribución posterior del estrato aporta contexto socioeconómico sin haber condicionado la formación de los grupos."
)Estas diferencias permiten interpretar los conglomerados como perfiles de oferta inmobiliaria y no como categorías previamente conocidas. La distribución posterior del estrato aporta contexto socioeconómico sin haber condicionado la formación de los grupos. El análisis de conglomerados permite pasar de la estructura continua identificada mediante ACP a una segmentación operativa de la oferta inmobiliaria. Los grupos obtenidos por K-Means no deben entenderse como categorías naturales o previamente existentes en el mercado, sino como perfiles construidos a partir de similitudes multivariadas en precio, área, habitaciones, baños y parqueaderos. Esta distinción es fundamental: el algoritmo no “descubre tipos oficiales de vivienda”, sino regiones del espacio de características donde determinados inmuebles resultan más semejantes entre sí que respecto a los demás grupos.
La solución obtenida muestra que la segmentación está muy relacionada con la escala económica y física de los inmuebles. En particular, el conglomerado 2 reúne simultáneamente el mayor precio mediano y la mayor área construida mediana, mientras que el conglomerado 1 se encuentra en el extremo inferior del precio. Esta correspondencia entre precio y tamaño sugiere que una parte importante de la estructura de los conglomerados reproduce el gradiente previamente identificado mediante ACP. La coincidencia entre dos métodos con objetivos diferentes fortalece la interpretación: el ACP identifica la dirección dominante de variabilidad y K-Means permite ver perfiles diferenciados.
Sin embargo, el valor del agrupamiento no consiste únicamente en ordenar viviendas de “menor a mayor”. Debe observarse la configuración conjunta de las demás variables dentro de cada conglomerado. Dos segmentos pueden diferir no solamente por precio, sino por la manera en que ese precio se acompaña de área, baños, habitaciones y parqueaderos. Esto permite distinguir entre tamaño, dotación y posicionamiento económico, evitando reducir el mercado a una clasificación exclusivamente monetaria. Desde esta perspectiva, los centroides y las medianas deben interpretarse como perfiles resultantes de la combinación de varias variales y no como simples rankings de precio.
También es importante reconocer una limitación inherente al método. K-Means obliga a cada inmueble a pertenecer a un conglomerado incluso cuando se encuentra próximo a la frontera entre dos grupos. Por ello, la existencia de conglomerados no implica necesariamente separaciones absolutas en el mercado. Si el ACP muestra continuidad o superposición entre observaciones, los clusters deben interpretarse como una simplificación útil, no como segmentos completamente independientes.
La incorporación posterior del estrato adquiere especial valor analítico precisamente porque esta variable no participó en la formación de los conglomerados. Si posteriormente aparecen diferencias en su distribución entre clusters, estas no fueron impuestas por el algoritmo, sino que emergen como una característica asociada con los perfiles encontrados. Esto permite evaluar hasta qué punto una segmentación construida exclusivamente con características físicas y económicas reproduce también patrones socioeconómicos del mercado inmobiliario.
El análisis de correspondencias se utiliza para representar
asociaciones entre categorías de variables cualitativas. En esta etapa
estrato se trata explícitamente como una variable
categórica ordinal. Se estudian dos relaciones con sentido inmobiliario
y consistentes con la estructura de la base:
Zona-Estrato y Tipo de
Vivienda-Estrato.
No se utiliza barrio como variable principal del análisis de correspondencias porque presenta un número elevado de categorías, lo que produciría una tabla muy dispersa y una representación factorial difícil de interpretar.
datosCorrespondenciaZona <- inmuebles_imp |>
dplyr::filter(!is.na(estrato), !is.na(zona)) |>
dplyr::mutate(
Estrato = factor(
estrato,
levels = sort(unique(estrato)),
ordered = TRUE
),
Zona = factor(zona)
)
datosCorrespondenciaTipo <- inmuebles_imp |>
dplyr::filter(!is.na(estrato), !is.na(tipo)) |>
dplyr::mutate(
Estrato = factor(
estrato,
levels = sort(unique(estrato)),
ordered = TRUE
),
TipoVivienda = factor(tipo)
)tablaZonaEstrato <- table(
datosCorrespondenciaZona$Zona,
datosCorrespondenciaZona$Estrato
)
colnames(tablaZonaEstrato) <- paste0("Estrato", colnames(tablaZonaEstrato))
as.data.frame.matrix(tablaZonaEstrato) |>
tibble::rownames_to_column("Zona") |>
tabla_html(
"Tabla de contingencia entre Zona y Estrato",
digitos = 0
)| Zona | Estrato3 | Estrato4 | Estrato5 | Estrato6 |
|---|---|---|---|---|
| Zona Centro | 105 | 14 | 4 | 1 |
| Zona Norte | 572 | 407 | 769 | 172 |
| Zona Oeste | 54 | 84 | 290 | 770 |
| Zona Oriente | 340 | 8 | 2 | 1 |
| Zona Sur | 382 | 1616 | 1685 | 1043 |
chiZonaEstrato <- chisq.test(tablaZonaEstrato)
resumenChiZonaEstrato <- tibble(
ChiCuadrado = as.numeric(chiZonaEstrato$statistic),
GradosLibertad = as.numeric(chiZonaEstrato$parameter),
ValorP = chiZonaEstrato$p.value,
PorcentajeEsperadasMenor5 = mean(chiZonaEstrato$expected < 5) * 100
)
tabla_html(
resumenChiZonaEstrato,
"Prueba Chi-cuadrado entre Zona y Estrato",
digitos = 5
)| ChiCuadrado | GradosLibertad | ValorP | PorcentajeEsperadasMenor5 |
|---|---|---|---|
| 3830.435 | 12 | 0 | 0 |
if (chiZonaEstrato$p.value < 0.05) {
cat("El p-valor es inferior a 0,05, por lo que se rechaza la hipótesis de independencia. Existe evidencia estadística de asociación entre la zona y el estrato de los inmuebles.")
} else {
cat("El p-valor no es inferior a 0,05, por lo que no existe evidencia suficiente para rechazar la hipótesis de independencia entre zona y estrato.")
}El p-valor es inferior a 0,05, por lo que se rechaza la hipótesis de independencia. Existe evidencia estadística de asociación entre la zona y el estrato de los inmuebles.
Los residuos estandarizados permiten identificar las combinaciones de categorías que aparecen con una frecuencia mayor o menor a la esperada bajo independencia. Valores absolutos altos muestran las celdas que más contribuyen a la asociación.
residuosZonaEstrato <- as.data.frame(as.table(chiZonaEstrato$stdres)) |>
dplyr::rename(
Zona = Var1,
Estrato = Var2,
ResiduoEstandarizado = Freq
) |>
dplyr::mutate(Magnitud = abs(ResiduoEstandarizado)) |>
dplyr::arrange(desc(Magnitud))
tabla_html(
head(residuosZonaEstrato, 12),
"Mayores residuos estandarizados entre Zona y Estrato",
digitos = 3
)| Zona | Estrato | ResiduoEstandarizado | Magnitud |
|---|---|---|---|
| Zona Oriente | Estrato3 | 40.033 | 40.033 |
| Zona Oeste | Estrato6 | 35.437 | 35.437 |
| Zona Sur | Estrato3 | -25.852 | 25.852 |
| Zona Sur | Estrato4 | 20.620 | 20.620 |
| Zona Centro | Estrato3 | 19.861 | 19.861 |
| Zona Norte | Estrato6 | -17.490 | 17.490 |
| Zona Norte | Estrato3 | 16.219 | 16.219 |
| Zona Oeste | Estrato4 | -15.929 | 15.929 |
| Zona Oriente | Estrato5 | -13.220 | 13.220 |
| Zona Oeste | Estrato3 | -12.768 | 12.768 |
| Zona Oriente | Estrato6 | -10.596 | 10.596 |
| Zona Oriente | Estrato4 | -10.227 | 10.227 |
ggplot(
residuosZonaEstrato,
aes(x = Estrato, y = Zona, fill = ResiduoEstandarizado)
) +
geom_tile() +
geom_text(aes(label = round(ResiduoEstandarizado, 1)), size = 3.2) +
scale_fill_gradient2(midpoint = 0) +
labs(
title = "Residuos estandarizados entre Zona y Estrato",
x = "Estrato",
y = "Zona",
fill = "Residuo"
) +
theme_minimal(base_size = 12)factoextra::fviz_ca_biplot(
modeloCaZonaEstrato,
repel = TRUE
) +
labs(
title = "Mapa de correspondencias entre Zona y Estrato",
subtitle = "La proximidad relativa permite reconocer asociaciones entre categorías"
) +
theme_minimal(base_size = 12)inerciaZonaEstrato <- as.data.frame(modeloCaZonaEstrato$eig) |>
tibble::rownames_to_column("Dimension") |>
dplyr::rename(
Autovalor = eigenvalue,
PorcentajeInercia = `percentage of variance`,
PorcentajeAcumulado = `cumulative percentage of variance`
)
tabla_html(
inerciaZonaEstrato,
"Inercia explicada en la correspondencia Zona-Estrato",
digitos = 2
)| Dimension | Autovalor | PorcentajeInercia | PorcentajeAcumulado |
|---|---|---|---|
| dim 1 | 0.32 | 69.97 | 69.97 |
| dim 2 | 0.13 | 27.68 | 97.65 |
| dim 3 | 0.01 | 2.35 | 100.00 |
factoextra::fviz_screeplot(
modeloCaZonaEstrato,
addlabels = TRUE
) +
labs(
title = "Inercia explicada: Zona y Estrato",
x = "Dimensión",
y = "Porcentaje de Inercia Explicada"
) +
theme_minimal(base_size = 12)tablaTipoEstrato <- table(
datosCorrespondenciaTipo$TipoVivienda,
datosCorrespondenciaTipo$Estrato
)
colnames(tablaTipoEstrato) <- paste0("Estrato", colnames(tablaTipoEstrato))
as.data.frame.matrix(tablaTipoEstrato) |>
tibble::rownames_to_column("TipoVivienda") |>
tabla_html(
"Tabla de contingencia entre Tipo de Vivienda y Estrato",
digitos = 0
)| TipoVivienda | Estrato3 | Estrato4 | Estrato5 | Estrato6 |
|---|---|---|---|---|
| Apartamento | 639 | 1404 | 1766 | 1291 |
| Casa | 814 | 725 | 984 | 696 |
chiTipoEstrato <- chisq.test(tablaTipoEstrato)
resumenChiTipoEstrato <- tibble(
ChiCuadrado = as.numeric(chiTipoEstrato$statistic),
GradosLibertad = as.numeric(chiTipoEstrato$parameter),
ValorP = chiTipoEstrato$p.value,
PorcentajeEsperadasMenor5 = mean(chiTipoEstrato$expected < 5) * 100
)
tabla_html(
resumenChiTipoEstrato,
"Prueba Chi-cuadrado entre Tipo de Vivienda y Estrato",
digitos = 5
)| ChiCuadrado | GradosLibertad | ValorP | PorcentajeEsperadasMenor5 |
|---|---|---|---|
| 224.331 | 3 | 0 | 0 |
if (chiTipoEstrato$p.value < 0.05) {
cat("El p-valor es inferior a 0,05, por lo que se rechaza la hipótesis de independencia. Existe evidencia estadística de asociación entre el tipo de vivienda y el estrato.")
} else {
cat("El p-valor no es inferior a 0,05, por lo que no existe evidencia suficiente para rechazar la hipótesis de independencia entre tipo de vivienda y estrato.")
}El p-valor es inferior a 0,05, por lo que se rechaza la hipótesis de independencia. Existe evidencia estadística de asociación entre el tipo de vivienda y el estrato.
residuosTipoEstrato <- as.data.frame(as.table(chiTipoEstrato$stdres)) |>
dplyr::rename(
TipoVivienda = Var1,
Estrato = Var2,
ResiduoEstandarizado = Freq
) |>
dplyr::mutate(Magnitud = abs(ResiduoEstandarizado)) |>
dplyr::arrange(desc(Magnitud))
tabla_html(
head(residuosTipoEstrato, 12),
"Mayores residuos estandarizados entre Tipo de Vivienda y Estrato",
digitos = 3
)| TipoVivienda | Estrato | ResiduoEstandarizado | Magnitud |
|---|---|---|---|
| Apartamento | Estrato3 | -14.927 | 14.927 |
| Casa | Estrato3 | 14.927 | 14.927 |
| Casa | Estrato4 | -5.097 | 5.097 |
| Apartamento | Estrato4 | 5.097 | 5.097 |
| Apartamento | Estrato6 | 3.847 | 3.847 |
| Casa | Estrato6 | -3.847 | 3.847 |
| Apartamento | Estrato5 | 3.833 | 3.833 |
| Casa | Estrato5 | -3.833 | 3.833 |
ggplot(
residuosTipoEstrato,
aes(x = Estrato, y = TipoVivienda, fill = ResiduoEstandarizado)
) +
geom_tile() +
geom_text(aes(label = round(ResiduoEstandarizado, 1)), size = 3.2) +
scale_fill_gradient2(midpoint = 0) +
labs(
title = "Residuos estandarizados entre Tipo de Vivienda y Estrato",
x = "Estrato",
y = "Tipo de Vivienda",
fill = "Residuo"
) +
theme_minimal(base_size = 12)# Verificar que el objeto contenga coordenadas de filas y columnas
if (
!is.null(modeloCaTipoEstrato$row$coord) &&
!is.null(modeloCaTipoEstrato$col$coord)
) {
numeroDimensionesTipoEstrato <- ncol(
as.matrix(modeloCaTipoEstrato$row$coord)
)
if (numeroDimensionesTipoEstrato >= 2) {
factoextra::fviz_ca_biplot(
modeloCaTipoEstrato,
repel = TRUE
) +
labs(
title = "Mapa de Correspondencias entre Tipo de Vivienda y Estrato",
subtitle = paste0(
"Las dos primeras dimensiones representan las principales ",
"asociaciones entre las categorías"
)
) +
theme_minimal(base_size = 12)
} else {
coordenadasTipo <- as.data.frame(
modeloCaTipoEstrato$row$coord
) |>
tibble::rownames_to_column("Categoria")
coordenadasEstrato <- as.data.frame(
modeloCaTipoEstrato$col$coord
) |>
tibble::rownames_to_column("Categoria")
names(coordenadasTipo)[2] <- "Dimension1"
names(coordenadasEstrato)[2] <- "Dimension1"
coordenadasTipo <- coordenadasTipo |>
dplyr::mutate(
Origen = "Tipo de Vivienda"
) |>
dplyr::select(
Categoria,
Dimension1,
Origen
)
coordenadasEstrato <- coordenadasEstrato |>
dplyr::mutate(
Origen = "Estrato"
) |>
dplyr::select(
Categoria,
Dimension1,
Origen
)
coordenadasCaTipo <- dplyr::bind_rows(
coordenadasTipo,
coordenadasEstrato
)
ggplot(
coordenadasCaTipo,
aes(
x = Dimension1,
y = Origen,
label = Categoria,
shape = Origen
)
) +
geom_vline(
xintercept = 0,
linetype = 2
) +
geom_point(
size = 3
) +
geom_text(
vjust = -0.8,
check_overlap = TRUE
) +
labs(
title = "Correspondencia entre Tipo de Vivienda y Estrato",
subtitle = paste0(
"La tabla genera una sola dimensión factorial; ",
"las categorías se representan sobre el eje principal"
),
x = "Dimensión 1",
y = NULL,
shape = NULL
) +
theme_minimal(base_size = 12) +
theme(
legend.position = "none"
)
}
} else {
cat(
"No fue posible construir el mapa porque el objeto ",
"`modeloCaTipoEstrato` no contiene las coordenadas ",
"esperadas de un análisis de correspondencias de FactoMineR."
)
}inerciaTipoEstrato <- as.data.frame(modeloCaTipoEstrato$eig) |>
tibble::rownames_to_column("Dimension") |>
dplyr::rename(
Autovalor = eigenvalue,
PorcentajeInercia = `percentage of variance`,
PorcentajeAcumulado = `cumulative percentage of variance`
)
tabla_html(
inerciaTipoEstrato,
"Inercia explicada en la correspondencia Tipo de Vivienda-Estrato",
digitos = 2
)| Dimension | Autovalor | PorcentajeInercia | PorcentajeAcumulado |
|---|---|---|---|
| dim 1 | 0.03 | 100 | 100 |
factoextra::fviz_screeplot(
modeloCaTipoEstrato,
addlabels = TRUE
) +
labs(
title = "Inercia explicada: Tipo de Vivienda y Estrato",
x = "Dimensión",
y = "Porcentaje de Inercia Explicada"
) +
theme_minimal(base_size = 12)Zona y Estrato:
if (chiZonaEstrato$p.value < 0.05) {
cat("se identifica una asociación estadísticamente significativa. ")
} else {
cat("no se identifica evidencia estadística suficiente de asociación. ")
}se identifica una asociación estadísticamente significativa.
cat(
paste0(
"Las dos primeras dimensiones del análisis de correspondencias explican **",
round(sum(modeloCaZonaEstrato$eig[1:min(2, nrow(modeloCaZonaEstrato$eig)), 2]), 1),
"%** de la inercia.\n\n"
)
)Las dos primeras dimensiones del análisis de correspondencias explican 97.6% de la inercia.
Tipo de Vivienda y Estrato:
if (chiTipoEstrato$p.value < 0.05) {
cat("se identifica una asociación estadísticamente significativa. ")
} else {
cat("no se identifica evidencia estadística suficiente de asociación. ")
}se identifica una asociación estadísticamente significativa.
cat(
paste0(
"Las dos primeras dimensiones explican **",
round(sum(modeloCaTipoEstrato$eig[1:min(2, nrow(modeloCaTipoEstrato$eig)), 2]), 1),
"%** de la inercia de esta tabla."
)
)Las dos primeras dimensiones explican 100% de la inercia de esta tabla.
La interpretación de los mapas se realiza conjuntamente con la prueba Chi-cuadrado y los residuos estandarizados. La proximidad entre categorías en el plano factorial sugiere patrones de asociación, pero no debe interpretarse como causalidad.
El análisis de correspondencias complementa los métodos anteriores porque desplaza la atención desde las características cuantitativas del inmueble hacia la estructura de asociación entre categorías del mercado. Las pruebas de independencia muestran evidencia estadística muy fuerte de asociación tanto entre Zona y Estrato como entre Tipo de Vivienda y Estrato, con valores p<2×10 −16. Por tanto, la distribución observada de los estratos no puede considerarse independiente de la localización ni de la tipología de vivienda dentro de esta muestra.
No obstante, un p-valor extremadamente pequeño no indica por sí mismo que la asociación sea fuerte ni permite identificar qué categorías explican dicha relación. Con un tamaño de muestra suficiente, incluso desviaciones relativamente moderadas respecto de la independencia pueden producir significancia estadística. Por ello, la interpretación debe apoyarse en la inercia, las posiciones factoriales y especialmente los residuos estandarizados, que permiten determinar qué combinaciones aparecen con una frecuencia superior o inferior a la esperada bajo independencia. Esta distinción evita confundir evidencia de asociación con magnitud de asociación.
Zona × Estrato
En la relación Zona–Estrato, el resultado sugiere que la dimensión socioeconómica de la oferta inmobiliaria posee también una expresión territorial. Los estratos no se distribuyen aleatoriamente entre las zonas consideradas, lo que indica que la localización contiene información relacionada con la composición socioeconómica de la oferta inmobiliaria. Desde una perspectiva de mercado, esto implica que la ubicación no funciona únicamente como una coordenada geográfica, sino como un componente de la estructura de segmentación.
Sin embargo, la proximidad entre una zona y un estrato en el mapa factorial debe interpretarse como una asociación relativa dentro de la tabla de contingencia y no como equivalencia, causalidad o exclusividad. Una zona próxima a determinado estrato no significa que todos sus inmuebles pertenezcan a él; significa que esa combinación contribuye de manera particular a diferenciarse del patrón esperado bajo independencia.
Tipo de vivienda × Estrato
La asociación entre Tipo de Vivienda y Estrato añade una segunda dimensión a esta estructura. El resultado indica que las tipologías residenciales tampoco se distribuyen de manera homogénea entre los niveles socioeconómicos. En consecuencia, el estrato no parece estar relacionado únicamente con el valor o la localización, sino también con la forma que adopta la oferta habitacional.
La lectura conjunta de ambas correspondencias resulta bien interesante: si el estrato presenta asociaciones tanto con la zona como con el tipo de vivienda, entonces la diferenciación socioeconómica observada en la base parece estar articulada simultáneamente con territorio y tipología residencial. Esto revela una estructura más compleja que una simple relación “mayor estrato = mayor precio”: el mercado observado presenta una organización multidimensional donde ubicación, características físicas, tipología y contexto socioeconómico se encuentran relacionados.
Estas asociaciones son descriptivas y no causales. Los resultados permiten afirmar que las categorías aparecen relacionadas en la muestra analizada, pero no que la zona cause determinado estrato ni que el estrato determine el tipo de vivienda. Para formular explicaciones causales serían necesarios otros diseños y variables de control.
Las técnicas empleadas responden a preguntas diferentes y complementarias. El análisis de correlaciones describe asociaciones lineales o monotónicas entre pares de variables; el ACP resume la variabilidad conjunta de las características cuantitativas; K-Means construye segmentos de inmuebles a partir de similitudes multivariadas; y el análisis de correspondencias estudia la asociación entre categorías, utilizando el estrato en su naturaleza ordinal/categórica.
tablaIntegracion <- tibble::tribble(
~Tecnica, ~Pregunta, ~VariablesPrincipales, ~ResultadoEsperado,
"Correlaciones", "¿Qué variables se asocian?", "Variables cuantitativas y Estrato", "Fuerza y dirección de asociación",
"ACP", "¿Qué dimensiones resumen la variabilidad?", "Precio, AreaConstruida, Parqueaderos, Banios y Habitaciones", "Componentes principales",
"K-Means", "¿Qué perfiles homogéneos existen?", "Cinco variables cuantitativas estandarizadas", "Segmentos de inmuebles",
"Correspondencias", "¿Qué categorías aparecen asociadas?", "Zona-Estrato y Tipo de Vivienda-Estrato", "Mapas de asociación categórica"
)
tabla_html(
tablaIntegracion,
"Integración de las técnicas multivariadas utilizadas",
digitos = 2
)| Tecnica | Pregunta | VariablesPrincipales | ResultadoEsperado |
|---|---|---|---|
| Correlaciones | ¿Qué variables se asocian? | Variables cuantitativas y Estrato | Fuerza y dirección de asociación |
| ACP | ¿Qué dimensiones resumen la variabilidad? | Precio, AreaConstruida, Parqueaderos, Banios y Habitaciones | Componentes principales |
| K-Means | ¿Qué perfiles homogéneos existen? | Cinco variables cuantitativas estandarizadas | Segmentos de inmuebles |
| Correspondencias | ¿Qué categorías aparecen asociadas? | Zona-Estrato y Tipo de Vivienda-Estrato | Mapas de asociación categórica |
cat(
paste0(
"1. El ACP se construyó con cinco variables cuantitativas y excluyó `Estrato` como variable activa debido a su naturaleza ordinal. Los dos primeros componentes explican **",
round(sum(varianzaPca[1:2]), 1),
"%** de la variabilidad total, mientras que para alcanzar al menos el 80% se requieren **",
k80Pca,
" componentes**.\n\n"
)
)Estrato como variable activa debido a su naturaleza
ordinal. Los dos primeros componentes explican 80.9% de
la variabilidad total, mientras que para alcanzar al menos el 80% se
requieren 2 componentes.cat(
paste0(
"2. El análisis K-Means seleccionó **",
kOptimo,
" conglomerados** mediante el método del codo complementado con la segunda diferencia. Para esta solución, la silueta promedio fue **",
round(tablaSilhouette$SilhouettePromedio[tablaSilhouette$K == kOptimo], 3),
"**.\n\n"
)
)cat(
"3. El estrato no fue utilizado para construir los conglomerados, sino para caracterizarlos después de la segmentación, evitando imponerle una interpretación métrica dentro de la distancia euclidiana.\n\n"
)cat(
paste0(
"4. La prueba de independencia entre Zona y Estrato obtuvo un p-valor de **",
format.pval(chiZonaEstrato$p.value, digits = 3),
"**, mientras que la relación entre Tipo de Vivienda y Estrato obtuvo un p-valor de **",
format.pval(chiTipoEstrato$p.value, digits = 3),
"**. Estas pruebas orientan la interpretación posterior de los mapas de correspondencias.\n\n"
)
)cat(
"5. La combinación de ACP, K-Means y análisis de correspondencias permite estudiar la oferta inmobiliaria desde tres perspectivas distintas: reducción de dimensionalidad, segmentación de inmuebles y asociación entre categorías."
)Esto también permite entender por qué fue metodológicamente apropiado no introducir indiscriminadamente todas las variables en un único algoritmo. Las variables cuantitativas fueron estudiadas mediante ACP y K-Means, mientras que las cualitativas y ordinales se analizaron mediante correspondencias o se utilizaron posteriormente para caracterizar los segmentos. Esta separación respeta la naturaleza de las variables y evita imponer distancias métricas artificiales sobre categorías que no necesariamente las admiten.
La oferta inmobiliaria analizada presenta una estructura multivariada y no puede explicarse adecuadamente mediante el precio de forma aislada. El ACP evidencia que precio, área construida, habitaciones, baños y parqueaderos comparten patrones de variación, lo que permite interpretar una parte importante de la heterogeneidad de los inmuebles a partir de dimensiones latentes relacionadas con su escala física, dotación y posicionamiento económico. Esto demuestra el valor del análisis multivariado frente a una lectura basada exclusivamente en indicadores individuales.
La segmentación obtenida mediante K-Means transforma esa heterogeneidad continua en perfiles operativos de oferta. El hecho de que el conglomerado 2 concentre simultáneamente el mayor precio mediano y la mayor área construida mediana, mientras el conglomerado 1 presenta el menor precio mediano, evidencia una diferenciación económica y física entre los segmentos. Sin embargo, estos grupos deben entenderse como perfiles exploratorios dependientes de las variables, escalamiento y número de clusters seleccionados, y no como categorías universales del mercado.
Existe coherencia entre la estructura revelada por ACP y la segmentación obtenida mediante K-Means. Mientras el primero muestra los principales ejes sobre los cuales se diferencian los inmuebles, el segundo identifica agrupaciones dentro de ese mismo espacio multivariado. Esta convergencia aporta consistencia interna al análisis y sugiere que los conglomerados responden a patrones estructurales presentes en los datos, aunque su delimitación concreta continúe siendo una construcción algorítmica.
La dimensión socioeconómica y territorial amplía la interpretación de los perfiles cuantitativos. Las asociaciones estadísticamente significativas entre Zona–Estrato y Tipo de Vivienda–Estrato muestran que la estructura de la oferta no está determinada únicamente por las características internas del inmueble. La ubicación, la tipología residencial y el contexto socioeconómico aparecen relacionados, configurando un mercado donde las dimensiones físicas, económicas, territoriales y categóricas interactúan. No obstante, estas asociaciones deben entenderse en términos descriptivos y no causales.
El principal aprendizaje del ejercicio no radica en identificar un componente, un número de clusters o una asociación particular, sino en reconocer que diferentes técnicas multivariadas responden preguntas distintas y complementarias sobre un mismo fenómeno. El ACP sintetiza estructura, K-Means segmenta observaciones y el análisis de correspondencias identifica asociaciones categóricas. Integrados, permiten pasar de describir inmuebles individuales a comprender patrones globales de organización de la oferta. Desde una perspectiva de Ciencia de Datos, esta integración también muestra que la elección de una técnica debe estar determinada por la naturaleza de las variables y por la pregunta analítica, y no simplemente por la disponibilidad del algoritmo.
En síntesis, los resultados muestran que la oferta inmobiliaria estudiada puede entenderse como un sistema heterogéneo en el que valor, dimensión física, dotación, localización, tipología y contexto socioeconómico forman estructuras relacionadas pero no equivalentes. El ACP permitió reconocer los ejes principales de esa heterogeneidad; K-Means convirtió parte de ella en segmentos interpretables; y el análisis de correspondencias mostró que la dimensión categórica del mercado tampoco se distribuye de manera independiente. La principal fortaleza del análisis consiste, por tanto, en la convergencia de perspectivas: ninguna técnica explica por sí sola el fenómeno, pero juntas proporcionan una representación más completa de cómo se organiza la oferta inmobiliaria. Al mismo tiempo, los resultados deben mantenerse dentro de su alcance exploratorio: los conglomerados dependen de las decisiones adoptadas y las asociaciones encontradas no implican causalidad. Esta combinación entre capacidad interpretativa y reconocimiento explícito de las limitaciones constituye una lectura más sólida de los resultados desde la Ciencia de Datos.
Teniendo lo anterior para este caso de estudio, el mercado inmobiliario analizado se organiza como un continuo de perfiles que van desde inmuebles de menor escala física y económica hasta propiedades de mayor tamaño, dotación y precio. El PCA revela ese gradiente general; K-Means lo convierte en segmentos o perfiles concretos de inmuebles; y el análisis de correspondencias muestra que estos patrones económicos y físicos se insertan además en una estructura territorial y socioeconómica, donde zona, tipo de vivienda y estrato no se distribuyen de manera independiente.
frecuenciasCategoricas <- purrr::map_dfr(
c("zona", "piso", "estrato", "tipo", "barrio"),
function(v) {
inmuebles_imp |>
dplyr::count(.data[[v]], name = "Frecuencia", sort = TRUE) |>
dplyr::mutate(
Variable = v,
Categoria = as.character(.data[[v]]),
Porcentaje = Frecuencia / sum(Frecuencia) * 100
) |>
dplyr::select(Variable, Categoria, Frecuencia, Porcentaje)
}
)
tabla_html(
frecuenciasCategoricas,
"Frecuencias de las variables categóricas",
digitos = 2
)| Variable | Categoria | Frecuencia | Porcentaje |
|---|---|---|---|
| zona | Zona Sur | 4726 | 56.81 |
| zona | Zona Norte | 1920 | 23.08 |
| zona | Zona Oeste | 1198 | 14.40 |
| zona | Zona Oriente | 351 | 4.22 |
| zona | Zona Centro | 124 | 1.49 |
| piso |
|
2635 | 31.67 |
| piso | 02 | 1450 | 17.43 |
| piso | 03 | 1097 | 13.19 |
| piso | 01 | 860 | 10.34 |
| piso | 04 | 607 | 7.30 |
| piso | 05 | 567 | 6.82 |
| piso | 06 | 245 | 2.95 |
| piso | 08 | 211 | 2.54 |
| piso | 07 | 204 | 2.45 |
| piso | 09 | 146 | 1.76 |
| piso | 10 | 130 | 1.56 |
| piso | 11 | 84 | 1.01 |
| piso | 12 | 83 | 1.00 |
| estrato | 5 | 2750 | 33.06 |
| estrato | 4 | 2129 | 25.59 |
| estrato | 6 | 1987 | 23.89 |
| estrato | 3 | 1453 | 17.47 |
| tipo | Apartamento | 5100 | 61.31 |
| tipo | Casa | 3219 | 38.69 |
| barrio | valle del lili | 1008 | 12.12 |
| barrio | ciudad jardín | 516 | 6.20 |
| barrio | pance | 409 | 4.92 |
| barrio | la flora | 366 | 4.40 |
| barrio | santa teresita | 262 | 3.15 |
| barrio | el caney | 208 | 2.50 |
| barrio | el ingenio | 202 | 2.43 |
| barrio | la hacienda | 164 | 1.97 |
| barrio | acopi | 158 | 1.90 |
| barrio | los cristales | 154 | 1.85 |
| barrio | normandía | 154 | 1.85 |
| barrio | el limonar | 135 | 1.62 |
| barrio | prados del norte | 126 | 1.51 |
| barrio | el refugio | 120 | 1.44 |
| barrio | aguacatal | 109 | 1.31 |
| barrio | ciudad 2000 | 95 | 1.14 |
| barrio | caney | 88 | 1.06 |
| barrio | cristales | 83 | 1.00 |
| barrio | urbanización la flora | 83 | 1.00 |
| barrio | brisas de los | 81 | 0.97 |
| barrio | zona sur | 74 | 0.89 |
| barrio | nueva tequendama | 73 | 0.88 |
| barrio | quintas de don | 72 | 0.87 |
| barrio | versalles | 71 | 0.85 |
| barrio | santa isabel | 63 | 0.76 |
| barrio | parcelaciones pance | 61 | 0.73 |
| barrio | el peñon | 60 | 0.72 |
| barrio | el lido | 59 | 0.71 |
| barrio | torres de comfandi | 57 | 0.69 |
| barrio | capri | 56 | 0.67 |
| barrio | san fernando | 54 | 0.65 |
| barrio | juanamb√∫ | 53 | 0.64 |
| barrio | melendez | 52 | 0.63 |
| barrio | santa monica | 51 | 0.61 |
| barrio | villa del prado | 51 | 0.61 |
| barrio | el bosque | 49 | 0.59 |
| barrio | ciudad bochalema | 48 | 0.58 |
| barrio | san vicente | 48 | 0.58 |
| barrio | santa anita | 48 | 0.58 |
| barrio | mayapan las vegas | 46 | 0.55 |
| barrio | santa rita | 45 | 0.54 |
| barrio | colseguros | 44 | 0.53 |
| barrio | cuarto de legua | 44 | 0.53 |
| barrio | tequendama | 44 | 0.53 |
| barrio | bellavista | 43 | 0.52 |
| barrio | salomia | 40 | 0.48 |
| barrio | santa mónica residencial | 39 | 0.47 |
| barrio | arboledas | 38 | 0.46 |
| barrio | Cali | 37 | 0.44 |
| barrio | primero de mayo | 37 | 0.44 |
| barrio | camino real | 35 | 0.42 |
| barrio | bochalema | 33 | 0.40 |
| barrio | seminario | 32 | 0.38 |
| barrio | vipasa | 32 | 0.38 |
| barrio | zona norte | 32 | 0.38 |
| barrio | chipichape | 30 | 0.36 |
| barrio | departamental | 29 | 0.35 |
| barrio | nápoles | 29 | 0.35 |
| barrio | multicentro | 27 | 0.32 |
| barrio | la merced | 26 | 0.31 |
| barrio | pampa linda | 26 | 0.31 |
| barrio | zona oeste | 26 | 0.31 |
| barrio | ciudad los álamos | 25 | 0.30 |
| barrio | los cambulos | 25 | 0.30 |
| barrio | los guaduales | 25 | 0.30 |
| barrio | miraflores | 25 | 0.30 |
| barrio | villa del sol | 25 | 0.30 |
| barrio | gran limonar | 24 | 0.29 |
| barrio | san antonio | 24 | 0.29 |
| barrio | las ceibas | 23 | 0.28 |
| barrio | meléndez | 23 | 0.28 |
| barrio | menga | 23 | 0.28 |
| barrio | cerro cristales | 22 | 0.26 |
| barrio | ciudad jardin | 22 | 0.26 |
| barrio | alfonso lópez | 21 | 0.25 |
| barrio | bosques del limonar | 21 | 0.25 |
| barrio | cañaverales | 21 | 0.25 |
| barrio | ciudadela pasoancho | 21 | 0.25 |
| barrio | el ingenio ii | 21 | 0.25 |
| barrio | guadalupe | 21 | 0.25 |
| barrio | los andes | 21 | 0.25 |
| barrio | metropolitano del norte | 21 | 0.25 |
| barrio | ciudad cordoba | 20 | 0.24 |
| barrio | el ingenio iii | 20 | 0.24 |
| barrio | prados del limonar | 20 | 0.24 |
| barrio | el guabal | 19 | 0.23 |
| barrio | el ingenio i | 19 | 0.23 |
| barrio | el troncal | 19 | 0.23 |
| barrio | pacara | 19 | 0.23 |
| barrio | bella suiza | 18 | 0.22 |
| barrio | junin | 18 | 0.22 |
| barrio | la arboleda | 18 | 0.22 |
| barrio | la floresta | 18 | 0.22 |
| barrio | san fernando viejo | 18 | 0.22 |
| barrio | zona oriente | 18 | 0.22 |
| barrio | chiminangos | 17 | 0.20 |
| barrio | ciudadela comfandi | 17 | 0.20 |
| barrio | los alcazares | 17 | 0.20 |
| barrio | alameda | 16 | 0.19 |
| barrio | bretaña | 16 | 0.19 |
| barrio | flora industrial | 16 | 0.19 |
| barrio | guayaquil | 16 | 0.19 |
| barrio | san joaquín | 16 | 0.19 |
| barrio | aranjuez | 15 | 0.18 |
| barrio | centenario | 15 | 0.18 |
| barrio | ciudad córdoba | 15 | 0.18 |
| barrio | el jardín | 15 | 0.18 |
| barrio | granada | 15 | 0.18 |
| barrio | la base | 15 | 0.18 |
| barrio | nueva floresta | 15 | 0.18 |
| barrio | alamos | 14 | 0.17 |
| barrio | champagnat | 14 | 0.17 |
| barrio | cristobal colón | 14 | 0.17 |
| barrio | tejares de san | 14 | 0.17 |
| barrio | ciudad capri | 13 | 0.16 |
| barrio | la campiña | 13 | 0.16 |
| barrio | cañaveralejo | 12 | 0.14 |
| barrio | la independencia | 12 | 0.14 |
| barrio | las acacias | 12 | 0.14 |
| barrio | pampalinda | 12 | 0.14 |
| barrio | calipso | 11 | 0.13 |
| barrio | la rivera | 11 | 0.13 |
| barrio | la selva | 11 | 0.13 |
| barrio | occidente | 11 | 0.13 |
| barrio | las granjas | 10 | 0.12 |
| barrio | san fernando nuevo | 10 | 0.12 |
| barrio | santa elena | 10 | 0.12 |
| barrio | villa del lago | 10 | 0.12 |
| barrio | atanasio girardot | 9 | 0.11 |
| barrio | norte | 9 | 0.11 |
| barrio | panamericano | 9 | 0.11 |
| barrio | san cayetano | 9 | 0.11 |
| barrio | benjamín herrera | 8 | 0.10 |
| barrio | calicanto | 8 | 0.10 |
| barrio | colinas del sur | 8 | 0.10 |
| barrio | el cedro | 8 | 0.10 |
| barrio | el gran limonar | 8 | 0.10 |
| barrio | la nueva base | 8 | 0.10 |
| barrio | san bosco | 8 | 0.10 |
| barrio | santa fe | 8 | 0.10 |
| barrio | siete de agosto | 8 | 0.10 |
| barrio | villas de veracruz | 8 | 0.10 |
| barrio | buenos aires | 7 | 0.08 |
| barrio | cañasgordas | 7 | 0.08 |
| barrio | chapinero | 7 | 0.08 |
| barrio | ciudad modelo | 7 | 0.08 |
| barrio | la cascada | 7 | 0.08 |
| barrio | san juan bosco | 7 | 0.08 |
| barrio | santa mónica popular | 7 | 0.08 |
| barrio | urbanización tequendama | 7 | 0.08 |
| barrio | barranquilla | 6 | 0.07 |
| barrio | calima | 6 | 0.07 |
| barrio | el castillo | 6 | 0.07 |
| barrio | el dorado | 6 | 0.07 |
| barrio | floralia | 6 | 0.07 |
| barrio | junín | 6 | 0.07 |
| barrio | los cámbulos | 6 | 0.07 |
| barrio | los parques barranquilla | 6 | 0.07 |
| barrio | oasis de comfandi | 6 | 0.07 |
| barrio | paso del comercio | 6 | 0.07 |
| barrio | pasoancho | 6 | 0.07 |
| barrio | popular | 6 | 0.07 |
| barrio | prados de oriente | 6 | 0.07 |
| barrio | puente del comercio | 6 | 0.07 |
| barrio | villa colombia | 6 | 0.07 |
| barrio | villa de veracruz | 6 | 0.07 |
| barrio | alférez real | 5 | 0.06 |
| barrio | arboleda | 5 | 0.06 |
| barrio | calimio norte | 5 | 0.06 |
| barrio | caney especial | 5 | 0.06 |
| barrio | el trébol | 5 | 0.06 |
| barrio | la alborada | 5 | 0.06 |
| barrio | la alianza | 5 | 0.06 |
| barrio | las delicias | 5 | 0.06 |
| barrio | los alcázares | 5 | 0.06 |
| barrio | manzanares | 5 | 0.06 |
| barrio | normandia | 5 | 0.06 |
| barrio | santa monica residencial | 5 | 0.06 |
| barrio | santo domingo | 5 | 0.06 |
| barrio | urbanización río lili | 5 | 0.06 |
| barrio | villa del sur | 5 | 0.06 |
| barrio | altos de guadalupe | 4 | 0.05 |
| barrio | bella suiza alta | 4 | 0.05 |
| barrio | centro | 4 | 0.05 |
| barrio | colseguros andes | 4 | 0.05 |
| barrio | jamundi | 4 | 0.05 |
| barrio | la fortaleza | 4 | 0.05 |
| barrio | los farallones | 4 | 0.05 |
| barrio | los libertadores | 4 | 0.05 |
| barrio | pacará | 4 | 0.05 |
| barrio | quintas de salomia | 4 | 0.05 |
| barrio | saavedra galindo | 4 | 0.05 |
| barrio | san carlos | 4 | 0.05 |
| barrio | san joaquin | 4 | 0.05 |
| barrio | tejares cristales | 4 | 0.05 |
| barrio | templete | 4 | 0.05 |
| barrio | urbanización barranquilla | 4 | 0.05 |
| barrio | urbanización la merced | 4 | 0.05 |
| barrio | urbanización la nueva | 4 | 0.05 |
| barrio | urbanización san joaquin | 4 | 0.05 |
| barrio | 20 de julio | 3 | 0.04 |
| barrio | Pance | 3 | 0.04 |
| barrio | altos de menga | 3 | 0.04 |
| barrio | belalcazar | 3 | 0.04 |
| barrio | cambulos | 3 | 0.04 |
| barrio | ciudad real | 3 | 0.04 |
| barrio | colinas de menga | 3 | 0.04 |
| barrio | el paraíso | 3 | 0.04 |
| barrio | la buitrera | 3 | 0.04 |
| barrio | las américas | 3 | 0.04 |
| barrio | libertadores | 3 | 0.04 |
| barrio | los guayacanes | 3 | 0.04 |
| barrio | morichal de comfandi | 3 | 0.04 |
| barrio | municipal | 3 | 0.04 |
| barrio | porvenir | 3 | 0.04 |
| barrio | primitivo crespo | 3 | 0.04 |
| barrio | san pedro | 3 | 0.04 |
| barrio | santa bárbara | 3 | 0.04 |
| barrio | santa mónica | 3 | 0.04 |
| barrio | unión de vivienda | 3 | 0.04 |
| barrio | urbanización colseguros | 3 | 0.04 |
| barrio | urbanización nueva granada | 3 | 0.04 |
| barrio | Ciudad Jardín | 2 | 0.02 |
| barrio | La Flora | 2 | 0.02 |
| barrio | La Hacienda | 2 | 0.02 |
| barrio | Santa Anita | 2 | 0.02 |
| barrio | aguablanca | 2 | 0.02 |
| barrio | alameda del río | 2 | 0.02 |
| barrio | alcazares | 2 | 0.02 |
| barrio | alferez real | 2 | 0.02 |
| barrio | antonio nariño | 2 | 0.02 |
| barrio | base aérea | 2 | 0.02 |
| barrio | belisario caicedo | 2 | 0.02 |
| barrio | chiminangos 2 etapa | 2 | 0.02 |
| barrio | ciudad pacifica | 2 | 0.02 |
| barrio | cristóbal colón | 2 | 0.02 |
| barrio | el diamante | 2 | 0.02 |
| barrio | el prado | 2 | 0.02 |
| barrio | eucarístico | 2 | 0.02 |
| barrio | evaristo garcía | 2 | 0.02 |
| barrio | guaduales | 2 | 0.02 |
| barrio | juanambu | 2 | 0.02 |
| barrio | la libertad | 2 | 0.02 |
| barrio | la rivera i | 2 | 0.02 |
| barrio | la rivera ii | 2 | 0.02 |
| barrio | lourdes | 2 | 0.02 |
| barrio | napoles | 2 | 0.02 |
| barrio | paseo de los | 2 | 0.02 |
| barrio | poblado campestre | 2 | 0.02 |
| barrio | portada de comfandi | 2 | 0.02 |
| barrio | prados del sur | 2 | 0.02 |
| barrio | primavera | 2 | 0.02 |
| barrio | refugio | 2 | 0.02 |
| barrio | san judas tadeo | 2 | 0.02 |
| barrio | san luis | 2 | 0.02 |
| barrio | santa monica norte | 2 | 0.02 |
| barrio | santa monica popular | 2 | 0.02 |
| barrio | sector cañaveralejo guadalupe | 2 | 0.02 |
| barrio | urbanizacion lili | 2 | 0.02 |
| barrio | 3 de julio | 1 | 0.01 |
| barrio | Belalcazar | 1 | 0.01 |
| barrio | Brisas De Los | 1 | 0.01 |
| barrio | Bueno Madrid | 1 | 0.01 |
| barrio | Camino Real | 1 | 0.01 |
| barrio | Centenario | 1 | 0.01 |
| barrio | Chiminangos | 1 | 0.01 |
| barrio | Ciudad 2000 | 1 | 0.01 |
| barrio | Ciudad Pacifica | 1 | 0.01 |
| barrio | Colseguros Andes | 1 | 0.01 |
| barrio | El Bosque | 1 | 0.01 |
| barrio | El Caney | 1 | 0.01 |
| barrio | El Ingenio | 1 | 0.01 |
| barrio | Los Guaduales | 1 | 0.01 |
| barrio | Miraflores | 1 | 0.01 |
| barrio | Prados Del Limonar | 1 | 0.01 |
| barrio | Prados Del Norte | 1 | 0.01 |
| barrio | Quintas De Don | 1 | 0.01 |
| barrio | San Fernando | 1 | 0.01 |
| barrio | Santa Isabel | 1 | 0.01 |
| barrio | Santa Monica | 1 | 0.01 |
| barrio | Santa Teresita | 1 | 0.01 |
| barrio | Santafe | 1 | 0.01 |
| barrio | Santo Domingo | 1 | 0.01 |
| barrio | Valle Del Lili | 1 | 0.01 |
| barrio | Villa Del Prado | 1 | 0.01 |
| barrio | Villas De Veracruz | 1 | 0.01 |
| barrio | agua blanca | 1 | 0.01 |
| barrio | alameda del rio | 1 | 0.01 |
| barrio | alborada | 1 | 0.01 |
| barrio | alfonso lopez | 1 | 0.01 |
| barrio | alfonso lópez i | 1 | 0.01 |
| barrio | alto jordán | 1 | 0.01 |
| barrio | altos de santa | 1 | 0.01 |
| barrio | arboleda campestre candelaria | 1 | 0.01 |
| barrio | autopista sur | 1 | 0.01 |
| barrio | bajo aguacatal | 1 | 0.01 |
| barrio | barrio 7de agosto | 1 | 0.01 |
| barrio | barrio el recuerdo | 1 | 0.01 |
| barrio | barrio eucarístico | 1 | 0.01 |
| barrio | barrio obrero | 1 | 0.01 |
| barrio | barrio tranquilo y | 1 | 0.01 |
| barrio | berlin | 1 | 0.01 |
| barrio | bloques del limonar | 1 | 0.01 |
| barrio | bolivariano | 1 | 0.01 |
| barrio | bosques de alboleda | 1 | 0.01 |
| barrio | boyacá | 1 | 0.01 |
| barrio | brisas de guadalupe | 1 | 0.01 |
| barrio | brisas del guabito | 1 | 0.01 |
| barrio | brisas del limonar | 1 | 0.01 |
| barrio | caldas | 1 | 0.01 |
| barrio | cali bella | 1 | 0.01 |
| barrio | cali canto | 1 | 0.01 |
| barrio | calibella | 1 | 0.01 |
| barrio | calicanto viii | 1 | 0.01 |
| barrio | campestre | 1 | 0.01 |
| barrio | cascajal | 1 | 0.01 |
| barrio | cataya real | 1 | 0.01 |
| barrio | cañaverales los samanes | 1 | 0.01 |
| barrio | ceibas | 1 | 0.01 |
| barrio | centelsa | 1 | 0.01 |
| barrio | cerros de guadalupe | 1 | 0.01 |
| barrio | chiminangos 1 etapa | 1 | 0.01 |
| barrio | ciudad antejardin | 1 | 0.01 |
| barrio | ciudad country | 1 | 0.01 |
| barrio | ciudad córdoba reservado | 1 | 0.01 |
| barrio | ciudad del campo | 1 | 0.01 |
| barrio | ciudad jardin pance | 1 | 0.01 |
| barrio | ciudad los alamos | 1 | 0.01 |
| barrio | ciudad melendez | 1 | 0.01 |
| barrio | ciudad meléndez | 1 | 0.01 |
| barrio | ciudad talanga | 1 | 0.01 |
| barrio | ciudad universitaria | 1 | 0.01 |
| barrio | ciudadela del río | 1 | 0.01 |
| barrio | ciudadela melendez | 1 | 0.01 |
| barrio | ciudadela paso ancho | 1 | 0.01 |
| barrio | colinas del bosque | 1 | 0.01 |
| barrio | colon | 1 | 0.01 |
| barrio | comfenalco | 1 | 0.01 |
| barrio | compartir | 1 | 0.01 |
| barrio | conjunto gibraltar | 1 | 0.01 |
| barrio | ed benjamin herrera | 1 | 0.01 |
| barrio | el guabito | 1 | 0.01 |
| barrio | el ingenio 3 | 1 | 0.01 |
| barrio | el jordán | 1 | 0.01 |
| barrio | el nacional | 1 | 0.01 |
| barrio | el rodeo | 1 | 0.01 |
| barrio | el sena | 1 | 0.01 |
| barrio | el vallado | 1 | 0.01 |
| barrio | farrallones de pance | 1 | 0.01 |
| barrio | fenalco kennedy | 1 | 0.01 |
| barrio | fepicol | 1 | 0.01 |
| barrio | flora | 1 | 0.01 |
| barrio | fonaviemcali | 1 | 0.01 |
| barrio | francisco eladio ramirez | 1 | 0.01 |
| barrio | fuentes de la | 1 | 0.01 |
| barrio | gaitan | 1 | 0.01 |
| barrio | guadalupe alto | 1 | 0.01 |
| barrio | hacienda alferez real | 1 | 0.01 |
| barrio | ingenio | 1 | 0.01 |
| barrio | ingenio i | 1 | 0.01 |
| barrio | ingenio ii | 1 | 0.01 |
| barrio | jamundi alfaguara | 1 | 0.01 |
| barrio | jorge eliecer gaitán | 1 | 0.01 |
| barrio | jorge isaacs | 1 | 0.01 |
| barrio | jose manuel marroquín | 1 | 0.01 |
| barrio | la ceibas | 1 | 0.01 |
| barrio | la esmeralda | 1 | 0.01 |
| barrio | la gran colombia | 1 | 0.01 |
| barrio | la luisa | 1 | 0.01 |
| barrio | la morada | 1 | 0.01 |
| barrio | la playa | 1 | 0.01 |
| barrio | la portada al | 1 | 0.01 |
| barrio | la primavera | 1 | 0.01 |
| barrio | la reforma | 1 | 0.01 |
| barrio | la riverita | 1 | 0.01 |
| barrio | la riviera | 1 | 0.01 |
| barrio | la villa del | 1 | 0.01 |
| barrio | laflora | 1 | 0.01 |
| barrio | lares de comfenalco | 1 | 0.01 |
| barrio | las camelias | 1 | 0.01 |
| barrio | las quintas de | 1 | 0.01 |
| barrio | las vegas | 1 | 0.01 |
| barrio | las vegas de | 1 | 0.01 |
| barrio | los alamos | 1 | 0.01 |
| barrio | los cristales club | 1 | 0.01 |
| barrio | los jockeys | 1 | 0.01 |
| barrio | los robles | 1 | 0.01 |
| barrio | mamellan | 1 | 0.01 |
| barrio | mariano ramos | 1 | 0.01 |
| barrio | marroquín iii | 1 | 0.01 |
| barrio | miradol del aguacatal | 1 | 0.01 |
| barrio | normandía west point | 1 | 0.01 |
| barrio | norte la flora | 1 | 0.01 |
| barrio | nueva base | 1 | 0.01 |
| barrio | oasis de pasoancho | 1 | 0.01 |
| barrio | palmas del ingenio | 1 | 0.01 |
| barrio | parque residencial el | 1 | 0.01 |
| barrio | ponce | 1 | 0.01 |
| barrio | portales de comfandi | 1 | 0.01 |
| barrio | puente palma | 1 | 0.01 |
| barrio | rafael uribe uribe | 1 | 0.01 |
| barrio | rep√∫blica de israel | 1 | 0.01 |
| barrio | rincon de la | 1 | 0.01 |
| barrio | rincón de salomia | 1 | 0.01 |
| barrio | riveras del valle | 1 | 0.01 |
| barrio | rozo la torre | 1 | 0.01 |
| barrio | samanes | 1 | 0.01 |
| barrio | samanes de guadalupe | 1 | 0.01 |
| barrio | sameco | 1 | 0.01 |
| barrio | san judas | 1 | 0.01 |
| barrio | san luís | 1 | 0.01 |
| barrio | san nicolas | 1 | 0.01 |
| barrio | san nicolás | 1 | 0.01 |
| barrio | santa | 1 | 0.01 |
| barrio | santa anita sur | 1 | 0.01 |
| barrio | santa helena de | 1 | 0.01 |
| barrio | santa mónica alta | 1 | 0.01 |
| barrio | santa rosa | 1 | 0.01 |
| barrio | santander | 1 | 0.01 |
| barrio | sector aguacatal | 1 | 0.01 |
| barrio | sierras de normandía | 1 | 0.01 |
| barrio | simón bolivar | 1 | 0.01 |
| barrio | tequendema | 1 | 0.01 |
| barrio | terrón colorado | 1 | 0.01 |
| barrio | unicentro cali | 1 | 0.01 |
| barrio | urbanizacion el saman | 1 | 0.01 |
| barrio | urbanizacion gratamira | 1 | 0.01 |
| barrio | urbanización boyacá | 1 | 0.01 |
| barrio | urbanización las cascadas | 1 | 0.01 |
| barrio | urbanización pacara | 1 | 0.01 |
| barrio | valle de lili | 1 | 0.01 |
| barrio | valle grande | 1 | 0.01 |
| barrio | villa del parque | 1 | 0.01 |
| barrio | zona centro | 1 | 0.01 |
| barrio | zona norte los | 1 | 0.01 |
| barrio | zona residencial | 1 | 0.01 |
parametrosReproducibilidad <- tibble(
Parametro = c(
"Semilla general",
"Algoritmo K-Means",
"Número de inicializaciones",
"Máximo de iteraciones",
"Rango evaluado de K"
),
Valor = c(
"20260730",
"Hartigan-Wong",
as.character(nstartKmeans),
as.character(iterMaxKmeans),
paste0("1-", kMaximo)
)
)
tabla_html(
parametrosReproducibilidad,
"Parámetros utilizados para reproducibilidad",
digitos = 0
)| Parametro | Valor |
|---|---|
| Semilla general | 20260730 |
| Algoritmo K-Means | Hartigan-Wong |
| Número de inicializaciones | 50 |
| Máximo de iteraciones | 100 |
| Rango evaluado de K | 1-10 |
## R version 4.5.2 (2025-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8 LC_CTYPE=Spanish_Colombia.utf8
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Spanish_Colombia.utf8
##
## time zone: America/Bogota
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] paqueteMODELOS_0.1.0 summarytools_1.1.5 gridExtra_2.3.1
## [4] broom_1.0.13 boot_1.3-32 GGally_2.4.0
## [7] mice_3.19.0 naniar_1.1.0 cluster_2.1.8.3
## [10] factoextra_2.2.0 FactoMineR_2.16 corrplot_0.95
## [13] scales_1.4.0 kableExtra_1.4.1 knitr_1.51
## [16] forcats_1.0.1 purrr_1.2.2 tidyr_1.3.2
## [19] dplyr_1.2.1 ggplot2_4.0.3
##
## loaded via a namespace (and not attached):
## [1] Rdpack_2.6.6 tcltk_4.5.2 rlang_1.3.0
## [4] magrittr_2.0.5 otel_0.2.0 matrixStats_1.5.0
## [7] compiler_4.5.2 mgcv_1.9-3 reshape2_1.4.5
## [10] systemfonts_1.3.2 vctrs_0.7.3 stringr_1.6.0
## [13] sysfonts_0.8.9 pkgconfig_2.0.3 shape_1.4.6.1
## [16] fastmap_1.2.0 magick_2.9.1 backports_1.5.1
## [19] labeling_0.4.3 pander_0.6.6 rmarkdown_2.31
## [22] nloptr_2.2.1 visdat_0.6.0 xfun_0.60
## [25] glmnet_5.0 jomo_2.7-6 showtext_0.9-8
## [28] cachem_1.1.0 jsonlite_2.0.0 flashClust_1.1-4
## [31] pan_2.0 irlba_2.3.7 R6_2.6.1
## [34] bslib_0.11.0 stringi_1.8.7 RColorBrewer_1.1-3
## [37] car_3.1-5 rpart_4.1.24 lubridate_1.9.5
## [40] jquerylib_0.1.4 estimability_2.0.0 Rcpp_1.1.2
## [43] iterators_1.0.14 base64enc_0.1-6 pacman_0.5.1
## [46] timechange_0.4.0 Matrix_1.7-4 splines_4.5.2
## [49] nnet_7.3-20 tidyselect_1.2.1 abind_1.4-8
## [52] rstudioapi_0.19.0 yaml_2.3.12 ggtext_0.1.2
## [55] codetools_0.2-20 plyr_1.8.9 lattice_0.22-7
## [58] tibble_3.3.1 withr_3.0.3 S7_0.2.2
## [61] evaluate_1.0.5 survival_3.8-3 ggstats_0.13.0
## [64] xml2_1.6.0 ggpubr_1.0.0 pillar_1.11.1
## [67] carData_3.0-6 checkmate_2.3.4 DT_0.34.0
## [70] foreach_1.5.2 reformulas_0.4.4 generics_0.1.4
## [73] minqa_1.2.8 xtable_1.8-8 leaps_3.2
## [76] glue_1.8.1 emmeans_2.0.4 scatterplot3d_0.3-45
## [79] tools_4.5.2 lme4_2.0-6 ggsignif_0.6.4
## [82] mvtnorm_1.4-2 rapportools_1.2 grid_4.5.2
## [85] rbibutils_2.4.1 nlme_3.1-168 showtextdb_3.0
## [88] Formula_1.2-5 cli_3.6.6 textshaping_1.0.5
## [91] viridisLite_0.4.3 svglite_2.2.2 gtable_0.3.6
## [94] rstatix_1.1.0 sass_0.4.10 digest_0.6.39
## [97] ggrepel_0.9.8 htmlwidgets_1.6.4 farver_2.1.2
## [100] htmltools_0.5.9 lifecycle_1.0.5 multcompView_0.1-12
## [103] mitml_0.4-5 gridtext_0.1.6 MASS_7.3-65