1 Resumen ejecutivo

Este informe analiza 8.322 ofertas de vivienda publicadas en el portal OLX para la ciudad de Cali, con el fin de caracterizar la estructura del mercado de oferta y apoyar las decisiones de compra, venta y valoración de una empresa inmobiliaria. Se aplicaron tres técnicas de análisis multivariante: componentes principales, conglomerados y correspondencias múltiples.

El precio de oferta se explica principalmente por el tamaño del inmueble. El área construida, el número de baños y la disponibilidad de parqueaderos acompañan el precio con mayor intensidad que el número de habitaciones, atributo que suele destacarse en los anuncios.

La oferta se organiza en dos segmentos. El primero, que reúne el 29,9 % de los inmuebles, corresponde a vivienda amplia con precio mediano de 695 millones y predominio de casas. El segundo, con el 70,1 % restante, corresponde a vivienda compacta con precio mediano de 260 millones y predominio de apartamentos. El precio por metro cuadrado resulta ligeramente superior en el segmento compacto.

Ambos segmentos conviven en las mismas zonas de la ciudad. La segmentación por características del inmueble no coincide con su localización, de modo que la zona no permite anticipar el tipo de producto que se encontrará en ella. La estructura territorial, en cambio, sí ordena la oferta según el estrato socioeconómico.

Los datos corresponden a precios solicitados por los vendedores y no a operaciones cerradas, por lo que los resultados describen la estructura de la oferta publicada y no el valor de mercado de los inmuebles.

2 Objetivos

2.1 Objetivo general

Apoyar las decisiones de compra, venta y valoración de una empresa inmobiliaria, mediante la caracterización de la estructura del mercado de oferta de vivienda urbana con técnicas de análisis multivariante.

2.2 Objetivos específicos

  • Determinar qué atributos concentran la variabilidad de la oferta mediante análisis de componentes principales, a partir de una base previamente depurada.

  • Segmentar la oferta en grupos homogéneos y caracterizar cada segmento según su precio, dimensiones y localización.

  • Establecer los patrones de asociación entre zona, tipo de vivienda y estrato mediante análisis de correspondencias múltiples, incorporando el barrio como variable ilustrativa.

  • Formular recomendaciones de captación, comercialización y valoración para la empresa, señalando los límites de lo que los datos permiten concluir.

3 Datos

3.1 Origen y alcance

El presente informe analiza los datos de una base extraída mediante webscraping de OLX, plataforma en línea de anuncios de vivienda. La base se distribuye en el paquete paqueteMODELOS y contiene 8.322 ofertas de vivienda en Cali, descritas por 13 variables y georreferenciadas por longitud y latitud.

Conviene precisar qué tipo de información recogen estos registros. Se trata de inmuebles publicados y no de operaciones cerradas, de modo que preciom corresponde al valor que solicita el vendedor y no al precio en que finalmente se negocia la vivienda. La base permite entonces establecer en qué rangos se está pidiendo por inmuebles con determinadas características, pero no estimar cuánto vale realmente un inmueble particular.

3.2 Diccionario de variables

A continuación se construye el diccionario de las variables que conforman la base de datos, en el que se señala el tipo de dato, la escala de medición, la unidad en que está expresada, el número de valores distintos que toman y el conteo de registros faltantes. La escala de medición se incluye de manera explícita porque de ella depende el tratamiento estadístico que cada variable admite.

meta <- data.frame(
  Variable = c("id", "zona", "piso", "estrato", "preciom", "areaconst",
               "parqueaderos", "banios", "habitaciones", "tipo", "barrio",
               "longitud", "latitud"),
  Escala = c("Nominal (identificador)", "Nominal", "Ordinal", "Ordinal",
             "Razón (continua)", "Razón (continua)", "Razón (discreta)",
             "Razón (discreta)", "Razón (discreta)", "Nominal", "Nominal",
             "Intervalo (coordenada)", "Intervalo (coordenada)"),
  Unidad = c("—", "—", "n.º de piso", "categoría", "millones COP", "m²",
             "conteo", "conteo", "conteo", "—", "—",
             "grados decimales", "grados decimales"),
  Descripcion = c(
    "Identificador del registro",
    "Zona de la ciudad donde se ubica el inmueble",
    "Piso de ubicación del inmueble",
    "Estrato socioeconómico del predio",
    "Precio de oferta del inmueble",
    "Área construida",
    "Número de parqueaderos",
    "Número de baños",
    "Número de habitaciones",
    "Tipo de vivienda",
    "Barrio de ubicación",
    "Coordenada de longitud",
    "Coordenada de latitud")
)

# sapply() aplica una función a cada elemento de una lista o data frame
# y simplifica el resultado a vector. Aquí recorre las columnas de la base
# y devuelve, de una sola vez, tres atributos de cada variable.

# class(x)[1] toma solo la primera clase declarada: los tibbles importados
# arrastran varias clases por columna y sin el [1] la tabla saldría rota.
meta$`Tipo en R` <- sapply(vivienda[meta$Variable], function(x) class(x)[1])

# unique() lista los valores distintos; na.omit() los depura de faltantes
# antes de contarlos, para que NA no se cuente como un nivel más.
meta$Niveles     <- sapply(vivienda[meta$Variable], function(x) length(unique(na.omit(x))))

# is.na() devuelve TRUE/FALSE por celda; sum() cuenta los TRUE.
meta$Faltantes   <- sapply(vivienda[meta$Variable], function(x) sum(is.na(x)))

meta$`% NA`      <- round(100 * meta$Faltantes / nrow(vivienda), 2)

diccionario <- meta[, c("Variable", "Tipo en R", "Escala", "Unidad",
                        "Niveles", "Faltantes", "% NA", "Descripcion")]

tabla(diccionario, caption = "Tabla 1. Diccionario de variables de la base vivienda")
Tabla 1. Diccionario de variables de la base vivienda
Variable Tipo en R Escala Unidad Niveles Faltantes % NA Descripcion
id numeric Nominal (identificador) 8,319 3 0.04 Identificador del registro
zona character Nominal 5 3 0.04 Zona de la ciudad donde se ubica el inmueble
piso character Ordinal n.º de piso 12 2,638 31.70 Piso de ubicación del inmueble
estrato numeric Ordinal categoría 4 3 0.04 Estrato socioeconómico del predio
preciom numeric Razón (continua) millones COP 539 2 0.02 Precio de oferta del inmueble
areaconst numeric Razón (continua) 652 3 0.04 Área construida
parqueaderos numeric Razón (discreta) conteo 10 1,605 19.29 Número de parqueaderos
banios numeric Razón (discreta) conteo 11 3 0.04 Número de baños
habitaciones numeric Razón (discreta) conteo 11 3 0.04 Número de habitaciones
tipo character Nominal 2 3 0.04 Tipo de vivienda
barrio character Nominal 436 3 0.04 Barrio de ubicación
longitud numeric Intervalo (coordenada) grados decimales 2,928 3 0.04 Coordenada de longitud
latitud numeric Intervalo (coordenada) grados decimales 3,679 3 0.04 Coordenada de latitud

3.3 Distribución de las variables categóricas

A continuación se examina la distribución de las variables categóricas de la base, reportando para cada categoría su frecuencia y el porcentaje que representa sobre el total de registros. Esta revisión se realiza porque la frecuencia de cada categoría condiciona su tratamiento en el análisis de correspondencias múltiples.

# --- Distribución de las variables categóricas --

resumen_cat <- function(x, nombre) {
  t <- table(x, useNA = "ifany")   # frecuencias; useNA fuerza a mostrar los NA
  data.frame(
    Variable   = nombre,
    Categoria  = names(t),                                   # nombres de las categorías
    Frecuencia = as.integer(t),                              # conteos
    Porcentaje = round(100 * as.integer(t) / length(x), 1),
    row.names  = NULL
  )
}

# rbind() apila data frames con las mismas columnas, uno debajo del otro.
cat_resumen <- rbind(
  resumen_cat(vivienda$zona,    "Zona"),
  resumen_cat(vivienda$tipo,    "Tipo"),
  resumen_cat(vivienda$estrato, "Estrato")
)

tabla(cat_resumen, caption = "Tabla 2. Distribución de las variables categóricas")
Tabla 2. Distribución de las variables categóricas
Variable Categoria Frecuencia Porcentaje
Zona Zona Centro 124 1.5
Zona Zona Norte 1,920 23.1
Zona Zona Oeste 1,198 14.4
Zona Zona Oriente 351 4.2
Zona Zona Sur 4,726 56.8
Zona NA 3 0.0
Tipo Apartamento 5,100 61.3
Tipo Casa 3,219 38.7
Tipo NA 3 0.0
Estrato 3 1,453 17.5
Estrato 4 2,129 25.6
Estrato 5 2,750 33.0
Estrato 6 1,987 23.9
Estrato NA 3 0.0
  • La base de datos cubre únicamente los estratos 3 a 6, sin registros de estratos 1 y 2.
  • La oferta se concentra en la Zona Sur, que reúne el 56.8% de los registros; entre Sur y Norte suman cerca del 80%.
  • Los apartamentos representan el 61.3% de la base y las casas el 38.7% restante.
  • Se identifican tres registros sin información en ninguna de las tres variables, cuyo tratamiento se aborda en la sección de preparación de los datos.

4 Preparación de los datos

