1. Introducción y planteamiento del problema

Una empresa inmobiliaria de una gran ciudad quiere conocer mejor cómo funciona el mercado de viviendas para poder tomar decisiones más acertadas. Para esto, cuenta con una base de datos amplia que contiene información sobre diferentes propiedades residenciales que se encuentran disponibles en el mercado.

El objetivo de este informe es analizar estos datos desde diferentes puntos de vista, buscando identificar características, grupos y relaciones que permitan entender mejor el comportamiento del mercado de viviendas. Para realizar este análisis, se aplicarán tres técnicas de análisis multivariado:

  1. Análisis de Componentes Principales (PCA): nos ayudará a resumir la información y a identificar cuáles son las características que más influyen en las diferencias de precios y en la oferta de viviendas.
  2. Análisis de Conglomerados (Clustering): permitirá agrupar las propiedades que tienen características similares, para así conocer qué tipos de viviendas se encuentran y cómo se distribuyen según las zonas y los estratos.
  3. Análisis de Correspondencia: permitirá observar la relación entre variables como el tipo de vivienda, la zona y el barrio, con el fin de encontrar patrones que ayuden a entender mejor cómo se distribuyen las propiedades.

1.1 Objetivos

Objetivo general: realizar un análisis integral del mercado de vivienda urbana a partir de la base de datos disponible, aplicando técnicas de análisis multivariado que permitan resumir la información, identificar grupos de propiedades y encontrar relaciones entre variables, con el fin de apoyar la toma de decisiones estratégicas de la empresa inmobiliaria.

Objetivos específicos:

  • Identificar, mediante Análisis de Componentes Principales, cuáles características de las propiedades explican la mayor parte de la variación en precios y en la oferta de vivienda.
  • Agrupar las propiedades residenciales en segmentos homogéneos mediante un análisis de conglomerados, y describir el perfil de cada segmento según sus características y su distribución por zona.
  • Examinar, mediante un análisis de correspondencia, la relación entre el tipo de vivienda, la zona y el barrio, para identificar patrones de especialización de la oferta inmobiliaria.
  • Presentar los resultados de los análisis anteriores mediante gráficos y mapas que faciliten su comunicación a la dirección de la empresa.
  • Formular, a partir de los resultados obtenidos, conclusiones y recomendaciones estratégicas que aporten valor a la toma de decisiones de la empresa.

2. Carga de librerías y datos

Antes de comenzar con el análisis, primero debemos cargar los datos y las librerías de R que vamos a utilizar. Estas librerías nos permiten realizar los cálculos estadísticos, analizar la información y crear los gráficos que se mostrarán a lo largo del informe.

Cada librería tiene una función diferente y nos ayudará en una parte específica del análisis.

  • paqueteMODELOS: contiene los datos de vivienda, que son los que utilizaremos para realizar el análisis.
  • dplyr: nos permite organizar y trabajar con los datos de una manera más sencilla, por ejemplo, filtrando, agrupando y resumiendo la información mediante funciones como filter(), group_by() y summarise().
  • ggplot2: nos permite crear los gráficos que utilizaremos en el informe, como gráficos de dispersión y mapas, para mostrar los resultados de una manera más clara y fácil de entender.
  • FactoMineR: contiene las funciones que utilizaremos para realizar las tres técnicas principales del análisis: PCA(), MCA() y CA(). Cada una nos permite analizar los datos desde una perspectiva diferente.
  • factoextra: complementa a FactoMineR y nos ayuda a mostrar los resultados de una forma más clara y fácil de interpretar mediante diferentes gráficos, usando funciones como fviz_eig(), fviz_pca_var() y fviz_cluster().
  • cluster: proporciona herramientas que nos ayudan a realizar y analizar la agrupación de las propiedades según sus características.
  • knitr: se utiliza para mostrar las tablas de resultados de una forma más ordenada y fácil de leer, usando funciones como kable(), en lugar de mostrarlas directamente en la consola de R.
# Cargar el paquete con los datos (solo la primera vez es necesario instalar)
devtools::install_github("centro-magis/paqueteMODELOS", force = TRUE)
## 
## ── R CMD build ─────────────────────────────────────────────────────────────────
##          checking for file 'C:\Users\rayo1\AppData\Local\Temp\Rtmpc5bQnR\remotes56b443a473af\centro-magis-paqueteMODELOS-3b06257/DESCRIPTION' ...  ✔  checking for file 'C:\Users\rayo1\AppData\Local\Temp\Rtmpc5bQnR\remotes56b443a473af\centro-magis-paqueteMODELOS-3b06257/DESCRIPTION' (556ms)
##       ─  preparing 'paqueteMODELOS':
##    checking DESCRIPTION meta-information ...  ✔  checking DESCRIPTION meta-information
##       ─  checking for LF line-endings in source and make files and shell scripts
##   ─  checking for empty or unneeded directories
##       ─  building 'paqueteMODELOS_0.1.0.tar.gz'
##      
## 
library(paqueteMODELOS)

library(dplyr)        # manipulación de datos
library(ggplot2)       # visualización
library(FactoMineR)    # PCA, MCA, CA
library(factoextra)    # visualización de resultados multivariados
library(cluster)       # métodos de clustering
library(knitr)         # tablas

Una vez cargadas las librerías, importamos los datos de vivienda a R usando data("vivienda"). Después, utilizamos str() para conocer mejor cómo están organizados los datos, revisando cuántos registros y variables tenemos y qué tipo de información contiene cada una, como datos numéricos o categorías.

data("vivienda")
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>

El resultado anterior confirma que vivienda tiene 8,322 registros (cada uno corresponde a un anuncio de una propiedad) y 13 variables: un identificador (id), variables de ubicación (zona, barrio, longitud, latitud), características físicas de la propiedad (piso, estrato, areaconst, parqueaderos, banios, habitaciones), el tipo de vivienda (tipo) y el precio (preciom).


3. Análisis exploratorio inicial

Antes de aplicar las técnicas de análisis multivariado, es importante revisar la calidad y la distribución general de los datos: si existen valores faltantes, si las variables numéricas presentan rangos razonables, y cómo se distribuyen las variables categóricas. Este paso nos permite evitar construir el análisis sobre datos que no comprendemos bien o que contienen errores sin detectar.

cat("Dimensiones del conjunto de datos:", nrow(vivienda), "observaciones,",
    ncol(vivienda), "variables\n")
