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:
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:
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).
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
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")
| 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.
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")
| 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")
| 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.
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)")
| 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.
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
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")
| 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
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).
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")
| 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.
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
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.
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 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ó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.
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
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.
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
(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
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.
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")
| 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
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.
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")
| 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")
| 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.
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.
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")
| 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
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.
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
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.
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.)
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
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
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.
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.
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:
| 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. |
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
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).