La base presenta datos faltantes concentrados en dos variables. En lugar de eliminar los registros incompletos o imputar los valores ausentes de manera uniforme, se examinó cada variable por separado, con el propósito de identificar su naturaleza, dado que un campo vacío no siempre significa lo mismo: en unos casos corresponde a información no reportada y en otros a una característica que el anuncio no registra. ## Datos faltantes

A continuación se examina la distribución de los registros según el número de campos faltantes que presentan.

# La Tabla 1 cuenta faltantes por variable; aquí se clasifican los registros
# según cuántas variables les faltan, que es la unidad sobre la que recae
# la decisión de eliminar.

vacios <- rowSums(is.na(vivienda))   # campos vacíos de cada registro

# cut() convierte un vector numérico en categorías definidas por cortes.
# breaks fija los límites y labels les pone nombre; right = FALSE hace que
# cada intervalo incluya su límite inferior y excluya el superior.
estado <- cut(vacios,
              breaks = c(0, 1, 2, 3, 14),
              labels = c("Completo",
                         "Falta una variable",
                         "Faltan dos variables",
                         "Sin información"),
              right  = FALSE)

completitud <- as.data.frame(table(estado))
names(completitud) <- c("Estado del registro", "Registros")
completitud$Porcentaje <- round(100 * completitud$Registros / nrow(vivienda), 1)

tabla(completitud, caption = "Tabla 3. Estado de completitud de los registros")
Tabla 3. Estado de completitud de los registros
Estado del registro Registros Porcentaje
Completo 4,808 57.8
Falta una variable 2,785 33.5
Faltan dos variables 726 8.7
Sin información 3 0.0

Como se observa en la Tabla 3, al examinar los registros de manera individual se encuentra que solo el 57.8% del conjunto de datos cuenta con información en las trece variables. El 42.2% restante presenta al menos un campo vacío, cuyo tratamiento se aborda en los apartados siguientes.

4.1 Variable piso

piso presenta 2.638 datos faltantes, el 31,70 % de la base de datos. La ausencia podría explicarse porque las casas no tienen número de piso, pero como se observa en la Tabla 4, esa explicación se descarta: 1.381 apartamentos carecen del dato y 1.965 casas sí lo registran.

cruce_piso <- as.data.frame.matrix(
  table(vivienda$tipo, is.na(vivienda$piso), useNA = "ifany")
)
names(cruce_piso) <- c("Con dato", "Sin dato")

cruce_piso <- cbind(`Tipo de vivienda` = rownames(cruce_piso), cruce_piso)
rownames(cruce_piso) <- NULL

cruce_piso$`Tipo de vivienda`[is.na(cruce_piso$`Tipo de vivienda`)] <- "Sin tipo registrado"

tabla(cruce_piso, caption = "Tabla 4. Registros con y sin dato de piso según tipo de vivienda")
Tabla 4. Registros con y sin dato de piso según tipo de vivienda
Tipo de vivienda Con dato Sin dato
Apartamento 3,719 1,381
Casa 1,965 1,254
NA. 0 3

El dato consignado en las casas indica que piso no mide lo mismo en todos los registros. Por esa razón, y por el porcentaje de faltantes, se toma la decisión de excluir la variable retirando la columna sin eliminar registros.

4.2 Variable Parqueaderos

La variable parqueaderos presenta 1.605 datos faltantes, el 19,29 % de la base de datos. El vacío podría indicar que el inmueble no tiene parqueadero, en lugar de un dato no reportado. Para verificarlo se examinan los valores observados de la variable.

frec_parq <- as.data.frame(table(vivienda$parqueaderos, useNA = "ifany"))
names(frec_parq) <- c("Parqueaderos", "Registros")
frec_parq$Porcentaje <- round(100 * frec_parq$Registros / nrow(vivienda), 1)
frec_parq$Parqueaderos <- as.character(frec_parq$Parqueaderos)
frec_parq$Parqueaderos[is.na(frec_parq$Parqueaderos)] <- "Sin dato"

tabla(frec_parq, caption = "Tabla 5. Distribución de los valores observados de parqueaderos")
Tabla 5. Distribución de los valores observados de parqueaderos
Parqueaderos Registros Porcentaje
1 3,155 37.9
2 2,475 29.7
3 520 6.2
4 384 4.6
5 68 0.8
6 68 0.8
7 18 0.2
8 17 0.2
9 4 0.0
10 8 0.1
Sin dato 1,605 19.3

La variable toma valores entre 1 y 10 y el cero no aparece en ninguno de los 6.717 registros con dato. Esto indica que el anuncio no dispone de una forma de registrar numéricamente la ausencia de parqueadero, condición que queda representada por el campo vacío.

Los 1.605 datos faltantes se recodifican entonces como cero, decisión que conserva la totalidad de los registros. Se trata de un supuesto sustentado en la codificación observada, no de un hecho verificado.

4.3 Valores banios y habitaciones

Estas dos variables registran el valor cero: 45 casos en banios y 66 en habitaciones. Un inmueble residencial no puede carecer de baño, de modo que ese cero corresponde a un campo sin diligenciar. En habitaciones, en cambio, el cero podría ser legítimo si se tratara de apartaestudios, por lo que se examina el detalle de esos registros.

frec_ceros <- rbind(
  data.frame(Variable = "Banios",       Valor = vivienda$banios),
  data.frame(Variable = "Habitaciones", Valor = vivienda$habitaciones)
)
frec_ceros <- subset(frec_ceros, !is.na(Valor))

ggplot(frec_ceros, aes(x = factor(Valor), fill = Valor == 0)) +
  geom_bar() +
  facet_wrap(~ Variable, ncol = 1, scales = "free_y") +
  scale_fill_manual(values = c("FALSE" = AZUL, "TRUE" = NARANJA), guide = "none") +
  labs(x = "Valor registrado", y = "Número de registros")
Figura 1. Distribución de banios y habitaciones

Figura 1. Distribución de banios y habitaciones

Como se observa en la Figura 1, el valor cero aparece aislado en las distribuciones de las dos varibles. Se puede inferir que ese patrón corresponde a un campo que, durante el proceso de web scraping se registró como cero cuando el anuncio lo dejó vacío, y no a un valor efectivamente reportado.

El detalle de los registros descarta además la hipótesis del apartaestudio: de los 66 casos con cero habitaciones, 45 corresponden a casas y 35 presentan también cero baños. Ninguna tipología de vivienda carece de habitaciones y de baños a la vez.

Por lo tanto, se toma la decisión de eliminar los 76 registros con cero en cualquiera de las dos variables.

4.4 Base depurada

vivienda_dep <- vivienda

# 1. Se retira piso
vivienda_dep$piso <- NULL

# 2. Los faltantes de parqueaderos se recodifican como cero
vivienda_dep$parqueaderos[is.na(vivienda_dep$parqueaderos)] <- 0

# 3. Se eliminan los registros con cero baños o cero habitaciones
vivienda_dep <- subset(vivienda_dep,
                       !(banios == 0 | habitaciones == 0) |
                         is.na(banios) | is.na(habitaciones))

# 4. Se eliminan los registros sin información
vivienda_dep <- vivienda_dep[rowSums(is.na(vivienda_dep)) < 10, ]

balance <- data.frame(
  Concepto = c("Registros iniciales",
               "Eliminados por cero en banios o habitaciones",
               "Eliminados por ausencia de información",
               "Registros finales"),
  Registros = c(nrow(vivienda), 76, 3, nrow(vivienda_dep))
)
balance$Porcentaje <- round(100 * balance$Registros / nrow(vivienda), 2)

tabla(balance, caption = "Tabla 6. Balance de la depuración")
Tabla 6. Balance de la depuración
Concepto Registros Porcentaje
Registros iniciales 8,322 100.00
Eliminados por cero en banios o habitaciones 76 0.91
Eliminados por ausencia de información 3 0.04
Registros finales 8,243 99.05

El tratamiento diferenciado por variable permite conservar 8.243 registros, el 99,05 % del total original. La exclusión de piso se resolvió retirando la columna y no los registros, de modo que los 2.638 inmuebles sin ese dato permanecen en la base con el resto de sus atributos completos. Los 1.605 faltantes de parqueaderos se recodificaron como cero y tampoco supusieron pérdida alguna. Las eliminaciones se limitaron a los 76 registros con valor cero en banios o en habitaciones, que no corresponden a inmuebles reales, y a los 3 sin información generados en la extracción. La eliminación por lista, en cambio, habría descartado el 42,2 % de la base.

5 Análisis descriptivo

Antes de aplicar las técnicas multivariadas se examina el comportamiento de las cinco variables cuantitativas que participarán en el análisis de componentes principales y en el de conglomerados. Ambas técnicas operan sobre distancias calculadas a partir de la varianza, de modo que la dispersión y la asimetría de cada variable condicionan sus resultados.

descriptivos <- data.frame(
  Variable = vars_cuant,
  Media    = sapply(vivienda_dep[vars_cuant], mean,   na.rm = TRUE),
  Mediana  = sapply(vivienda_dep[vars_cuant], median, na.rm = TRUE),
  Desv     = sapply(vivienda_dep[vars_cuant], sd,     na.rm = TRUE),
  Minimo   = sapply(vivienda_dep[vars_cuant], min,    na.rm = TRUE),
  Maximo   = sapply(vivienda_dep[vars_cuant], max,    na.rm = TRUE),
  row.names = NULL
)