## Dimensiones del conjunto de datos: 8322 observaciones, 13 variables

3.1 Valores faltantes por variable

Revisamos cuántos valores faltantes (NA) tiene cada columna, ya que estos pueden generar problemas más adelante al aplicar las técnicas de análisis multivariado si no se tratan a tiempo:

# Valores faltantes por columna, en formato de tabla
na_conteo <- colSums(is.na(vivienda))
na_tabla <- data.frame(
  Variable  = names(na_conteo),
  NAs       = as.integer(na_conteo),
  Porcentaje = sprintf("%.1f%%", 100 * na_conteo / nrow(vivienda))
)
na_tabla <- na_tabla[order(-na_tabla$NAs), ]

kable(na_tabla, row.names = FALSE,
      col.names = c("Variable", "N° de NA", "% del total"),
      caption = "Valores faltantes por variable")
Valores faltantes por variable
Variable N° de NA % del total
piso 2638 31.7%
parqueaderos 1605 19.3%
id 3 0.0%
zona 3 0.0%
estrato 3 0.0%
areaconst 3 0.0%
banios 3 0.0%
habitaciones 3 0.0%
tipo 3 0.0%
barrio 3 0.0%
longitud 3 0.0%
latitud 3 0.0%
preciom 2 0.0%

Se pueden observar dos variables que tienen una cantidad importante de datos faltantes: piso, porque las casas no tienen número de piso y este dato solo aplica para los apartamentos, por lo que es un faltante esperado y no necesariamente un error; y parqueaderos, que se analizará con más detalle en la sección 3.3. Las demás variables tienen muy pocos datos faltantes, posiblemente debido a información que no estaba disponible en algunos registros.

3.2 Resumen estadístico y distribución de categorías

Revisamos algunos datos estadísticos, como el valor mínimo, máximo, promedio, mediana y cuartiles de las variables numéricas que utilizaremos en el PCA y el clustering. Esto nos permite conocer mejor los datos y detectar posibles valores que sean muy altos, muy bajos o poco realistas.

vars_num <- c("estrato", "preciom", "areaconst", "parqueaderos",
              "banios", "habitaciones")
summary(vivienda[, vars_num])
##     estrato         preciom         areaconst       parqueaderos   
##  Min.   :3.000   Min.   :  58.0   Min.   :  30.0   Min.   : 1.000  
##  1st Qu.:4.000   1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000  
##  Median :5.000   Median : 330.0   Median : 123.0   Median : 2.000  
##  Mean   :4.634   Mean   : 433.9   Mean   : 174.9   Mean   : 1.835  
##  3rd Qu.:5.000   3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000  
##  Max.   :6.000   Max.   :1999.0   Max.   :1745.0   Max.   :10.000  
##  NA's   :3       NA's   :2        NA's   :3        NA's   :1605    
##      banios        habitaciones   
##  Min.   : 0.000   Min.   : 0.000  
##  1st Qu.: 2.000   1st Qu.: 3.000  
##  Median : 3.000   Median : 3.000  
##  Mean   : 3.111   Mean   : 3.605  
##  3rd Qu.: 4.000   3rd Qu.: 4.000  
##  Max.   :10.000   Max.   :10.000  
##  NA's   :3        NA's   :3

También revisamos cómo se distribuyen las propiedades según algunas de las categorías más importantes, como zona y tipo, para conocer qué tan representadas están las diferentes zonas y tipos de vivienda dentro de los datos.

kable(table(vivienda$zona), col.names = c("Zona", "Frecuencia"),
      caption = "Distribución de propiedades por zona")
Distribución de propiedades por zona
Zona Frecuencia
Zona Centro 124
Zona Norte 1920
Zona Oeste 1198
Zona Oriente 351
Zona Sur 4726
kable(table(vivienda$tipo), col.names = c("Tipo", "Frecuencia"),
      caption = "Distribución de propiedades por tipo")
Distribución de propiedades por tipo
Tipo Frecuencia
Apartamento 5100
Casa 3219

Esto nos permite observar, antes de realizar el análisis, que las propiedades no se encuentran distribuidas de la misma manera en todas las zonas de la ciudad. Algunas zonas tienen muchos más anuncios que otras. Esta diferencia será importante más adelante, especialmente en el análisis de clustering y en el mapa geográfico.

Para los análisis que trabajan con datos numéricos, como el PCA y los conglomerados, utilizaremos las variables estrato, preciom, areaconst, parqueaderos, banios y habitaciones. Por otro lado, para el análisis de correspondencia utilizaremos las variables categóricas tipo, zona y barrio.

3.3 Tratamiento de valores faltantes en variables numéricas

Antes de realizar el PCA y el análisis de conglomerados, debemos asegurarnos de que las variables numéricas no tengan datos faltantes. Esto es importante porque funciones como PCA() y kmeans() pueden presentar errores cuando encuentran valores vacíos. Por eso, revisamos específicamente la cantidad de datos faltantes en las 6 variables numéricas que vamos a utilizar:

na_por_var <- colSums(is.na(vivienda[, vars_num]))
kable(data.frame(Variable = names(na_por_var), NAs = na_por_var,
                  Porcentaje = sprintf("%.1f%%", 100 * na_por_var / nrow(vivienda))),
      row.names = FALSE,
      caption = "Valores faltantes por variable numérica (antes de imputar)")
Valores faltantes por variable numérica (antes de imputar)
Variable NAs Porcentaje
estrato 3 0.0%
preciom 2 0.0%
areaconst 3 0.0%
parqueaderos 1605 19.3%
banios 3 0.0%
habitaciones 3 0.0%

La variable parqueaderos es la que tiene la mayor cantidad de datos faltantes, aproximadamente el 19% de los registros. Como se trata de datos obtenidos de anuncios inmobiliarios, podemos considerar que cuando el anunciante no indica la cantidad de parqueaderos, probablemente la propiedad no cuenta con uno. Por esta razón, en lugar de eliminar esos registros, reemplazamos los valores faltantes por 0.

En las demás variables numéricas hay muy pocos datos faltantes. En estos casos, decidimos eliminar los registros que tienen información incompleta, ya que son pocos y esto no afecta de manera importante la cantidad total de datos disponibles.

vivienda_imputado <- vivienda %>%
  mutate(parqueaderos = ifelse(is.na(parqueaderos), 0, parqueaderos))

# Conjunto de datos sin NA/NaN/Inf en las variables numéricas de interés,
# usado específicamente para PCA y clustering (las técnicas categóricas
# de la sección 6 siguen usando 'vivienda' completo, sin imputar)
vivienda_completo <- vivienda_imputado %>%
  filter(if_all(all_of(vars_num), ~ !is.na(.) & is.finite(.)))

cat("Observaciones originales:", nrow(vivienda), "\n")
## Observaciones originales: 8322
cat("Valores de 'parqueaderos' imputados con 0:",
    sum(is.na(vivienda$parqueaderos)), "\n")
## Valores de 'parqueaderos' imputados con 0: 1605
cat("Observaciones tras remover NA/NaN/Inf en las demás variables numéricas:",
    nrow(vivienda_completo), "\n")
## Observaciones tras remover NA/NaN/Inf en las demás variables numéricas: 8319
cat("Observaciones descartadas:",
    nrow(vivienda) - nrow(vivienda_completo),
    sprintf("(%.1f%% del total)",
            100 * (nrow(vivienda) - nrow(vivienda_completo)) / nrow(vivienda)),
    "\n")
## Observaciones descartadas: 3 (0.0% del total)

Con esta forma de trabajar, logramos conservar casi todos los registros originales, normalmente más del 99,9% de los datos, en lugar de perder una parte importante de la información al eliminar todas las filas que no tenían datos de parqueaderos.

A partir de este punto, utilizaremos vivienda_completo para realizar el PCA y el análisis de conglomerados, ya que contiene los datos numéricos preparados para estos análisis. Por otro lado, seguiremos utilizando vivienda para el análisis de correspondencia, porque este análisis trabaja principalmente con variables categóricas y no depende de las variables numéricas.

3.4 Detección y tratamiento de valores atípicos

Tanto el PCA como el análisis de conglomerados se basan en varianzas y en distancias entre observaciones, por lo que son sensibles a valores muy extremos (outliers): un solo registro con un valor mucho más alto o más bajo que el resto puede desplazar los componentes principales o “jalar” el centro de un segmento. Antes de continuar, revisamos visualmente la distribución de las 6 variables numéricas mediante diagramas de caja:

vivienda_completo %>%
  select(all_of(vars_num)) %>%
  tidyr::pivot_longer(everything(), names_to = "variable", values_to = "valor") %>%
  ggplot(aes(x = variable, y = valor)) +
  geom_boxplot(fill = "#66A182", outlier.color = "#D1AC00", outlier.alpha = 0.5) +
  facet_wrap(~variable, scales = "free", ncol = 3) +
  theme_minimal() +
  labs(x = NULL, y = NULL)
Distribución de las variables numéricas antes de tratar valores atípicos

Distribución de las variables numéricas antes de tratar valores atípicos

En varias variables (particularmente areaconst y preciom) se observan puntos considerablemente alejados del resto de la distribución. Es importante aclarar que no todos estos valores son necesariamente errores: en un mercado inmobiliario real es normal que existan algunas propiedades excepcionalmente grandes o costosas. Por esta razón, en vez de eliminar estas observaciones (lo que reduciría la muestra y podría descartar casos legítimos), aplicamos una técnica llamada winsorización: los valores por debajo del percentil 1% o por encima del percentil 99% de cada variable se “recortan” hasta ese límite, en vez de eliminarse. Así se reduce la influencia desproporcionada de los valores más extremos sobre el PCA y el clustering, conservando el 100% de los registros:

winsorizar <- function(x, p = 0.01) {
  limites <- quantile(x, probs = c(p, 1 - p), na.rm = TRUE)
  pmin(pmax(x, limites[1]), limites[2])
}

# guardamos una copia de las variables antes de winsorizar, para poder
# reportar cuántos valores fueron ajustados en cada una
vars_antes_winsor <- vivienda_completo[, vars_num]

vivienda_completo <- vivienda_completo %>%
  mutate(across(all_of(vars_num), ~ winsorizar(.x)))

cambios <- sapply(vars_num, function(v) {
  sum(vars_antes_winsor[[v]] != vivienda_completo[[v]])
})

kable(data.frame(Variable = names(cambios), `Valores ajustados` = as.integer(cambios),
                  check.names = FALSE),
      row.names = FALSE,
      caption = "Cantidad de valores ajustados (recortados) por winsorización")
Cantidad de valores ajustados (recortados) por winsorización
Variable Valores ajustados
estrato 0
preciom 163
areaconst 152
parqueaderos 47
banios 117
habitaciones 117

Y verificamos visualmente el efecto del tratamiento:

vivienda_completo %>%
  select(all_of(vars_num)) %>%
  tidyr::pivot_longer(everything(), names_to = "variable", values_to = "valor") %>%
  ggplot(aes(x = variable, y = valor)) +
  geom_boxplot(fill = "#2E4057", outlier.color = "#D1AC00", outlier.alpha = 0.5) +
  facet_wrap(~variable, scales = "free", ncol = 3) +
  theme_minimal() +
  labs(x = NULL, y = NULL)
Distribución de las variables numéricas después de tratar valores atípicos

Distribución de las variables numéricas después de tratar valores atípicos

De aquí en adelante, vivienda_completo contiene las variables numéricas ya winsorizadas, y es este conjunto de datos el que alimenta el PCA (sección 4) y el análisis de conglomerados (sección 5).


4. Análisis de Componentes Principales (PCA)

4.1 ¿Qué hace esta técnica y por qué la usamos?

El PCA toma las 6 variables numéricas del conjunto de datos y busca resumir la información que contienen en un número menor de variables, llamadas componentes principales. Esto es útil porque algunas de estas variables están relacionadas entre sí; por ejemplo, las propiedades más grandes normalmente tienen más banios y también pueden tener un precio más alto. De esta manera, el PCA permite reducir las 6 variables originales a 2 o 3 componentes, conservando la mayor cantidad posible de información y facilitando el análisis y la visualización de los datos.

Antes de realizar el PCA, es necesario estandarizar las variables, es decir, hacer que todas tengan una media de 0 y una desviación estándar de 1. Para esto se utiliza el argumento scale.unit = TRUE. Este paso es importante porque las variables tienen valores en escalas muy diferentes. Por ejemplo, el precio puede estar expresado en cientos de millones, mientras que el número de banios puede ir de 0 a 10. Si no se estandarizaran, las variables con valores más grandes podrían tener una mayor influencia en los resultados y afectar el análisis.