descriptivos$CV <- round(100 * descriptivos$Desv / descriptivos$Media, 1)

asimetria <- function(x) {
  x <- x[!is.na(x)]
  mean((x - mean(x))^3) / sd(x)^3
}
descriptivos$Asimetria <- round(sapply(vivienda_dep[vars_cuant], asimetria), 2)

tabla(descriptivos, caption = "Tabla 7. Estadísticos descriptivos de las variables cuantitativas")
Tabla 7. Estadísticos descriptivos de las variables cuantitativas
Variable Media Mediana Desv Minimo Maximo CV Asimetria
preciom 433.27 330 328.93 58 1,999 75.9 1.85
areaconst 174.04 122 142.32 30 1,745 81.8 2.73
parqueaderos 1.49 1 1.24 0 10 83.2 1.63
banios 3.13 3 1.41 1 10 45.2 0.98
habitaciones 3.63 3 1.43 1 10 39.3 1.82

La Tabla 7 evidencia asimetría positiva en las cinco variables. areaconst presenta el coeficiente más alto, con 2,73, seguida de preciom con 1,85. Solo banios, con 0,98, se ubica en un rango moderado.

Esta asimetría se refleja en la relación entre la media y la mediana. En preciom, la media asciende a 433 millones frente a una mediana de 330, y en areaconst a 174 m² frente a 122. En ambos casos el promedio se desplaza por encima del valor típico, de modo que la mediana describe mejor la oferta corriente que la media.

Los valores máximos confirman la presencia de observaciones extremas: 1.999 millones en preciom y 1.745 m² en areaconst, entre seis y catorce veces por encima de sus respectivas medianas. Su tratamiento se aborda en el apartado siguiente.

Nota: el coeficiente de variación de parqueaderos, de 83,2 %, no refleja la dispersión real del atributo en el mercado. La recodificación de los 1.605 datos faltantes como cero reduce la media y amplía la desviación, elevando el coeficiente por ambas vías.

5.1 Valores atípicos en preciom y areaconst

La detección de valores atípicos se realizó atendiendo a la naturaleza de cada variable. En preciom y areaconst, continuas y de rango amplio, se aplicó el método del rango intercuartílico, que considera atípicos los valores situados por debajo de \(Q_1 - 1{,}5 \times IQR\) o por encima de \(Q_3 + 1{,}5 \times IQR\), donde

\[IQR = Q_3 - Q_1\]

El método se apoya en cuartiles y no en la media, por lo que no asume normalidad ni se ve afectado por los propios valores extremos que busca identificar.

calc_stats <- function(x, nombre) {
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQR_val <- Q3 - Q1
  lim_inf <- Q1 - 1.5 * IQR_val
  lim_sup <- Q3 + 1.5 * IQR_val
  n_atip  <- sum(x < lim_inf | x > lim_sup, na.rm = TRUE)

  data.frame(
    Variable = nombre,
    Q1 = Q1, Q3 = Q3, IQR = IQR_val,
    `Límite superior` = lim_sup,
    Atipicos = n_atip,
    `% Atípicos` = round(100 * n_atip / sum(!is.na(x)), 1),
    check.names = FALSE, row.names = NULL
  )
}

atipicos <- rbind(
  calc_stats(vivienda_dep$preciom,   "preciom"),
  calc_stats(vivienda_dep$areaconst, "areaconst")
)

tabla(atipicos, caption = "Tabla 8. Detección de valores atípicos por el método IQR")
Tabla 8. Detección de valores atípicos por el método IQR
Variable Q1 Q3 IQR Límite superior Atipicos % Atípicos
preciom 220 540 320 1,020.0 547 6.6
areaconst 80 227 147 447.5 428 5.2

El método identifica 547 registros atípicos en preciom, el 6,6 % de la base, y 428 en areaconst, el 5,2 %. La proporción es consistente con la asimetría reportada en la Tabla 7 y se concentra en la cola derecha de ambas distribuciones.

p1 <- ggplot(vivienda_dep, aes(x = "", y = preciom)) +
  geom_boxplot(fill = ACENTO, alpha = 0.7, outlier.colour = NARANJA) +
  labs(title = "a) Precio de oferta", y = "Millones COP", x = "")

p2 <- ggplot(vivienda_dep, aes(x = "", y = areaconst)) +
  geom_boxplot(fill = ACENTO, alpha = 0.7, outlier.colour = NARANJA) +
  labs(title = "b) Área construida", y = "m²", x = "")

p3 <- ggplot(vivienda_dep, aes(x = preciom)) +
  geom_histogram(bins = 40, fill = AZUL, colour = "white") +
  labs(title = "c) Distribución del precio", x = "Millones COP", y = "Frecuencia")

(p1 + p2) / p3
Figura 2. Distribución de preciom y areaconst

Figura 2. Distribución de preciom y areaconst

Como se observa en la Figura 2, ambas variables presentan una concentración de observaciones en los valores bajos y una cola derecha extensa. El histograma del precio muestra además un escalonamiento propio de la fuente: los anuncios reportan cifras redondeadas, lo que genera acumulaciones en valores como 250, 300 y 350 millones.

La revisión de los registros situados por encima del límite superior descarta que se trate de errores de captura. Los registros examinados corresponden a inmuebles de estratos 5 y 6, ubicados principalmente en la Zona Oeste y la Zona Norte, con áreas construidas amplias y entre tres y seis baños. El perfil es internamente coherente y corresponde al segmento alto del mercado.

Por consiguiente, estos registros se conservan. Su eliminación supondría excluir el segmento de mayor valor de la oferta, precisamente uno de los que el análisis busca caracterizar, y resultaría inconsistente con el criterio adoptado en la depuración, donde solo se eliminaron los registros que no correspondían a inmuebles reales.

Nota: la conservación de estos valores tiene una consecuencia para las técnicas siguientes. El análisis de componentes principales y el de conglomerados operan sobre distancias euclidianas, en las que los valores extremos ejercen una influencia desproporcionada. La estandarización por puntaje Z no corrige esa influencia, dado que la desviación estándar empleada como divisor se encuentra a su vez elevada por dichos valores. La conveniencia de aplicar una transformación logarítmica se evalúa en el apartado de componentes principales.

5.2 Valores extremos en las variables discretas

En parqueaderos, banios y habitaciones el método del rango intercuartílico no resulta apropiado. Al tomar entre diez y once valores distintos, su recorrido es corto y el criterio clasificaría como atípica una proporción considerable de observaciones sin que ello represente una anomalía real. Estas variables se examinan mediante tablas de frecuencia.

frec_discretas <- rbind(
  resumen_cat(vivienda_dep$parqueaderos, "parqueaderos"),
  resumen_cat(vivienda_dep$banios,       "banios"),
  resumen_cat(vivienda_dep$habitaciones, "habitaciones")
)

tabla(frec_discretas, caption = "Tabla 9. Distribución de frecuencias de las variables discretas")
Tabla 9. Distribución de frecuencias de las variables discretas
Variable Categoria Frecuencia Porcentaje
parqueaderos 0 1,555 18.9
parqueaderos 1 3,144 38.1
parqueaderos 2 2,462 29.9
parqueaderos 3 519 6.3
parqueaderos 4 382 4.6
parqueaderos 5 68 0.8
parqueaderos 6 67 0.8
parqueaderos 7 18 0.2
parqueaderos 8 17 0.2
parqueaderos 9 4 0.0
parqueaderos 10 7 0.1
banios 1 494 6.0
banios 2 2,935 35.6
banios 3 1,988 24.1
banios 4 1,448 17.6
banios 5 888 10.8
banios 6 312 3.8
banios 7 107 1.3
banios 8 47 0.6
banios 9 15 0.2
banios 10 9 0.1
habitaciones 1 58 0.7
habitaciones 2 926 11.2
habitaciones 3 4,092 49.6
habitaciones 4 1,728 21.0
habitaciones 5 678 8.2
habitaciones 6 318 3.9
habitaciones 7 172 2.1
habitaciones 8 138 1.7
habitaciones 9 83 1.0
habitaciones 10 50 0.6

La Tabla 9 muestra distribuciones concentradas en los valores bajos, con modas en un parqueadero, dos baños y tres habitaciones. Las categorías superiores presentan frecuencias reducidas pero decrecientes de forma gradual, sin discontinuidades que sugieran errores de captura.

La aplicación del método IQR resultaría inadecuada en estas variables. En habitaciones, con \(Q_1 = 3\) y \(Q_3 = 4\), el límite superior se situaría en 5,5 y clasificaría como atípicos 761 registros, el 9,2 % de la base, entre los que se encuentran viviendas de seis o más habitaciones que corresponden a inmuebles corrientes en los estratos altos.

En consecuencia, los valores de las tres variables se conservan en su totalidad.

5.3 Verificación del supuesto de normalidad

El análisis de componentes principales se construye sobre la matriz de correlaciones. El coeficiente de Pearson, empleado habitualmente para estimarla, se calcula a partir de la media y la desviación estándar, por lo que supone que las variables siguen una distribución normal. De no cumplirse ese supuesto, corresponde emplear un coeficiente no paramétrico como el de Spearman.