Nota metodológica: la variable estrato es, en sentido estricto, una variable ordinal (categorías socioeconómicas ordenadas de 1 a 6) y no una variable numérica continua. Al incluirla en el PCA junto con las demás variables continuas, se asume implícitamente que la diferencia entre, por ejemplo, estrato 1 y 2 es comparable a la diferencia entre estrato 5 y 6, lo cual no necesariamente se cumple en la práctica. Esta es una simplificación común en este tipo de análisis, pero es importante tenerla en cuenta como una limitación al interpretar los resultados relacionados con esta variable.

datos_pca <- vivienda_completo[, vars_num]

res.pca <- PCA(datos_pca, scale.unit = TRUE, graph = FALSE)

kable(round(res.pca$eig, 2),
      col.names = c("Autovalor", "% Varianza", "% Varianza acumulada"),
      caption = "Varianza explicada por cada componente")
Varianza explicada por cada componente
Autovalor % Varianza % Varianza acumulada
comp 1 3.50 58.31 58.31
comp 2 1.27 21.12 79.42
comp 3 0.44 7.29 86.71
comp 4 0.40 6.60 93.31
comp 5 0.23 3.83 97.14

Cada fila de la tabla representa un componente principal. La columna “Autovalor” muestra cuánta información explica cada componente. La columna “% Varianza” indica qué porcentaje de la información total representa, mientras que “% Varianza acumulada” muestra el porcentaje total que se ha explicado al sumar los componentes de forma progresiva.

4.2 Varianza explicada (Scree plot)

El scree plot es una representación gráfica de la información de la tabla anterior. Sirve para observar cómo cambia la cantidad de información explicada por cada componente y, a partir de esto, decidir cuántos componentes es conveniente conservar para el análisis.

fviz_eig(res.pca, addlabels = TRUE, barfill = "#2E4057", barcolor = "#2E4057")
Porcentaje de varianza explicada por componente

Porcentaje de varianza explicada por componente

Con los resultados de este análisis, el primer componente explica cerca del 58% de la varianza total y el segundo cerca del 21% adicional, para un acumulado cercano al 79% entre ambos. Este es un resultado adecuado: en lugar de analizar las 6 variables originales por separado, gran parte del comportamiento del mercado se puede resumir observando solo estos dos ejes, que son los que se utilizan en los gráficos de las secciones 4.3 y 4.4.

4.3 Contribución de las variables

Una vez que se decide trabajar con los dos primeros componentes, es importante entender qué variables tienen mayor influencia en cada uno. Esto nos permite saber qué representa cada componente y qué variables tienen más peso en cada eje.

kable(round(res.pca$var$contrib[, 1:2], 1),
      col.names = c("Contribución Dim1 (%)", "Contribución Dim2 (%)"),
      caption = "Contribución de cada variable a los dos primeros componentes")
Contribución de cada variable a los dos primeros componentes
Contribución Dim1 (%) Contribución Dim2 (%)
estrato 10.8 34.1
preciom 22.5 3.3
areaconst 20.2 6.3
parqueaderos 16.6 7.1
banios 21.9 3.0
habitaciones 8.1 46.2
fviz_pca_var(res.pca, col.var = "contrib",
             gradient.cols = c("#2E4057", "#66A182", "#D1AC00"),
             repel = TRUE)
Círculo de correlaciones de las variables

Círculo de correlaciones de las variables

Cómo leer este gráfico: cada flecha representa una variable original. Dos flechas que apuntan en una misma dirección corresponden a variables que están positivamente correlacionadas entre sí; flechas en direcciones opuestas están negativamente correlacionadas, y flechas perpendiculares no están correlacionadas. La longitud de cada flecha indica qué tan bien queda representada esa variable en este plano de dos dimensiones. El color indica cuánto contribuye cada variable a la formación de estos dos componentes.

Interpretación con los resultados obtenidos: el primer componente, representado en el eje horizontal, resume principalmente el tamaño y las características generales de la propiedad. Las variables que más influyen en este componente son preciom, banios y areaconst, en ese orden.

Por otro lado, el segundo componente, representado en el eje vertical, está más relacionado con las variables habitaciones y estrato. Esto permite diferenciar las propiedades teniendo en cuenta el número de habitaciones y el nivel socioeconómico, de manera independiente al primer componente.

En otras palabras, dos propiedades pueden tener valores similares en el primer componente porque tienen características generales parecidas, pero pueden diferenciarse en el segundo componente si, por ejemplo, tienen diferente número de habitaciones o pertenecen a distintos estratos.

4.4 Individuos en el espacio de componentes

Además de observar la posición de las variables, también es útil analizar dónde se ubica cada propiedad en este mismo plano, utilizando diferentes colores según su estrato. Esto permite observar de manera visual si existe alguna relación entre el estrato de las propiedades y los componentes principales.

fviz_pca_ind(res.pca, geom = "point", alpha.ind = 0.6,
             col.ind = vivienda_completo$estrato) +
  scale_color_gradient(low = "#D1AC00", high = "#2E4057") +
  labs(color = "Estrato")
Propiedades proyectadas en los dos primeros componentes, coloreadas por estrato

Propiedades proyectadas en los dos primeros componentes, coloreadas por estrato

Cada punto representa una propiedad. Se puede observar que los colores se distribuyen de manera relativamente ordenada a lo largo de los ejes, lo que indica que existe una relación entre el estrato socioeconómico y la posición de las propiedades en este espacio. Esto es coherente con los resultados anteriores, donde los componentes principales están relacionados, en mayor o menor medida, con características como el estrato, el tamaño y las condiciones generales de la vivienda.


5. Análisis de Conglomerados (Clustering)

5.1 ¿Qué hace esta técnica y por qué la usamos?

Mientras que el PCA busca resumir las variables para facilitar su análisis, el análisis de conglomerados busca agrupar las observaciones que tienen características similares. En este caso, se busca agrupar las propiedades que se parecen entre sí, formando grupos o clusters que sean similares internamente y diferentes entre ellos.

Para realizar este análisis se utiliza el algoritmo k-means, el cual requiere definir previamente la cantidad de grupos (k) que se quieren formar.