La verificación se realiza mediante la prueba de Anderson-Darling. La elección obedece a dos razones. En primer lugar, la prueba de Shapiro-Wilk, de uso frecuente, admite un máximo de 5.000 observaciones y la base cuenta con 8.243. En segundo lugar, el estadístico de Anderson-Darling pondera con mayor peso el comportamiento de las colas de la distribución, que es precisamente donde las variables de este conjunto presentan mayor apartamiento, según lo evidenciado por los coeficientes de asimetría de la Tabla 7.

Las hipótesis contrastadas son:

\[H_0: \text{la variable sigue una distribución normal}\]

\[H_1: \text{la variable no sigue una distribución normal}\]

Se adopta un nivel de significancia de 0,05.

normalidad <- data.frame(
  Variable = vars_cuant,
  Estadistico = sapply(vivienda_dep[vars_cuant],
                       function(x) round(nortest::ad.test(x)$statistic, 2)),
  `Valor p` = sapply(vivienda_dep[vars_cuant],
                     function(x) format.pval(nortest::ad.test(x)$p.value,
                                             digits = 3, eps = 0.001)),
  check.names = FALSE, row.names = NULL
)

tabla(normalidad, caption = "Tabla 10. Prueba de normalidad de Anderson-Darling")
Tabla 10. Prueba de normalidad de Anderson-Darling
Variable Estadistico Valor p
preciom 455.88 <0.001
areaconst 505.25 <0.001
parqueaderos 401.73 <0.001
banios 334.81 <0.001
habitaciones 652.60 <0.001

La Tabla 10 evidencia el rechazo de la hipótesis nula en las cinco variables, con valores p inferiores a 0,001. Los estadísticos oscilan entre 334,81 en banios y 652,60 en habitaciones, muy por encima del valor crítico de referencia, que se sitúa alrededor de 0,75 para un nivel de significancia de 0,05. Ninguna de las cinco variables cuantitativas sigue una distribución normal.

Con 8.243 observaciones, cualquier prueba de normalidad tiende a rechazar la hipótesis nula, incluso cuando el apartamiento es pequeño. Por eso el resultado se contrasta con los coeficientes de asimetría de la Tabla 7: 1,85 en preciom y 2,73 en areaconst. Ambas medidas coinciden, de modo que el rechazo no es un efecto del tamaño de la muestra.

La matriz de correlaciones se estima entonces con el coeficiente de Spearman, que no exige normalidad porque se calcula sobre los rangos de las observaciones y no sobre sus valores.

Trabajar con rangos aporta además una ventaja frente a los valores atípicos conservados en el apartado anterior. Un inmueble de 1.999 millones ocupa la última posición del ordenamiento, con independencia de qué tan alejado esté del resto, de modo que su magnitud no distorsiona la estimación.

5.4 Matriz de correlaciones

El análisis de componentes principales opera reduciendo la información redundante entre variables, de modo que su aplicación solo resulta pertinente cuando existe correlación entre ellas. Se examina entonces la matriz de correlaciones de las cinco variables cuantitativas, estimada mediante el coeficiente de Spearman conforme a lo establecido en el apartado anterior.

mat_cor <- cor(vivienda_dep[vars_cuant], method = "spearman", use = "complete.obs")

tabla(round(mat_cor, 3), caption = "Tabla 11. Matriz de correlaciones de Spearman")
Tabla 11. Matriz de correlaciones de Spearman
preciom areaconst parqueaderos banios habitaciones
preciom 1.00 0.82 0.67 0.78 0.44
areaconst 0.82 1.00 0.52 0.79 0.67
parqueaderos 0.67 0.52 1.00 0.55 0.24
banios 0.78 0.79 0.55 1.00 0.62
habitaciones 0.44 0.67 0.24 0.62 1.00
corrplot(mat_cor, method = "ellipse", type = "upper",
         addCoef.col = "black", tl.col = AZUL, number.cex = 0.8)
Figura 3. Matriz de correlaciones de las variables cuantitativas

Figura 3. Matriz de correlaciones de las variables cuantitativas

La Tabla 11 evidencia correlaciones positivas entre las cinco variables, con valores que oscilan entre 0,24 y 0,82. La existencia de asociación entre ellas es la condición que justifica la aplicación del análisis de componentes principales.

El precio de oferta se asocia principalmente con el tamaño del inmueble. La correlación más alta corresponde a preciom con areaconst, con 0,82, seguida de banios con 0,78 y parqueaderos con 0,67. La asociación con habitaciones, de 0,44, resulta considerablemente menor.

Como se observa en la Figura 3, la asociación más débil se presenta entre parqueaderos y habitaciones, con 0,24. El número de habitaciones responde al tamaño del grupo familiar, mientras que la disponibilidad de parqueaderos depende de las características de la construcción, de modo que ambos atributos varían con relativa independencia.

La matriz de Spearman se emplea para verificar la existencia de asociación entre las variables, que es la condición de aplicación del análisis de componentes principales. El cálculo de los componentes se realiza sobre las variables estandarizadas y no sobre esta matriz.

Conviene precisar el alcance del supuesto de normalidad. El análisis de componentes principales es una técnica descriptiva de reducción de dimensión y no requiere normalidad para su aplicación; el supuesto condiciona la interpretación del coeficiente de Pearson como medida de asociación, razón por la cual la matriz se reporta con el coeficiente de Spearman.

6 Análisis de componentes principales

Las cinco variables cuantitativas se estandarizan mediante el puntaje Z, dado que están expresadas en unidades distintas. Sin esa transformación preciom, con desviación de 328,93 millones, dominaría el análisis frente a banios, con desviación de 1,41, por efecto de la escala y no de su relevancia.

\[Z = \frac{X - \bar{X}}{s}\]

No se aplicó la transformación logarítmica anunciada en el apartado de valores atípicos. La transformación reduciría la asimetría de preciom y areaconst, pero las cargas de los componentes quedarían expresadas sobre el logaritmo de esas variables, lo que dificulta su lectura en términos del mercado. Se conserva entonces la escala original y se reconoce la influencia de los valores extremos como una limitación, que se retoma en el apartado final.

vivienda_z <- scale(vivienda_dep[vars_cuant])

acp <- prcomp(vivienda_z)

varianza <- data.frame(
  Componente = paste0("PC", 1:5),
  `Desviación estándar` = round(acp$sdev, 3),
  `Valor propio` = round(acp$sdev^2, 3),
  `% Varianza` = round(100 * acp$sdev^2 / sum(acp$sdev^2), 2),
  `% Acumulado` = round(cumsum(100 * acp$sdev^2 / sum(acp$sdev^2)), 2),
  check.names = FALSE, row.names = NULL
)

tabla(varianza, caption = "Tabla 12. Varianza explicada por los componentes principales")
Tabla 12. Varianza explicada por los componentes principales
Componente Desviación estándar Valor propio % Varianza % Acumulado
PC1 1.78 3.17 63.43 63.43
PC2 0.96 0.92 18.39 81.83
PC3 0.64 0.41 8.14 89.96
PC4 0.56 0.31 6.28 96.24
PC5 0.43 0.19 3.76 100.00

6.1 Selección del número de componentes

La elección del número de componentes se apoya en tres criterios: el valor propio asociado a cada componente, el porcentaje de varianza acumulada y la inspección del gráfico de sedimentación.

fviz_eig(acp, addlabels = TRUE, barfill = ACENTO, barcolor = ACENTO) +
  labs(title = "", x = "Componentes", y = "Porcentaje de varianza explicada")
Figura 4. Gráfico de sedimentación de los componentes principales

Figura 4. Gráfico de sedimentación de los componentes principales

El primer componente concentra el 63,4 % de la varianza y el segundo un 18,4 % adicional, de modo que ambos acumulan el 81,8 %, cifra que supera el rango de referencia del 70 % al 80 %. Como se observa en la Figura 4, la pendiente desciende de forma pronunciada entre el primer y el segundo componente y se aplana a partir del tercero, cuyo aporte es de 8,1 %.

El criterio del valor propio mayor que uno retendría únicamente el primer componente, dado que el segundo alcanza 0,92. No obstante, ese valor se sitúa muy próximo al umbral y su exclusión impediría representar los individuos en un plano factorial, elemento necesario para el análisis posterior de conglomerados.

Se retienen entonces dos componentes, que conservan el 81,8 % de la varianza total.

6.2 Interpretación de los componentes

Las cargas indican la influencia de cada variable sobre cada componente y permiten atribuirles un significado.

cargas <- as.data.frame(round(acp$rotation[, 1:2], 3))
cargas <- cbind(Variable = rownames(cargas), cargas)
rownames(cargas) <- NULL

tabla(cargas, caption = "Tabla 13. Cargas de las variables en los dos primeros componentes")
Tabla 13. Cargas de las variables en los dos primeros componentes
Variable PC1 PC2
preciom 0.48 0.35
areaconst 0.49 -0.11
parqueaderos 0.41 0.54
banios 0.50 -0.14
habitaciones 0.36 -0.75

En el primer componente las cinco variables presentan cargas positivas y de magnitud similar, entre 0,355 y 0,497. El componente ordena los inmuebles según su tamaño y dotación: valores altos corresponden a viviendas amplias, con mayor número de baños, habitaciones y parqueaderos, y de mayor precio. Se denomina entonces tamaño y capacidad de la vivienda.

El segundo componente opone signos. habitaciones presenta la carga más elevada en magnitud, con −0,746, frente a parqueaderos con 0,536 y preciom con 0,354. Las variables areaconst y banios registran cargas próximas a cero, de modo que no intervienen en su definición.

Este componente diferencia inmuebles de tamaño comparable según la distribución de su espacio. Valores negativos corresponden a viviendas que reparten el área construida en un mayor número de habitaciones, mientras que los valores positivos corresponden a inmuebles con menos habitaciones, más parqueaderos y precio superior. Se denomina distribución del espacio interior.

6.3 Contribución y calidad de representación

A continuación se examina la contribución de cada variable a los dos componentes retenidos y su calidad de representación en el plano factorial. La contribución corresponde a la carga al cuadrado normalizada sobre la suma del componente, de modo que los aportes de cada eje suman 100 %. La calidad de representación, o \(\cos^2\), compara la longitud de la proyección de la variable con su distancia original.

\[\text{Contribución de } X_j \text{ a } PC_i = \frac{a_{ij}^2}{\sum_k a_{ik}^2}\]

acp_fm <- PCA(vivienda_dep[vars_cuant], scale.unit = TRUE, ncp = 2, graph = FALSE)

contrib <- data.frame(
  Variable = rownames(acp_fm$var$contrib),
  `Contrib. PC1` = round(acp_fm$var$contrib[, 1], 2),
  `Contrib. PC2` = round(acp_fm$var$contrib[, 2], 2),
  `cos2 PC1`     = round(acp_fm$var$cos2[, 1], 3),
  `cos2 PC2`     = round(acp_fm$var$cos2[, 2], 3),
  check.names = FALSE, row.names = NULL
)

tabla(contrib, caption = "Tabla 14. Contribución y calidad de representación de las variables")
Tabla 14. Contribución y calidad de representación de las variables
Variable Contrib. PC1 Contrib. PC2 cos2 PC1 cos2 PC2
preciom 22.67 12.56 0.72 0.12
areaconst 23.64 1.20 0.75 0.01
parqueaderos 16.43 28.71 0.52 0.26
banios 24.68 1.88 0.78 0.02
habitaciones 12.57 55.65 0.40 0.51

banios, areaconst y preciom aportan cada una alrededor de la cuarta parte del primer componente, sin que ninguna predomine, lo que corresponde a un eje que resume el tamaño general del inmueble.

El segundo componente lo define habitaciones, con una contribución de 55,65 %, seguida de parqueaderos con 28,71 %. Las aportaciones de areaconst y banios son inferiores al 2 %.

La calidad de representación muestra que habitaciones es la variable menos explicada por el primer componente, con un \(\cos^2\) de 0,40, y la mejor explicada por el segundo, con 0,51. Retener el segundo componente permite entonces representar adecuadamente un atributo que el primero recoge de forma parcial.

fviz_pca_var(acp, col.var = "contrib",
             gradient.cols = c(GRIS, ACENTO, AZUL),
             repel = TRUE) +
  labs(title = "")
Figura 5. Círculo de correlaciones de las variables

Figura 5. Círculo de correlaciones de las variables

La Figura 5 representa las variables en el plano factorial. La cercanía de cada vector al borde del círculo indica su calidad de representación, mientras que el color corresponde a su contribución. Ambos indicadores son distintos y se leen sobre el mismo gráfico.

6.4 Proyección de los individuos en el plano factorial

Los 8.243 inmuebles se proyectan sobre el plano definido por los dos componentes. La nube se colorea según estrato y según tipo, que no intervinieron en el cálculo de los componentes.

fviz_pca_ind(acp,
             geom.ind = "point",
             col.ind = factor(vivienda_dep$estrato),
             addEllipses = TRUE,
             pointsize = 0.8, alpha.ind = 0.3,
             legend.title = "Estrato") +
  labs(title = "")
Figura 6. Individuos en el plano factorial según estrato

Figura 6. Individuos en el plano factorial según estrato

En la Figura 6 las elipses se desplazan hacia la derecha a medida que aumenta el estrato. El estrato 3 se ubica en los valores bajos del primer componente y el estrato 6 en los altos. Esto es consistente con la forma en que se asigna el estrato, que depende de las características de la vivienda y de su entorno.

El segundo componente también los diferencia. El estrato 6 se desplaza hacia los valores positivos, donde están los parqueaderos, y el estrato 3 hacia los negativos, donde están las habitaciones.

Las elipses de los estratos 3, 4 y 5 se superponen bastante. El estrato desplaza los perfiles, no los separa.

fviz_pca_ind(acp,
             geom.ind = "point",
             col.ind = vivienda_dep$tipo,
             addEllipses = TRUE,
             pointsize = 0.8, alpha.ind = 0.3,
             palette = c(ACENTO, NARANJA),
             legend.title = "Tipo") +
  labs(title = "")
Figura 7. Individuos en el plano factorial según tipo de vivienda

Figura 7. Individuos en el plano factorial según tipo de vivienda

La Figura 7 muestra una diferencia de dispersión y no de posición. La elipse de los apartamentos es pequeña y se concentra cerca del origen. La de las casas es mucho más amplia en los dos componentes.

Los apartamentos son un producto homogéneo. Las casas van desde inmuebles parecidos a un apartamento hasta los valores más altos de la nube, y se extienden hacia el lado de las habitaciones en el segundo componente.

El tipo de vivienda informa sobre el perfil del inmueble cuando se trata de un apartamento, pero no cuando se trata de una casa.

El análisis de componentes principales reduce las cinco variables a dos componentes que conservan el 81,8 % de la varianza. El primero, definido por banios, areaconst y preciom con contribuciones cercanas al 23 % cada una, resume el tamaño y la dotación del inmueble y concentra por sí solo el 63,4 % de la varianza. El segundo lo define habitaciones, con una contribución de 55,65 %, en oposición a parqueaderos con 28,71 %, y distingue entre inmuebles de tamaño comparable según cómo se reparte el espacio construido.

preciom presenta un \(\cos^2\) de 0,72 en el primer componente, de modo que el precio de oferta queda explicado principalmente por la dimensión de tamaño. habitaciones es la única variable cuya información reside mayoritariamente en el segundo componente, con un \(\cos^2\) de 0,51 frente a 0,40 en el primero.

Los atributos de tamaño concentran entonces la mayor parte de la información sobre el precio de oferta, mientras que el número de habitaciones aporta una dimensión de diferenciación independiente de la magnitud del

6.5 Casos extremos

Con el fin de precisar el sentido de los componentes, se examinan los inmuebles situados en los extremos de cada eje.

coord <- as.data.frame(acp$x[, 1:2])

extremos <- rbind(
  vivienda_dep[order(coord$PC1)[1:2], ],
  vivienda_dep[order(-coord$PC1)[1:2], ],
  vivienda_dep[order(coord$PC2)[1:2], ],
  vivienda_dep[order(-coord$PC2)[1:2], ]
)

extremos_tab <- data.frame(
  Posición = c("PC1 mínimo", "PC1 mínimo", "PC1 máximo", "PC1 máximo",
               "PC2 mínimo", "PC2 mínimo", "PC2 máximo", "PC2 máximo"),
  extremos[c("tipo", "zona", "estrato", "preciom", "areaconst",
             "banios", "habitaciones", "parqueaderos")],
  row.names = NULL
)

tabla(extremos_tab, caption = "Tabla 15. Inmuebles situados en los extremos de los componentes")
Tabla 15. Inmuebles situados en los extremos de los componentes
Posición tipo zona estrato preciom areaconst banios habitaciones parqueaderos
PC1 mínimo Apartamento Zona Sur 4 108 40 1 1 0
PC1 mínimo Apartamento Zona Sur 5 93 48 1 1 0
PC1 máximo Casa Zona Sur 6 1,800 1,586 4 5 10
PC1 máximo Casa Zona Sur 6 1,600 1,600 6 6 3
PC2 mínimo Casa Zona Sur 3 470 366 10 10 0
PC2 mínimo Casa Zona Centro 4 610 750 8 10 0
PC2 máximo Casa Zona Sur 6 1,600 330 5 3 10
PC2 máximo Casa Zona Sur 6 1,200 360 6 4 10

Los inmuebles situados en el extremo inferior del primer componente son apartamentos de 40 y 48 m², con un baño, una habitación y sin parqueadero. En el extremo superior aparecen casas de estrato 6 con áreas cercanas a 1.600 m², hasta diez parqueaderos y precios entre 1.600 y 1.800 millones. Todos los atributos aumentan de forma conjunta, lo que confirma la interpretación del componente como una medida de tamaño y capacidad.

El segundo componente diferencia inmuebles de dimensiones comparables. En su extremo inferior se ubican casas de 366 y 750 m² con diez habitaciones y ningún parqueadero. En el superior, casas de 330 y 360 m² con tres o cuatro habitaciones y diez parqueaderos. Las cuatro corresponden a viviendas amplias y de precio elevado, de modo que la diferencia entre ellas no reside en su magnitud sino en la distribución del espacio construido.

7 Análisis de conglomerados

El análisis de conglomerados agrupa los inmuebles en segmentos internamente homogéneos y diferenciados entre sí. Se emplea el algoritmo k-medias, que asigna cada observación al centroide más cercano y recalcula los centroides hasta alcanzar la convergencia, minimizando la varianza dentro de cada grupo.

Se utilizan las mismas cinco variables cuantitativas del análisis de componentes principales, estandarizadas mediante el puntaje Z, dado que el algoritmo opera sobre distancias euclidianas y es sensible a las diferencias de escala.