Para elegir un valor adecuado de k, se utiliza el método del codo. Este método consiste en aplicar el algoritmo k-means con diferentes cantidades de grupos y observar cómo cambia la variabilidad dentro de cada grupo. La idea es encontrar el punto en la gráfica donde agregar un grupo más ya no genera una mejora significativa. Este punto se conoce como el “codo” y ayuda a determinar una cantidad adecuada de grupos para el análisis.

datos_esc <- scale(vivienda_completo[, vars_num])

set.seed(123)
fviz_nbclust(datos_esc, kmeans, method = "wss", k.max = 8)
Método del codo para seleccionar el número de clusters

Método del codo para seleccionar el número de clusters

(Nota: set.seed(123) permite fijar la semilla aleatoria utilizada por R. Como el algoritmo k-means utiliza puntos de partida aleatorios, establecer una semilla hace que, al ejecutar nuevamente el análisis, se obtengan los mismos resultados. Esto es importante para que el análisis pueda ser reproducido.)

Con base en el gráfico anterior, el método del codo sugiere k = 4 conglomerados como un equilibrio adecuado entre tener pocos grupos, que sean fáciles de interpretar, y conservar suficiente información para el análisis.

Como el método del codo depende en parte de la interpretación visual de quien lo aplica, lo complementamos con el método de la silueta (silhouette), que sí entrega un valor numérico objetivo: para cada observación calcula qué tan bien encaja en su propio grupo en comparación con el grupo vecino más cercano, y promedia ese valor para cada posible número de clusters. Cuanto más alto el promedio (más cercano a 1), mejor separados están los grupos.

set.seed(123)
fviz_nbclust(datos_esc, kmeans, method = "silhouette", k.max = 8)
Ancho de silueta promedio para distintos valores de k

Ancho de silueta promedio para distintos valores de k

set.seed(123)
km_temp <- kmeans(datos_esc, centers = 4, nstart = 25)
sil <- silhouette(km_temp$cluster, dist(datos_esc))
ancho_silueta_prom <- mean(sil[, 3])

cat("Ancho de silueta promedio para k = 4:", round(ancho_silueta_prom, 3), "\n")
## Ancho de silueta promedio para k = 4: 0.306

(Cómo interpretar este número: valores por encima de 0.5 indican grupos muy bien separados; valores entre aproximadamente 0.25 y 0.5 indican una estructura de agrupamiento razonable, aunque con cierto solapamiento entre grupos vecinos; valores cercanos a 0 indican que los grupos se solapan considerablemente. En datos socioeconómicos reales —como precios y características de vivienda, que varían de forma continua y no en grupos perfectamente separados— es normal obtener valores moderados en vez de valores cercanos a 1; esto no invalida la segmentación, solo indica que los límites entre segmentos son graduales y no completamente nítidos.)

Tomando en cuenta tanto el método del codo como el método de la silueta, se confirma k = 4 como una elección razonable para este análisis.

5.2 Segmentación con k-means

set.seed(123)
km <- kmeans(datos_esc, centers = 4, nstart = 25)

vivienda_completo$segmento <- factor(km$cluster,
                                      labels = paste("Segmento", 1:4))

kable(table(vivienda_completo$segmento), col.names = c("Segmento", "N° de propiedades"),
      caption = "Tamaño de cada segmento")
Tamaño de cada segmento
Segmento N° de propiedades
Segmento 1 1064
Segmento 2 3513
Segmento 3 2844
Segmento 4 898

(Nota: nstart = 25 indica que el algoritmo k-means va a probar 25 configuraciones iniciales diferentes y seleccionar la que obtenga el mejor resultado. Esto ayuda a reducir la posibilidad de obtener una agrupación poco adecuada y permite conseguir resultados más confiables.)

fviz_cluster(km, data = datos_esc, geom = "point",
             ellipse.type = "convex", palette = "jco") +
  labs(title = "Segmentación de propiedades residenciales")
Visualización de los segmentos en el plano de componentes principales

Visualización de los segmentos en el plano de componentes principales

Este gráfico muestra los 4 segmentos encontrados mediante k-means en el mismo plano de componentes principales de la sección 4. La elipse de cada grupo permite observar cómo se distribuyen las propiedades y comprobar visualmente si los grupos están bien diferenciados. De esta manera, podemos verificar que la segmentación realizada tiene sentido y que los grupos no se formaron de manera aleatoria.

5.3 Caracterización de los segmentos

El número de cada segmento (1, 2, 3 o 4) por sí solo no nos da mucha información. Lo importante es conocer las características de cada grupo, para lo cual se calcula el promedio de cada variable numérica dentro de cada segmento. Esto permite identificar las principales diferencias y entender mejor el perfil de las propiedades que pertenecen a cada grupo.

perfil <- vivienda_completo %>%
  group_by(segmento) %>%
  summarise(across(all_of(vars_num), \(x) round(mean(x), 1)),
            n = n(), .groups = "drop")

kable(perfil, caption = "Perfil promedio de cada segmento")
Perfil promedio de cada segmento
segmento estrato preciom areaconst parqueaderos banios habitaciones n
Segmento 1 5.7 1073.2 382.8 3.4 5.0 4.3 1064
Segmento 2 3.9 210.7 88.9 0.7 2.0 2.9 3513
Segmento 3 5.3 462.1 158.3 1.7 3.3 3.3 2844
Segmento 4 3.9 448.2 298.6 1.2 4.4 6.5 898

También se compara cada segmento con la variable zona para identificar si los diferentes tipos de propiedades se concentran en determinadas zonas de la ciudad. Esto permite conocer mejor la distribución de los grupos y observar si existe alguna relación entre las características de las propiedades y su ubicación.

tabla_seg_zona <- vivienda_completo %>%
  count(segmento, zona) %>%
  tidyr::pivot_wider(names_from = zona, values_from = n, values_fill = 0)

kable(tabla_seg_zona, caption = "Distribución de cada segmento por zona")
Distribución de cada segmento por zona
segmento Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
Segmento 1 2 120 309 1 632
Segmento 2 72 1042 149 197 2053
Segmento 3 2 551 690 2 1599
Segmento 4 48 207 50 151 442

Interpretación con los resultados obtenidos — a partir de los resultados obtenidos en las tablas anteriores, se pueden identificar y describir las principales características de cada uno de los cuatro segmentos de propiedades.

  • Segmento premium (aprox. 1,000 propiedades): presenta el precio promedio más alto de los cuatro grupos. Además, reúne propiedades con mayor área construida, más banios y más parqueaderos. Por estas características, es el segmento de mayor valor.
  • Segmento estrato alto compacto (el segundo más numeroso, cerca de 2,800 propiedades): tiene un estrato similar al segmento anterior, pero sus propiedades presentan precios, áreas y número de habitaciones más bajos. En general, corresponde a propiedades de estrato alto, pero de menor tamaño.
  • Segmento amplio de estrato medio (el más pequeño, cerca de 900 propiedades): se caracteriza por tener un estrato medio, pero propiedades con áreas construidas y cantidades de habitaciones y banios relativamente altas. Esto podría corresponder principalmente a casas de mayor tamaño ubicadas en zonas de estrato medio.
  • Segmento económico (el más numeroso, cerca de 3,500 propiedades): se caracteriza por un estrato medio-bajo y por presentar los precios, áreas y números de habitaciones más bajos entre los cuatro grupos. Por estas características, representa el segmento más accesible del mercado.

En cuanto a la distribución geográfica, la mayoría de los segmentos, incluyendo el segmento económico y el segmento de estrato alto compacto, se concentran principalmente en la Zona Sur, que es la zona con mayor cantidad de oferta de vivienda en la ciudad. De hecho, más de la mitad de los anuncios de la base de datos pertenecen a esta zona.

Por otro lado, el segmento amplio de estrato medio presenta una distribución más equilibrada entre las diferentes zonas, con una presencia importante en la Zona Oriente en comparación con los demás segmentos.

En general, estos resultados muestran que la Zona Sur concentra propiedades de diferentes características, mientras que otras zonas presentan una mayor concentración de determinados tipos de vivienda.


6. Análisis de Correspondencia

6.1 ¿Qué hace esta técnica y por qué la usamos?

El análisis de correspondencia es similar al PCA, pero está diseñado para trabajar con variables categóricas en lugar de variables numéricas. En este caso, permite analizar la relación entre diferentes categorías, como las zonas y los barrios, e identificar cuáles tienden a aparecer juntas con mayor frecuencia.

Como en este análisis se busca estudiar la relación entre tres variables categóricas (tipo, zona y barrio), se utiliza el Análisis de Correspondencias Múltiples (MCA). Este método es una extensión del análisis de correspondencia simple y permite analizar al mismo tiempo tres o más variables categóricas.

datos_cat <- vivienda[, c("tipo", "zona", "barrio")]
datos_cat[] <- lapply(datos_cat, as.factor)

res.mca <- MCA(datos_cat, graph = FALSE)

kable(round(res.mca$eig[1:5, ], 2),
      col.names = c("Autovalor", "% Varianza", "% Varianza acumulada"),
      caption = "Varianza explicada — MCA")
Varianza explicada — MCA
Autovalor % Varianza % Varianza acumulada
dim 1 1.00 0.68 0.68
dim 2 0.71 0.48 1.16
dim 3 0.66 0.45 1.61
dim 4 0.65 0.44 2.05
dim 5 0.62 0.42 2.47

(Nota sobre estos porcentajes: en el MCA es normal que cada dimensión explique un porcentaje relativamente pequeño de la información cuando se tienen muchas categorías, como ocurre con la variable barrio. Esto se debe a que la información se distribuye entre varias dimensiones. Por lo tanto, estos porcentajes bajos no significan que el análisis esté mal realizado. En este caso, lo más importante es observar la posición y relación entre las categorías en el mapa que se presenta a continuación.)

fviz_mca_var(res.mca, repel = TRUE, col.var = "cos2",
             gradient.cols = c("#D1AC00", "#66A182", "#2E4057")) +
  labs(title = "Relación entre tipo, zona y barrio")
Mapa de categorías del Análisis de Correspondencias Múltiples

Mapa de categorías del Análisis de Correspondencias Múltiples

Cada punto del gráfico representa una categoría, ya sea una zona, un barrio o un tipo de vivienda como “Casa” o “Apartamento”. Cuando dos categorías aparecen cerca entre sí, significa que tienen una relación y tienden a presentarse juntas con mayor frecuencia. Por ejemplo, si un barrio aparece cerca de “Casa”, puede indicar que en ese barrio hay una mayor presencia de casas en comparación con otras zonas de la ciudad.

El color de cada punto indica qué tan bien está representada la categoría en el gráfico (cos2). Las categorías con colores más intensos están mejor representadas por estas dos dimensiones.

Este análisis permite identificar posibles patrones de distribución geográfica de las viviendas, mostrando qué barrios y zonas tienen una mayor relación con determinados tipos de vivienda. Esta información puede ser útil para conocer en qué sectores existe una mayor presencia de casas o apartamentos.

6.2 Correspondencia adicional: Zona vs. Barrio

Como complemento al análisis anterior, y teniendo en cuenta que la variable barrio tiene muchas categorías, se realiza un análisis de correspondencia simple entre zona y barrio. Esto permite analizar esta relación de manera más específica y facilita la interpretación de cómo se distribuyen los barrios dentro de las diferentes zonas de la ciudad.

tabla_zb <- table(vivienda$zona, vivienda$barrio)
res.ca <- CA(tabla_zb, graph = FALSE)

fviz_ca_biplot(res.ca, repel = TRUE) +
  labs(title = "Correspondencia entre zona y barrio")
Análisis de correspondencia: Zona vs. Barrio

Análisis de correspondencia: Zona vs. Barrio

Como era de esperarse, cada barrio pertenece a una única zona de la ciudad. Por esta razón, el gráfico permite observar claramente esta relación geográfica y sirve como una referencia rápida para identificar qué barrios pertenecen a cada zona.

Nota metodológica: como cada barrio pertenece a una única zona (es decir, barrio está anidado dentro de zona), estas dos variables contienen información parcialmente redundante entre sí: conociendo el barrio de una propiedad, la zona queda determinada automáticamente. Por esta razón, al incluir ambas variables juntas en el MCA de la sección 6.1, una parte de la relación que se observa entre categorías de zona y de barrio refleja simplemente esta jerarquía geográfica y no un patrón adicional del mercado. Esto no invalida el análisis, pero es importante tenerlo en cuenta al interpretar la cercanía entre categorías de estas dos variables específicamente.

6.3 Prueba de independencia (chi-cuadrado)