7.1 Selección del número de conglomerados

El algoritmo requiere fijar el número de grupos de antemano. Se aplican dos criterios: el método del codo, que examina la reducción de la suma de cuadrados dentro de los conglomerados a medida que aumenta el número de grupos, y el coeficiente de silueta, que mide para cada observación la diferencia entre su distancia media al propio grupo y al grupo vecino más cercano.

\[s(i) = \frac{b(i) - a(i)}{\max\{a(i), b(i)\}}\]

donde \(a(i)\) corresponde a la distancia media respecto de las observaciones del mismo conglomerado y \(b(i)\) a la del conglomerado más próximo. Valores cercanos a 1 indican una asignación inequívoca.

set.seed(1234)

g1 <- fviz_nbclust(vivienda_z, kmeans, method = "wss", k.max = 8) +
  labs(title = "a) Método del codo", subtitle = "",
       x = "Número de conglomerados", y = "Suma de cuadrados intra")

g2 <- fviz_nbclust(vivienda_z, kmeans, method = "silhouette", k.max = 8) +
  labs(title = "b) Coeficiente de silueta", subtitle = "",
       x = "Número de conglomerados", y = "Silueta promedio")

g1 / g2
Figura 8. Criterios para la selección del número de conglomerados

Figura 8. Criterios para la selección del número de conglomerados

Como se observa en la Figura 8, la suma de cuadrados desciende de forma pronunciada al pasar de uno a dos conglomerados y la reducción se atenúa a partir del cuarto. El coeficiente de silueta alcanza su valor máximo en dos conglomerados, con 0,46, y disminuye de manera sostenida hasta 0,24 en ocho.

Ambos criterios convergen en dos conglomerados, que es el valor adoptado.

7.2 Perfil de los conglomerados

Se ajusta el modelo con dos conglomerados y se caracteriza cada uno en las unidades originales de las variables, lo que permite interpretar los segmentos en términos del mercado y no de puntuaciones estandarizadas.

set.seed(1234)
km <- kmeans(vivienda_z, centers = 2, nstart = 50)

vivienda_dep$cluster <- factor(km$cluster)

perfil <- aggregate(vivienda_dep[vars_cuant],
                    by = list(Conglomerado = vivienda_dep$cluster),
                    FUN = median)

perfil$n <- as.integer(table(vivienda_dep$cluster))
perfil$`%` <- round(100 * perfil$n / nrow(vivienda_dep), 1)

tabla(perfil, caption = "Tabla 16. Perfil de los conglomerados en unidades originales (medianas)")
Tabla 16. Perfil de los conglomerados en unidades originales (medianas)
Conglomerado preciom areaconst parqueaderos banios habitaciones n %
1 695 296 2 5 4 2,461 29.9
2 260 93 1 2 3 5,782 70.1

Los perfiles se describen mediante la mediana y no la media, dado que las variables presentan asimetría positiva, según lo establecido en la Tabla 7.

El primer conglomerado agrupa 2.461 inmuebles, el 29,9 % de la base, con precio mediano de 695 millones, 296 m² de área construida, cinco baños y dos parqueaderos. El segundo reúne 5.782 inmuebles, el 70,1 %, con precio mediano de 260 millones, 93 m², dos baños y un parqueadero.

La diferencia de precio entre ambos es de 2,7 veces y la de área construida de 3,2 veces. Todas las variables se ordenan en el mismo sentido, sin inversiones: el conglomerado de mayor precio presenta también mayor área y mejor dotación en los tres atributos de conteo.

No obstante, el precio por metro cuadrado invierte esa relación. El primer conglomerado registra una mediana de 2,43 millones por m² y el segundo de 2,68, de modo que los inmuebles de menor tamaño se ofertan a un valor unitario superior.

La partición reproduce la estructura identificada en el análisis de componentes principales: los dos conglomerados se sitúan a cada lado del eje de tamaño y capacidad. Dado que ese componente concentra el 63,4 % de la varianza, los grupos obtenidos corresponden a tramos de un continuo de gama y no a poblaciones separadas.

7.3 Composición de los conglomerados

Los conglomerados se construyeron únicamente con las variables cuantitativas. Se examina a continuación su composición según tipo de vivienda, estrato y zona, que no intervinieron en la segmentación.

g_tipo <- ggplot(vivienda_dep, aes(x = cluster, fill = tipo)) +
  geom_bar(position = "fill") +
  scale_fill_manual(values = c(ACENTO, NARANJA), name = "Tipo") +
  labs(title = "a) Tipo", x = "Conglomerado", y = "Proporción")

g_estrato <- ggplot(vivienda_dep, aes(x = cluster, fill = factor(estrato))) +
  geom_bar(position = "fill") +
  scale_fill_brewer(palette = "Blues", name = "Estrato") +
  labs(title = "b) Estrato", x = "Conglomerado", y = "")

g_zona <- ggplot(vivienda_dep, aes(x = cluster, fill = zona)) +
  geom_bar(position = "fill") +
  scale_fill_brewer(palette = "Greys", name = "Zona") +
  labs(title = "c) Zona", x = "Conglomerado", y = "")

g_tipo + g_estrato + g_zona
Figura 9. Composición de los conglomerados según tipo, estrato y zona

Figura 9. Composición de los conglomerados según tipo, estrato y zona

Como se observa en la Figura 9, los dos conglomerados presentan composiciones opuestas en tipo de vivienda y en estrato, pero similares en zona.

El primer conglomerado está compuesto en un 71 % por casas, mientras que el segundo lo está en un 75 % por apartamentos. En cuanto al estrato, el primer conglomerado concentra el 49,3 % de sus inmuebles en estrato 6, proporción que en el segundo desciende al 13,2 %. Este último se distribuye principalmente entre los estratos 5 y 4, con 34,9 % y 31,4 % respectivamente.

La distribución por zona es equivalente en Zona Sur, que reúne cerca del 56 % de los inmuebles en ambos conglomerados, y en Zona Centro y Zona Oriente, con proporciones prácticamente iguales. Zona Oeste, en cambio, concentra el 22,1 % del primer conglomerado frente al 11,1 % del segundo, mientras que Zona Norte presenta la relación inversa, con 16,3 % y 25,7 % respectivamente.

La segmentación por características del inmueble no coincide con la división geográfica de la oferta, aunque Zona Oeste y Zona Norte muestran una composición diferenciada. Ambos segmentos coexisten en las mismas zonas de la ciudad, de modo que la localización no permite anticipar por sí sola a qué segmento pertenece un inmueble.

7.4 Representación de los conglomerados

Los conglomerados se proyectan sobre el plano de los dos primeros componentes principales, que resume el 81,8 % de la varianza de las variables empleadas en la segmentación.

fviz_cluster(km, data = vivienda_z,
             geom = "point", pointsize = 0.8, alpha = 0.3,
             ellipse.type = "convex",
             palette = c(NARANJA, ACENTO),
             ggtheme = tema_informe) +
  labs(title = "", x = "PC1 — Tamaño y capacidad", y = "PC2 — Distribución del espacio")
Figura 10. Conglomerados en el plano de los componentes principales

Figura 10. Conglomerados en el plano de los componentes principales

Como se observa en la Figura 10, la división entre los dos conglomerados es una línea recta que corta la nube a lo largo del primer componente. Las dos regiones se tocan sin que exista un espacio vacío entre ellas, y a ambos lados del corte la concentración de inmuebles sigue siendo alta.

Esto significa que el algoritmo no encontró dos grupos separados, sino que dividió en dos una nube continua. Si existieran dos mercados diferenciados, la zona de la frontera estaría despoblada. Los inmuebles situados a un lado y otro del corte se parecen entre sí más de lo que la clasificación sugiere.

El resultado es consistente con el análisis de componentes principales, donde el primer componente concentraba el 63,4 % de la varianza. Si los inmuebles varían principalmente a lo largo de una sola dimensión, lo esperable es un continuo y no grupos aislados.

8 Tablas de contingencia y análisis de correspondencias múltiples

Antes de representar las asociaciones entre las variables categóricas se verifica su existencia mediante la prueba chi-cuadrado de Pearson, que compara las frecuencias observadas en la tabla de contingencia con las esperadas bajo el supuesto de independencia.

\[\chi^2 = \sum \frac{(O - E)^2}{E}\]

Las hipótesis contrastadas para cada par de variables son:

\[H_0: \text{las dos variables son independientes}\]

\[H_1: \text{las dos variables no son independientes}\]

Se adopta un nivel de significancia de 0,05. Dado que el estadístico chi-cuadrado aumenta con el tamaño de la muestra, se acompaña de la V de Cramér como medida del tamaño del efecto:

\[V = \sqrt{\frac{\chi^2}{n \cdot \min(r-1,\, c-1)}}\]

que toma valores entre 0 y 1 con independencia del número de observaciones.

prueba_chi <- function(x, y, nombre) {
  t <- table(x, y)
  p <- chisq.test(t)
  v <- sqrt(as.numeric(p$statistic) / (sum(t) * (min(dim(t)) - 1)))

  data.frame(
    Cruce = nombre,
    `Chi-cuadrado` = round(as.numeric(p$statistic), 1),
    gl = as.integer(p$parameter),
    `Valor p` = format.pval(p$p.value, digits = 3, eps = 0.001),
    `V de Cramér` = round(v, 3),
    check.names = FALSE, row.names = NULL
  )
}