Las interpretaciones anteriores se basaron en la inspección visual del mapa de MCA. Para respaldarlas con mayor rigor estadístico, aplicamos una prueba chi-cuadrado de independencia sobre la tabla de contingencia entre zona y tipo. Esta prueba evalúa formalmente si existe una asociación estadísticamente significativa entre dos variables categóricas, en lugar de basarse únicamente en la apariencia visual de un gráfico:

tabla_zona_tipo <- table(vivienda$zona, vivienda$tipo)
chi_zona_tipo <- chisq.test(tabla_zona_tipo)
chi_zona_tipo
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_zona_tipo
## X-squared = 690.93, df = 4, p-value < 2.2e-16

(Cómo interpretar este resultado: la hipótesis nula de esta prueba es que zona y tipo son independientes, es decir, que no existe relación entre ellas. Si el p-valor obtenido es menor a 0.05, se rechaza esa hipótesis y se concluye que sí existe una asociación estadísticamente significativa entre la zona y el tipo de vivienda, lo cual respalda con un fundamento estadístico formal los patrones que ya se habían observado visualmente en el mapa de correspondencias de la sección 6.1. Si el p-valor fuera mayor a 0.05, no habría evidencia suficiente para afirmar que existe esa asociación.)


7. Visualización geográfica

7.1 ¿Por qué agregamos mapas?

El reto solicita presentar recursos visuales, incluyendo mapas, para mostrar los resultados de una manera clara a la dirección de la empresa. Un mapa de dispersión utilizando las coordenadas (longitud y latitud) de cada propiedad permite observar fácilmente cómo se distribuyen geográficamente. Además, ayuda a comprobar si los patrones encontrados mediante el PCA y el análisis de conglomerados también se reflejan en la ubicación real de las propiedades, algo que resulta más difícil de identificar únicamente con las tablas numéricas.

Para darle un poco más de contexto geográfico a los mapas (y no dejar los puntos “flotando” sin ninguna referencia), calculamos un polígono aproximado para cada zona de la ciudad, usando la envolvente convexa (convex hull) de las propiedades que pertenecen a esa zona — es decir, el polígono más pequeño que contiene a todos sus puntos. Esto permite dibujar un límite de referencia por zona directamente a partir de los propios datos, sin depender de un mapa base externo:

poligonos_zona <- vivienda_completo %>%
  group_by(zona) %>%
  slice(chull(longitud, latitud))
ggplot() +
  geom_polygon(data = poligonos_zona,
               aes(x = longitud, y = latitud, fill = zona, group = zona),
               alpha = 0.15, color = "grey50", linewidth = 0.3,
               show.legend = FALSE) +
  geom_point(data = vivienda_completo,
             aes(x = longitud, y = latitud, color = segmento),
             alpha = 0.6, size = 1.5) +
  labs(title = "Distribución geográfica de las propiedades por segmento",
       x = "Longitud", y = "Latitud", color = "Segmento") +
  theme_minimal()
Distribución geográfica de las propiedades por segmento, con el contorno aproximado de cada zona

Distribución geográfica de las propiedades por segmento, con el contorno aproximado de cada zona

Este mapa muestra cada propiedad con un color diferente según el segmento de conglomerado al que pertenece, definido en la sección 5. Si se observa que los colores tienden a concentrarse en determinadas áreas, en lugar de aparecer distribuidos de manera uniforme, esto indica que los segmentos identificados también tienen una relación con la ubicación geográfica de las propiedades. De esta forma, se puede comprobar que los grupos encontrados no son solo resultado del análisis estadístico, sino que también reflejan diferencias que se presentan en distintas zonas de la ciudad.

ggplot() +
  geom_polygon(data = poligonos_zona,
               aes(x = longitud, y = latitud, group = zona),
               fill = NA, color = "grey50", linewidth = 0.3) +
  geom_point(data = vivienda_completo,
             aes(x = longitud, y = latitud, color = preciom),
             alpha = 0.6, size = 1.5) +
  scale_color_viridis_c() +
  labs(title = "Distribución geográfica del precio",
       x = "Longitud", y = "Latitud", color = "Precio\n(millones)") +
  theme_minimal()
Distribución geográfica del precio de las propiedades, con el contorno aproximado de cada zona

Distribución geográfica del precio de las propiedades, con el contorno aproximado de cada zona

Este segundo mapa muestra cada propiedad según su precio, utilizando una escala de colores en la que los tonos más claros o amarillos representan precios más altos. A diferencia del mapa anterior, aquí no se utilizan los segmentos del análisis de conglomerados.

Este mapa permite identificar de manera visual las zonas donde se concentran las propiedades con precios más altos o más bajos. De esta forma, complementa el análisis realizado por segmentos y ayuda a comprender mejor la distribución de los precios en la ciudad.


8. Conclusiones y recomendaciones

Sobre la estructura de los datos (PCA): el análisis de componentes principales muestra que los dos primeros componentes explican cerca del 79% de la variación total de los datos (58% + 21%). El primer componente está principalmente relacionado con las características generales de la vivienda, especialmente el precio, el número de banios y el área construida. El segundo componente está más relacionado con el número de habitaciones y el estrato socioeconómico. Estos resultados muestran que es posible resumir gran parte de la información de las variables originales utilizando un número reducido de componentes, lo que facilita el análisis y la interpretación de las propiedades.

Sobre la segmentación del mercado (Conglomerados): el análisis de conglomerados permitió identificar 4 grupos de propiedades con características diferentes. Estos grupos van desde un segmento de mayor valor, caracterizado por propiedades de estrato alto, mayor tamaño y precios elevados, hasta un segmento económico, con propiedades de estrato medio-bajo, menor tamaño y precios más accesibles. También se identificaron dos grupos intermedios: uno formado por propiedades de estrato alto pero de menor tamaño y otro compuesto por propiedades de estrato medio con áreas relativamente grandes. Además, se encontró que la Zona Sur concentra una gran parte de la oferta de los diferentes segmentos, mientras que otras zonas presentan una distribución más específica. Esto puede ayudar a la empresa a establecer estrategias comerciales según las características de cada segmento y su ubicación.

Sobre los patrones categóricos (Correspondencia): el análisis de correspondencia permitió identificar una relación entre el tipo de vivienda, la zona y el barrio, mostrando que algunos sectores presentan una mayor presencia de determinados tipos de propiedades, como casas o apartamentos. Estos resultados permiten conocer mejor las características de la oferta en diferentes partes de la ciudad y pueden servir como apoyo para decidir qué tipo de vivienda resulta más conveniente ofrecer en cada zona.