contingencia <- rbind(
  prueba_chi(vivienda_dep$zona, vivienda_dep$estrato, "Zona × Estrato"),
  prueba_chi(vivienda_dep$zona, vivienda_dep$tipo,    "Zona × Tipo"),
  prueba_chi(vivienda_dep$tipo, vivienda_dep$estrato, "Tipo × Estrato")
)

tabla(contingencia, caption = "Tabla 17. Pruebas de independencia entre las variables categóricas")
Tabla 17. Pruebas de independencia entre las variables categóricas
Cruce Chi-cuadrado gl Valor p V de Cramér
Zona × Estrato 3,801.8 12 <0.001 0.39
Zona × Tipo 688.2 4 <0.001 0.29
Tipo × Estrato 215.4 3 <0.001 0.16

Las tres pruebas rechazan la hipótesis de independencia con valores p inferiores a 0,001, de modo que existe asociación entre las tres variables categóricas.

La V de Cramér permite ordenar la intensidad de esas asociaciones. La más fuerte corresponde a zona y estrato, con 0,39, resultado consistente con el hecho de que la estratificación socioeconómica se asigna por sectores geográficos. Le siguen zona y tipo con 0,29, y tipo y estrato con 0,16.

Cabe señalar que, con 8.243 registros, la prueba chi-cuadrado rechaza la independencia ante desviaciones de magnitud reducida. Los valores de la V de Cramér indican que las asociaciones, aun siendo estadísticamente significativas, son de intensidad moderada: ninguna de las tres variables determina a las otras.

8.1 Preparación de las variables categóricas

El análisis de correspondencias múltiples se aplica sobre zona, tipo y estrato como variables activas. La variable barrio se incorpora como ilustrativa, dado que sus 433 categorías impedirían la lectura del mapa factorial y las modalidades con pocos registros ejercerían una influencia desproporcionada sobre la construcción de los ejes.

Se conservan con su denominación original los barrios con al menos 100 registros y las restantes categorías se agrupan bajo una etiqueta común.

frec_barrio  <- table(vivienda_dep$barrio)
barrios_frec <- names(frec_barrio[frec_barrio >= 100])

vivienda_dep$barrio_agr <- factor(
  ifelse(vivienda_dep$barrio %in% barrios_frec, vivienda_dep$barrio, "Otros")
)

vivienda_dep$zona      <- factor(vivienda_dep$zona)
vivienda_dep$tipo      <- factor(vivienda_dep$tipo)
vivienda_dep$estrato_f <- factor(vivienda_dep$estrato)

resumen_barrio <- data.frame(
  Concepto = c("Barrios en la base",
               "Barrios con 100 o más registros",
               "Cobertura de la oferta (%)"),
  Valor = c(length(frec_barrio),
            length(barrios_frec),
            round(100 * sum(vivienda_dep$barrio %in% barrios_frec) / nrow(vivienda_dep), 1))
)

tabla(resumen_barrio, caption = "Tabla 18. Agrupamiento de la variable barrio")
Tabla 18. Agrupamiento de la variable barrio
Concepto Valor
Barrios en la base 433.0
Barrios con 100 o más registros 15.0
Cobertura de la oferta (%) 49.2

8.2 Análisis de correspondencias múltiples

El análisis de correspondencias múltiples representa las modalidades de las variables categóricas en un espacio de baja dimensión, en el que la proximidad entre categorías indica que tienden a presentarse conjuntamente en los mismos inmuebles. Se aplica sobre zona, tipo y estrato como variables activas, e incorpora barrio y el conglomerado obtenido en el apartado anterior como variables ilustrativas, que se proyectan sobre los ejes sin intervenir en su construcción.

levels(vivienda_dep$estrato_f) <- paste("Estrato", 3:6)
levels(vivienda_dep$cluster)   <- c("Conglomerado 1", "Conglomerado 2")

acm <- MCA(vivienda_dep[c("zona", "tipo", "estrato_f", "barrio_agr", "cluster")],
           quali.sup = 4:5,
           graph = FALSE)

inercia <- data.frame(
  Dimensión = paste("Dim", 1:5),
  `Valor propio` = round(acm$eig[1:5, 1], 4),
  `% Inercia`    = round(acm$eig[1:5, 2], 2),
  `% Acumulado`  = round(acm$eig[1:5, 3], 2),
  check.names = FALSE, row.names = NULL
)

tabla(inercia, caption = "Tabla 19. Inercia explicada por dimensión")
Tabla 19. Inercia explicada por dimensión
Dimensión Valor propio % Inercia % Acumulado
Dim 1 0.56 21.05 21.05
Dim 2 0.45 17.00 38.05
Dim 3 0.38 14.27 52.32
Dim 4 0.33 12.51 64.83
Dim 5 0.32 12.16 76.99

Los dos primeros ejes concentran el 21,05 % y el 17,00 % de la inercia, con un acumulado del 38,05 %. Estos porcentajes no son comparables con los del análisis de componentes principales: en un análisis de correspondencias múltiples la inercia total depende del número de modalidades y no del grado de asociación entre las variables. Con 11 modalidades activas y 3 variables, la inercia total asciende a 2,67 y se distribuye entre 8 dimensiones, de modo que ninguna concentra una proporción elevada aun cuando la asociación sea fuerte.

Se retienen los dos primeros ejes, que permiten representar las modalidades en un plano factorial.

fviz_mca_var(acm, repel = TRUE,
             col.var = AZUL, col.quali.sup = NARANJA,
             labelsize = 3, max.overlaps = 30) +
  labs(title = "")
Figura 11. Mapa factorial de las variables categóricas

Figura 11. Mapa factorial de las variables categóricas

8.3 Contribución de las categorías

A continuación se examina la contribución de cada categoría a la formación de los ejes y su calidad de representación en el plano factorial.

contrib_acm <- data.frame(
  Categoría = rownames(acm$var$contrib),
  `Contrib. Dim1` = round(acm$var$contrib[, 1], 2),
  `Contrib. Dim2` = round(acm$var$contrib[, 2], 2),
  `cos2 Dim1`     = round(acm$var$cos2[, 1], 3),
  `cos2 Dim2`     = round(acm$var$cos2[, 2], 3),
  check.names = FALSE, row.names = NULL
)

contrib_acm <- contrib_acm[order(-contrib_acm$`Contrib. Dim1`), ]

tabla(contrib_acm, caption = "Tabla 20. Contribución y calidad de representación de las categorías")
Tabla 20. Contribución y calidad de representación de las categorías
Categoría Contrib. Dim1 Contrib. Dim2 cos2 Dim1 cos2 Dim2
8 Estrato 3 30.65 4.92 0.62 0.08
4 Zona Oriente 23.82 6.32 0.42 0.09
3 Zona Oeste 9.89 32.75 0.20 0.52
7 Casa 9.42 0.23 0.26 0.00
11 Estrato 6 8.26 23.53 0.18 0.42
1 Zona Centro 6.39 1.03 0.11 0.01
6 Apartamento 5.88 0.14 0.26 0.00
2 Zona Norte 2.83 0.92 0.06 0.02
5 Zona Sur 1.47 9.69 0.06 0.31
10 Estrato 5 0.78 5.41 0.02 0.11
9 Estrato 4 0.60 15.06 0.01 0.28

La primera dimensión está definida principalmente por Estrato 3 y Zona Oriente, que aportan el 30,65 % y el 23,82 % de su inercia. Sumada la contribución de Zona Centro, con 6,39 %, la periferia de estrato bajo explica cerca del 61 % de este eje. Estas tres modalidades se sitúan en los valores positivos y quedan separadas del resto de la ciudad.

La segunda dimensión la definen Zona Oeste con 32,75 % y Estrato 6 con 23,53 %, en los valores positivos, frente a Estrato 4 con 15,06 % y Zona Sur con 9,69 % en el extremo opuesto. Distingue la concentración de estrato alto de la oferta de estratos intermedios.

Las modalidades Casa y Apartamento contribuyen de forma moderada a la primera dimensión, con 9,42 % y 5,88 %, y resultan irrelevantes en la segunda, con aportes inferiores al 0,25 %. El tipo de vivienda participa entonces en la separación de la periferia respecto del resto de la ciudad, pero no en la distinción entre estratos altos e intermedios. El resultado es coherente con la V de Cramér obtenida en la Tabla 16, la más baja de los tres cruces.

Zona Sur presenta una contribución reducida en la primera dimensión, de 1,47 %. Al concentrar el 56,8 % de la oferta, su composición por tipo y estrato coincide en buena medida con la del conjunto de la ciudad, de modo que se sitúa cerca del origen del plano.

La calidad de representación indica qué categorías admiten una lectura confiable. Estrato 3 alcanza un cos² de 0,62 en la primera dimensión y Zona Oeste de 0,52 en la segunda. Zona Norte, en cambio, registra valores de 0,06 y 0,02, de modo que su posición en el mapa no resulta interpretable.

Los barrios proyectados se distribuyen de forma consistente con la estructura descrita. Normandía, Santa Teresita, Los Cristales y Aguacatal se sitúan próximos a Zona Oeste y al estrato 6, mientras que Valle del Lili, El Refugio, El Caney y El Limonar lo hacen cerca de Zona Sur y de los estratos 4 y 5.