Recomendaciones estratégicas para la empresa:

A partir de los resultados obtenidos, se recomienda diferenciar las estrategias comerciales según el tipo de propiedad y la zona. Los segmentos de estrato alto pueden representar una oportunidad importante para productos de mayor valor, mientras que el segmento económico puede ser relevante para atender a compradores con presupuestos más limitados.

Para la valoración de nuevas propiedades, se recomienda utilizar las características promedio identificadas en los cuatro segmentos, como precio, área, número de banios, habitaciones y parqueaderos. Esto permitiría contar con una referencia más objetiva y facilitar la comparación entre propiedades con características similares.

Finalmente, para la planificación de la oferta, es recomendable tener en cuenta los resultados del análisis de correspondencia. Identificar qué barrios y zonas tienen mayor presencia de casas o apartamentos puede ayudar a orientar mejor la oferta de la empresa y evitar aplicar una misma estrategia para toda la ciudad.

En conjunto, los análisis realizados permiten obtener una visión más clara del mercado inmobiliario, identificar diferentes tipos de propiedades y comprender cómo se distribuyen en la ciudad. Esto proporciona información útil para apoyar la toma de decisiones comerciales, la valoración de propiedades y la planificación de futuras inversiones.


9. Anexos

9.1 Anexo A. Diccionario de variables

Para facilitar la lectura del informe a cualquier persona que no haya participado en la construcción de la base de datos, se incluye a continuación una breve descripción de cada una de las variables del conjunto de datos vivienda:

Diccionario de variables del conjunto de datos vivienda
Variable Descripción
id Identificador único del anuncio de la propiedad.
zona Zona general de la ciudad en la que se ubica la propiedad.
piso Número de piso (aplica únicamente a apartamentos).
estrato Estrato socioeconómico de la propiedad (escala de 1 a 6).
preciom Precio de la propiedad, en millones de pesos.
areaconst Área construida de la propiedad, en metros cuadrados.
parqueaderos Número de parqueaderos con los que cuenta la propiedad.
banios Número de banios de la propiedad.
habitaciones Número de habitaciones de la propiedad.
tipo Tipo de vivienda: Casa o Apartamento.
barrio Barrio específico dentro de la zona en el que se ubica la propiedad.
longitud Coordenada de longitud de la ubicación de la propiedad.
latitud Coordenada de latitud de la ubicación de la propiedad.

9.2 Anexo B. Información de la sesión de R

Para garantizar que este análisis pueda reproducirse en las mismas condiciones, se incluye la información de la sesión de R utilizada para generar este informe (versión de R y de cada una de las librerías empleadas):

sessionInfo()
## R version 4.5.3 (2026-03-11 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] cluster_2.1.8.3      factoextra_2.2.0     FactoMineR_2.16     
##  [4] dplyr_1.2.1          paqueteMODELOS_0.1.0 summarytools_1.1.5  
##  [7] knitr_1.51           gridExtra_2.3.1      GGally_2.4.0        
## [10] ggplot2_4.0.3        broom_1.0.13         boot_1.3-32         
## 
## loaded via a namespace (and not attached):
##  [1] tcltk_4.5.3          remotes_2.5.0        rlang_1.3.0         
##  [4] magrittr_2.0.5       otel_0.2.0           matrixStats_1.5.0   
##  [7] compiler_4.5.3       callr_3.8.0          vctrs_0.7.3         
## [10] reshape2_1.4.5       stringr_1.6.0        sysfonts_0.8.9      
## [13] pkgconfig_2.0.3      fastmap_1.2.0        backports_1.5.1     
## [16] magick_2.9.1         ellipsis_0.3.3       labeling_0.4.3      
## [19] pander_0.6.6         rmarkdown_2.31       sessioninfo_1.2.4   
## [22] purrr_1.2.2          xfun_0.60            showtext_0.9-8      
## [25] cachem_1.1.0         jsonlite_2.0.0       flashClust_1.1-4    
## [28] irlba_2.3.7          R6_2.6.1             bslib_0.10.0        
## [31] stringi_1.8.9        RColorBrewer_1.1-3   car_3.1-5           
## [34] pkgload_1.5.3        lubridate_1.9.5      jquerylib_0.1.4     
## [37] estimability_2.0.0   Rcpp_1.1.2           usethis_3.2.1       
## [40] base64enc_0.1-6      Matrix_1.7-4         timechange_0.4.0    
## [43] tidyselect_1.2.1     abind_1.4-8          rstudioapi_0.19.0   
## [46] yaml_2.3.12          ggtext_0.1.2         curl_7.1.0          
## [49] processx_3.9.0       pkgbuild_1.4.8       lattice_0.22-9      
## [52] tibble_3.3.1         plyr_1.8.9           withr_3.0.3         
## [55] S7_0.2.2             evaluate_1.0.5       desc_1.4.3          
## [58] ggstats_0.13.0       xml2_1.6.0           pillar_1.11.1       
## [61] ggpubr_1.0.0         carData_3.0-6        checkmate_2.3.4     
## [64] DT_0.34.0            generics_0.1.4       scales_1.4.0        
## [67] xtable_1.8-8         leaps_3.2            glue_1.8.1          
## [70] emmeans_2.0.4        scatterplot3d_0.3-45 tools_4.5.3         
## [73] ggsignif_0.6.4       fs_2.1.0             mvtnorm_1.4-2       
## [76] rapportools_1.2      grid_4.5.3           tidyr_1.3.2         
## [79] devtools_2.5.2       showtextdb_3.0       Formula_1.2-5       
## [82] cli_3.6.6            viridisLite_0.4.3    gtable_0.3.6        
## [85] ggsci_5.2.0          rstatix_1.1.0        sass_0.4.10         
## [88] digest_0.6.39        ggrepel_0.9.8        htmlwidgets_1.6.4   
## [91] farver_2.1.2         memoise_2.0.1        htmltools_0.5.9     
## [94] lifecycle_1.0.5      multcompView_0.1-12  gridtext_0.1.6      
## [97] MASS_7.3-65

9.3 Anexo C. Fuente de los datos

Los datos utilizados en este informe fueron tomados de OLX mediante un procedimiento de web scraping, y se encuentran contenidos en el paquete de R paqueteMODELOS (repositorio centro-magis/paqueteMODELOS).