Los dos conglomerados se proyectan cerca del origen, con una separación reducida entre sí frente a la de las modalidades activas, que alcanzan valores superiores a 3 en la primera dimensión. La segmentación construida a partir de los atributos físicos y del precio no se diferencia de manera apreciable en el espacio de las variables categóricas.

9 Visualización integrada

Los resultados de los apartados anteriores se representan sobre la localización geográfica de los inmuebles, con el fin de examinar la distribución espacial de la segmentación obtenida. Se emplean las coordenadas de longitud y latitud, reservadas desde el diccionario de variables para este propósito.

El mapa representa una muestra aleatoria de 2.000 inmuebles. La restricción responde al tamaño del archivo generado y no afecta la lectura, dado que el mapa cumple una función ilustrativa de los hallazgos ya establecidos.

set.seed(1234)
muestra_mapa <- vivienda_dep[sample(nrow(vivienda_dep), 2000), ]

pal <- colorFactor(c(NARANJA, ACENTO), domain = muestra_mapa$cluster)

leaflet(muestra_mapa) |>
  addTiles() |>
  addCircleMarkers(
    lng = ~longitud, lat = ~latitud,
    color = ~pal(cluster), radius = 3,
    stroke = FALSE, fillOpacity = 0.6,
    popup = ~paste0("Precio: ", preciom, " millones<br>",
                    "Área: ", areaconst, " m²<br>",
                    "Estrato: ", estrato)
  ) |>
  addLegend(position = "bottomright", pal = pal,
            values = ~cluster, title = "Conglomerado")

Figura 12. Distribución espacial de los conglomerados.

La Figura 11 muestra los inmuebles de ambos conglomerados entremezclados en toda la extensión de la ciudad. No se identifican sectores en los que uno de los segmentos aparezca de forma exclusiva.

La oferta se distribuye siguiendo un eje norte-sur, con mayor densidad en el centro y el sur. El conglomerado 1 presenta una concentración relativamente mayor en el sector occidental, consistente con el 22,1 % que registra en Zona Oeste frente al 11,1 % del conglomerado 2.

El mapa confirma visualmente lo establecido en la Figura 9: la segmentación construida a partir de los atributos del inmueble no se corresponde con una división territorial de la oferta.

10 Conclusiones

La variabilidad de la oferta se concentra en el tamaño del inmueble. Dos componentes principales resumen el 81,8 % de la varianza de las cinco variables cuantitativas. El primero, que reúne área construida, número de baños y precio con contribuciones cercanas al 23 % cada una, explica por sí solo el 63,4 % y ordena los inmuebles según su tamaño y dotación. El precio de oferta queda explicado casi enteramente por esa dimensión, con un \(\cos^2\) de 0,72. El segundo componente, definido por el número de habitaciones, distingue inmuebles de tamaño comparable según cómo reparten su espacio construido.

La oferta se organiza en dos segmentos diferenciados. El primero reúne el 29,9 % de los inmuebles, con precio mediano de 695 millones, 296 m² y predominio de casas en un 71 %. El segundo concentra el 70,1 % restante, con 260 millones, 93 m² y predominio de apartamentos en un 75 %. La diferencia de precio entre ambos es de 2,7 veces, aunque el precio por metro cuadrado resulta superior en el segmento de menor tamaño, con 2,68 millones frente a 2,43.

Las variables categóricas presentan asociaciones de intensidad moderada. Las tres pruebas de independencia se rechazan, pero la V de Cramér ubica la asociación más fuerte entre zona y estrato, con 0,39, y la más débil entre tipo de vivienda y estrato, con 0,16. El análisis de correspondencias múltiples organiza el territorio en dos dimensiones: la primera aísla la periferia de estrato bajo, conformada por Zona Oriente y Zona Centro, y la segunda separa la concentración de estrato 6 en Zona Oeste de los estratos intermedios de Zona Sur.

La segmentación por atributos del inmueble no coincide con la estructura territorial de la oferta. Los dos conglomerados presentan distribuciones por zona equivalentes, se proyectan próximos al origen en el plano del análisis de correspondencias y aparecen entremezclados en el mapa. La gama del inmueble y su localización constituyen entonces dos dimensiones independientes del mercado, y ninguna de las dos por separado describe adecuadamente la oferta.

El tratamiento diferenciado de los datos faltantes permitió conservar el 99,05 % de los registros. La verificación del origen de las ausencias mostró que la falta de dato en piso no responde al tipo de vivienda y que el valor cero nunca se registra en parqueaderos, lo que sustentó decisiones distintas para cada variable. La eliminación por lista habría reducido la base al 57,8 %.

11 Recomendaciones

12 Recomendaciones

Dar más peso al área y a los baños que al número de habitaciones al fijar precios. En esta oferta el precio va de la mano sobre todo con el tamaño del inmueble y con la cantidad de baños. El número de habitaciones, que suele destacarse en los anuncios, acompaña mucho menos al precio: su correlación es de 0,44, frente a 0,82 del área construida. Al sugerir un precio de lista conviene mirar primero los metros cuadrados.

Valorar de forma distinta las casas y los apartamentos. Los apartamentos se parecen bastante entre sí, de modo que comparar con inmuebles similares funciona bien. Las casas son muy distintas unas de otras: bajo esa misma etiqueta hay desde viviendas pequeñas hasta las más grandes y costosas del mercado. Conviene revisar cada casa individualmente en lugar de aplicar un criterio general.

Organizar los equipos comerciales por tipo de producto y no por zona. Los dos grupos de inmuebles conviven en los mismos barrios. Un equipo asignado a una zona termina manejando al mismo tiempo apartamentos de 260 millones y casas de 695, que se venden a públicos distintos y con argumentos distintos. Especializar por segmento aprovecha mejor el conocimiento del vendedor.

No abandonar el segmento de vivienda compacta. Concentra el 70,1 % de los inmuebles ofertados y su precio por metro cuadrado es incluso algo mayor que el del segmento amplio: 2,68 millones frente a 2,43. El segmento de gama alta deja más dinero por operación, pero el volumen está del otro lado.

Buscar inmuebles de gama alta en la Zona Oeste. Es la única zona donde la composición de los dos segmentos difiere de forma apreciable: 22,1 % del segmento amplio frente a 11,1 % del compacto. La Zona Sur, con el 56,8 % de toda la oferta, es el mercado de volumen antes que el de producto premium.

Revisar qué pasa en la Zona Oriente y la Zona Centro. Entre las dos apenas reúnen el 5,7 % de los avisos y concentran el estrato 3. No se sabe si es porque hay poca vivienda en venta o porque esos propietarios no publican en este portal. Vale la pena averiguarlo con información propia: si es lo segundo, hay un mercado sin atender.

Registrar el cero cuando un inmueble no tenga parqueadero. En el 19,29 % de los avisos ese dato no aparece, y el cero no se escribe en ninguno de los 6.717 que sí lo reportan. Dejarlo en blanco hace que un inmueble sin parqueadero se vea igual que uno del que no se sabe.

13 Referencias

Kassambara, A., & Mundt, F. (2020). factoextra: Extract and visualize the results of multivariate data analyses. R package version 1.0.7.

Lê, S., Josse, J., & Husson, F. (2008). FactoMineR: An R package for multivariate analysis. Journal of Statistical Software, 25(1), 1–18.

Ortega Lenis, D. (2026). Modelos estadísticos para la toma de decisiones [material del curso]. Pontificia Universidad Javeriana Cali.

R Core Team (2026). R: A language and environment for statistical computing. R Foundation for Statistical Computing.

Rousseeuw, P. J. (1987). Silhouettes: A graphical aid to the interpretation and validation of cluster analysis. Journal of Computational and Applied Mathematics, 20, 53–65.

14 Anexos

14.1 Anexo A. Barrios agrupados en la categoría “Otros”

La categoría residual del análisis de correspondencias múltiples reúne los barrios con menos de 100 registros. Se listan a continuación los veinte de mayor frecuencia dentro de ese conjunto.

otros <- sort(frec_barrio[frec_barrio < 100], decreasing = TRUE)[1:20]

anexo_b <- data.frame(
  Barrio = names(otros),
  Registros = as.integer(otros),
  row.names = NULL
)

tabla(anexo_b, caption = "Tabla A1. Barrios más frecuentes agrupados en la categoría Otros")
Tabla A1. Barrios más frecuentes agrupados en la categoría Otros
Barrio Registros
ciudad 2000 95
caney 87
cristales 83
urbanización la flora 83
brisas de los 81
zona sur 74
nueva tequendama 73
quintas de don 72
versalles 70
santa isabel 63
parcelaciones pance 61
el peñon 60
el lido 59
torres de comfandi 57
capri 56
san fernando 53
juanamb√∫ 52
melendez 51
santa monica 51
villa del prado 50

14.2 Anexo B. Información de la sesión

Se documenta el entorno de cómputo empleado, con el fin de permitir la reproducción de los resultados.

sesion <- data.frame(
  Elemento = c("Versión de R", "Sistema operativo", "Semilla aleatoria"),
  Valor = c(R.version.string,
            Sys.info()[["sysname"]],
            "1234")
)

tabla(sesion, caption = "Tabla A2. Entorno de cómputo")
Tabla A2. Entorno de cómputo
Elemento Valor
Versión de R R version 4.6.1 (2026-06-24 ucrt)
Sistema operativo Windows
Semilla aleatoria 1234