Actividad 1

Modelos Estadísticos para la toma de decisiones

Problema

Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

Retos:

El reto principal consiste en realizar un análisis integral y multidimensional de la base de datos para obtener una comprensión del mercado inmobiliario urbano. Se requiere aplicar diversas técnicas de análisis de datos, incluyendo:

  1. Análisis de Componentes Principales: Reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y oferta del mercado.

  2. Análisis de Conglomerados: Agrupar las propiedades residenciales en segmentos homogéneos con características similares para entender las dinámicas de las ofertas específicas en diferentes partes de la ciudad y en diferentes estratos socioeconómicos.

  3. Análisis de Correspondencia: Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio), para identificar patrones de comportamiento de la oferta en mercado inmobiliario.

  4. Visualización de resultados: Presentar gráficos, mapas y otros recursos visuales para comunicar los hallazgos de manera clara y efectiva a la dirección de la empresa.

Desarrollo

0. Paquetes y datos

En este subcapítulo se instalan los paquetes necesarios para el análisis y se carga la base de datos de las propiedades residenciales disponibles en el mercado, “vivienda” desde el paquete “paqueteMODELOS”.

La base de datos tienen 8322 registros y 13 variables, a continuación se presenta un resumen de las cada una de las variables.

# Cargar base de datos del paquete
data("vivienda")
#glimpse(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")=
##   .. cols(
##   ..   id = col_double(),
##   ..   zona = col_character(),
##   ..   piso = col_character(),
##   ..   estrato = col_double(),
##   ..   preciom = col_double(),
##   ..   areaconst = col_double(),
##   ..   parqueaderos = col_double(),
##   ..   banios = col_double(),
##   ..   habitaciones = col_double(),
##   ..   tipo = col_character(),
##   ..   barrio = col_character(),
##   ..   longitud = col_double(),
##   ..   latitud = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>
summary(vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

1. Limpieza e imputación datos faltantes

Debido a que se observan datos faltantes, se procede a realizar limpieza de la base de datos y a realizar la imputación de los datos faltantes.

vivienda1 <- vivienda
vivienda1 <- vivienda1 %>% filter(!is.na(areaconst), areaconst>0, !is.na(preciom))

vivienda1 <- vivienda1 %>% mutate(
          piso = factor(piso),
          zona = factor(zona),
          tipo = factor(tipo),
          barrio = factor(barrio))

# Resumen
summary(vivienda1)
##        id                 zona           piso         estrato     
##  Min.   :   1   Zona Centro : 124   02     :1450   Min.   :3.000  
##  1st Qu.:2080   Zona Norte  :1920   03     :1097   1st Qu.:4.000  
##  Median :4160   Zona Oeste  :1198   01     : 860   Median :5.000  
##  Mean   :4160   Zona Oriente: 351   04     : 607   Mean   :4.634  
##  3rd Qu.:6240   Zona Sur    :4726   05     : 567   3rd Qu.:5.000  
##  Max.   :8319                       (Other):1103   Max.   :6.000  
##                                     NA's   :2635                  
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##                                    NA's   :1602                     
##   habitaciones             tipo                 barrio        longitud     
##  Min.   : 0.000   Apartamento:5100   valle del lili:1008   Min.   :-76.59  
##  1st Qu.: 3.000   Casa       :3219   ciudad jardín : 516   1st Qu.:-76.54  
##  Median : 3.000                      pance         : 409   Median :-76.53  
##  Mean   : 3.605                      la flora      : 366   Mean   :-76.53  
##  3rd Qu.: 4.000                      santa teresita: 262   3rd Qu.:-76.52  
##  Max.   :10.000                      el caney      : 208   Max.   :-76.46  
##                                      (Other)       :5550                   
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
## 

A continuación, se presenta el porcentaje (%) de datos faltantes en las variables “piso”, “parqueaderos”:

# Estimación del porcentaje de datos faltantes
na_summary <- sapply(vivienda1[, c("piso", "parqueaderos")], function(x) {
  sum(is.na(x)) / length(x) * 100
})
print(na_summary)
##         piso parqueaderos 
##     31.67448     19.25712

Se procede a realizar la imputación de los datos por la moda de la variable piso y parqueaderos agrupada por zona y tipo de vivienda:

# Función para calcular moda
moda <- function(v) {
  uniqv <- na.omit(unique(v))
  uniqv[which.max(tabulate(match(v, uniqv)))]}

# Resultados de la moda de la variable piso y parqueaderos - 
# agrupada por zona y tipo de vivienda
moda_zona_tipo <- vivienda1 %>%
  group_by(zona, tipo) %>%
  summarise(
    moda_piso = moda(piso),
    moda_parqueaderos = moda(parqueaderos),
    .groups = 'drop'
  )
print(moda_zona_tipo)
## # A tibble: 10 × 4
##    zona         tipo        moda_piso moda_parqueaderos
##    <fct>        <fct>       <fct>                 <dbl>
##  1 Zona Centro  Apartamento 05                        1
##  2 Zona Centro  Casa        01                        1
##  3 Zona Norte   Apartamento 03                        1
##  4 Zona Norte   Casa        02                        1
##  5 Zona Oeste   Apartamento 03                        2
##  6 Zona Oeste   Casa        02                        2
##  7 Zona Oriente Apartamento 01                        1
##  8 Zona Oriente Casa        01                        1
##  9 Zona Sur     Apartamento 05                        1
## 10 Zona Sur     Casa        02                        2
# Imputación por zona y tipo
vivienda1 <- vivienda1 %>%
  group_by(zona, tipo) %>%  # Agrupar por zona y tipo
  mutate(
    piso = ifelse(is.na(piso), moda(piso), piso),
    parqueaderos = ifelse(is.na(parqueaderos), moda(parqueaderos), parqueaderos)
  ) %>%
  ungroup()

summary(vivienda1 %>% select(piso,parqueaderos))
##       piso         parqueaderos   
##  Min.   : 1.000   Min.   : 1.000  
##  1st Qu.: 2.000   1st Qu.: 1.000  
##  Median : 3.000   Median : 1.000  
##  Mean   : 3.503   Mean   : 1.712  
##  3rd Qu.: 5.000   3rd Qu.: 2.000  
##  Max.   :12.000   Max.   :10.000

2. Identificación y depuración de outliers

# Identificación de valores atipicos

variables_cuantitativas<-vivienda1 %>% select(piso,
                                              estrato,
                                              preciom,
                                              areaconst,
                                              parqueaderos,
                                              banios,
                                              habitaciones,
                                              longitud,
                                              latitud)

variables_cuantitativas_largo <- variables_cuantitativas %>%
  pivot_longer(cols = everything(), 
               names_to = "variable", 
               values_to = "valor")

# Gráfico múltiple
p_multiple <- ggplot(variables_cuantitativas_largo, aes(x = variable, y = valor)) +
  geom_boxplot(outlier.colour = "red", 
               outlier.size = 1.5, 
               fill = "lightblue", 
               alpha = 0.7) +
  facet_wrap(~ variable, scales = "free", ncol = 3) +
  labs(title = "Detección de Datos Atípicos por Variable",
       x = "Variables",
       y = "Valor") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        plot.title = element_text(hjust = 0.5),
        plot.subtitle = element_text(hjust = 0.5))

print(p_multiple)

# --- Identificación y eliminación de outliers usando IQR ---

# Función para filtrar valores atípicos según IQR
eliminar_outliers <- function(df, variable) {
  Q1 <- quantile(df[[variable]], 0.25, na.rm = TRUE)
  Q3 <- quantile(df[[variable]], 0.75, na.rm = TRUE)
  IQR_value <- Q3 - Q1
  df %>% 
    filter(df[[variable]] >= (Q1 - 1.5 * IQR_value) &
             df[[variable]] <= (Q3 + 1.5 * IQR_value))
}

# Variables donde eliminaremos outliers
variables_a_filtrar <- c("areaconst", "preciom", "parqueaderos", "banios", "habitaciones")

# Copia del dataset original limpio
vivienda_sin_outliers <- vivienda1

# Filtrado progresivo por cada variable
for (var in variables_a_filtrar) {
  vivienda_sin_outliers <- eliminar_outliers(vivienda_sin_outliers, var)
}

# Gráfico después de eliminar outliers
variables_cuantitativas_largo_sin <- vivienda_sin_outliers %>%
  select(all_of(variables_a_filtrar)) %>%
  pivot_longer(cols = everything(), 
               names_to = "variable", 
               values_to = "valor")

p_multiple_sin <- ggplot(variables_cuantitativas_largo_sin, aes(x = variable, y = valor)) +
  geom_boxplot(outlier.colour = "red", outlier.size = 1.5,
               fill = "lightgreen", alpha = 0.7) +
  facet_wrap(~ variable, scales = "free", ncol = 3) +
  labs(title = "Datos sin Outliers",
       x = "Variables", y = "Valor") +
  theme_minimal()

print(p_multiple_sin)

# Comparamos tamaño de la base
cat("Registros originales:", nrow(vivienda1), "\n")
## Registros originales: 8319
cat("Registros después de imputar outliers:", nrow(vivienda_sin_outliers), "\n")
## Registros después de imputar outliers: 6554

Solo se realiza la eliminación de outliers de las variables “areaconst”, “preciom”, “parqueaderos”, “banios”, “habitaciones”, para no sesgar las variables por casos extremos que representan propiedades muy atípicas. Se mantiene los outliers de las variables de longitud, estrato y piso.

3. Análisis de Componentes Principales:

# 3 - PCA sobre variables numéricas

# seleccionar variables numéricas de interés 

num_vars <- vivienda_sin_outliers %>% select(piso,
                                                estrato,
                                                preciom,
                                                areaconst,
                                                parqueaderos,
                                                banios,
                                                habitaciones)
  
# estandarización
  
num_scaled <- scale(num_vars)
  
# PCA
  
res.pca <- prcomp(num_scaled, center = TRUE, scale. = FALSE) 
summary(res.pca)
## Importance of components:
##                           PC1    PC2    PC3     PC4     PC5     PC6     PC7
## Standard deviation     1.8852 1.1728 0.8590 0.71246 0.61101 0.54492 0.39366
## Proportion of Variance 0.5077 0.1965 0.1054 0.07251 0.05333 0.04242 0.02214
## Cumulative Proportion  0.5077 0.7042 0.8096 0.88211 0.93544 0.97786 1.00000
# Scree plot
  
fviz_eig(res.pca, addlabels = TRUE)

De la grafica anterior puede inferir que la dimensión 1 explica 50,8% de la variabilidad de la base de datos y que entre la dimensión 1 y 2 se explica el 70,05% de la variabilidad.

# Biplot de variables
  
fviz_pca_var(res.pca, col.var = 'contrib', gradient.cols = c('#00AFBB','#E7B800','#FC4E07'), repel = TRUE)

El primer componente esta asociado principalmente a las variables “habitaciones”, “areaconst”, “banios”, “preciom”, “parqueaderos” y “estrato”, indicando que entre mejor sean las caracteristicas de la vivienda, en cuanto a tamaño (habitaciones, área construida, número de baños y número de parqueaderos) mayor será su precio y su clasificación en el estrato socioeconómico.

Por otro lado, la variable “piso” esta asociado al segundo componente, indicando que independiente de las caracteristicas de la vivienda.

# Individuos coloreados por estrato
  
# debemos asegurar un vector habillage con misma longitud
  
ind_hab <- vivienda_sin_outliers$estrato[complete.cases(num_vars)] 
fviz_pca_ind(res.pca, geom.ind = 'point', pointsize = 1, habillage = ind_hab, addEllipses = FALSE, repel = TRUE)

Respecto a la gráfica de componentes principales por estratos se interpreta que las viviendas de estratos socioeconómicos más altos estan ubicadas más hacia la izquierda del primer componente y los estratos más bajos estan ubicados hacia la derecha, confirmando la asocaición de las variables que describen las características de la vivienda en el primer componente.

4. Análisis de Conglomerados:

# Selección variables numéricas
num_vars <- vivienda_sin_outliers %>%
  select(preciom, areaconst, estrato, banios, habitaciones, parqueaderos) %>%
  drop_na()

# Escalar variables
num_scaled <- scale(num_vars)

#Elección DE k con Silhouette

fviz_nbclust(num_scaled, kmeans, method = "silhouette") +
  labs(title = "Índice de Silhouette para K-means")

Para elegir el número de conglomerados, el indice de Silhouette que la mejor agrupación corresponde a K=2 conglomerados. Sin embargo, a continuación, se visualiza el Dendograma y se eligen en su lugar 4 grupos.

# Dendograma

dist_eucl <- dist(num_scaled, method = "euclidean")
hc <- hclust(dist_eucl, method = "complete")

plot(hc,
     main = "Dendrograma - Distancia Euclidiana",
     xlab = "Observaciones",
     sub = "",
     cex = 0.6)

# Rectángulos para k clusters (puedes ajustar k)
rect.hclust(hc, k = 4, border = 2:5)

Posteriormente se utiliza el algoritmo K-MEANS para agrupar en los 4 cluster y se utiliza componentes principales.

# K-MEANS (ejemplo con k = 4)

set.seed(123)
km <- kmeans(num_scaled, centers = 4, nstart = 50)

# Visualización de clusters en PCA
fviz_cluster(list(data = num_scaled, cluster = km$cluster),
             geom = "point",
             ellipse.type = "convex",
             main = "Clusters K-means (k = 4)")

# Perfil de cada cluster
cluster_profile <- num_vars %>%
  mutate(cluster_km = factor(km$cluster)) %>%
  group_by(cluster_km) %>%
  summarise(across(everything(), median, na.rm = TRUE), .groups = "drop")

rows_used <- which(complete.cases(num_vars))
vivienda_sin_outliers$cluster_km <- NA
vivienda_sin_outliers$cluster_km[rows_used] <- factor(km$cluster)

print(cluster_profile)
## # A tibble: 4 × 7
##   cluster_km preciom areaconst estrato banios habitaciones parqueaderos
##   <fct>        <dbl>     <dbl>   <dbl>  <dbl>        <dbl>        <dbl>
## 1 1              600     172         6      4            3            2
## 2 2              160      66.4       4      2            3            1
## 3 3              300      98         5      2            3            1
## 4 4              371     220         4      4            4            2

El grupo 1, corresponde las viviendas de con altas caracteristicas con mayor costo.

El grupo 2, agrupa las viviendas más pequeñas y de menor costo.

El grupo 3, representa las viviendas de un estrato alto, precio intermedio y poca área construida.

El grupo 4, esta conformado por viviendas con caracteristicas y precio intermedio.

Se podría inferir que la mayoria de viviendas se encuentran en el grupo 1 y 4.

5. Análisis de Correspondencia:

NOTA: No se utiliza la variable barrio, tiene demasiadas categorias.

# Preparar variables categóricas
cat_vars <- vivienda_sin_outliers %>%
  mutate(
    # Categorizar estrato en grupos más manejables
    Estrato = case_when(
      estrato %in% c(1,2) ~ "E_Bajo",
      estrato %in% c(3,4) ~ "E_Medio", 
      estrato %in% c(5,6) ~ "E_Alto"
    ),
    # Categorizar precio en rangos
    Precio = case_when(
      preciom <= 300 ~ "P_Bajo",
      preciom <= 600 ~ "P_Medio",
      preciom <= 1000 ~ "P_Alto",
      TRUE ~ "P_Muy Alto"
    ),
    # Categorizar área construida
    Area = case_when(
      areaconst <= 80 ~ "V_Pequeña",
      areaconst <= 120 ~ "V_Mediana",
      areaconst <= 180 ~ "V_Grande",
      TRUE ~ "V_Muy_Grande"
    )
  ) %>%
  select(tipo, zona, Estrato, Precio, Area) %>%
  mutate_all(as.factor) %>%
  drop_na()

# Realizar MCA
res.mca <- FactoMineR::MCA(cat_vars, graph = FALSE)

# Información del MCA
print("Varianza explicada por dimensión:")
## [1] "Varianza explicada por dimensión:"
print(res.mca$eig)
##        eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.47025366              21.375167                          21.37517
## dim 2  0.31148470              14.158395                          35.53356
## dim 3  0.24686253              11.221024                          46.75459
## dim 4  0.21552789               9.796722                          56.55131
## dim 5  0.20146662               9.157573                          65.70888
## dim 6  0.19964782               9.074901                          74.78378
## dim 7  0.18527078               8.421399                          83.20518
## dim 8  0.14766382               6.711992                          89.91717
## dim 9  0.10938797               4.972180                          94.88935
## dim 10 0.07042389               3.201086                          98.09044
## dim 11 0.04201032               1.909560                         100.00000
fviz_screeplot(res.mca, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+ ylab( "Porcentaje de varianza explicado") + xlab("Ejes")

De la grafica anterior se infiere que el primer componente solo representa el 21,4% de la varianza de los datos, se considera que representa muy poco la variabilidad de los datos.

# Individuos por zona
p_ind_zona <- fviz_mca_ind(res.mca, 
                           habillage = cat_vars$zona,
                           addEllipses = TRUE,
                           ellipse.level = 0.68,
                           geom = "point",
                           pointsize = 0.8,
                           repel = FALSE) +
  labs(title = "Distribución de Viviendas por Zona",
       subtitle = "Análisis de Correspondencia Múltiple") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
        plot.subtitle = element_text(hjust = 0.5, size = 12))

print(p_ind_zona)

Se interpreta que la mayoria de las viviendas se encuentra ubicada en la zona norte y en la zona sur de la ciudad.

# Individuos por tipo de vivienda
p_ind_tipo <- fviz_mca_ind(res.mca, 
                           habillage = cat_vars$tipo,
                           addEllipses = TRUE,
                           ellipse.level = 0.68,
                           geom = "point",
                           pointsize = 0.8,
                           palette = "Set2") +
  labs(title = "Distribución por Tipo de Vivienda",
       subtitle = "Casas vs Apartamentos en el espacio MCA") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
        plot.subtitle = element_text(hjust = 0.5, size = 12))

print(p_ind_tipo)

De la grafica se podria interpretar que el numero de casas es mayor que el numero de apartamentos.

# Individuos por PRECIO de vivienda
p_ind_precio <- fviz_mca_ind(res.mca, 
                           habillage = cat_vars$Precio,
                           addEllipses = TRUE,
                           ellipse.level = 0.68,
                           geom = "point",
                           pointsize = 0.8,
                           palette = "Set2") +
  labs(title = "Distribución por Precio de las Vivienda",
       subtitle = "Precio en el espacio MCA") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
        plot.subtitle = element_text(hjust = 0.5, size = 12))

print(p_ind_precio)

A partir de una superposición de las 3 imagenes se podria interpretar que hacia la derecha se encuentran las viviendas de mayor precio, que en su mayoria corresponde a las casas y se encuentran ubicadas en la zona oeste en mayor proporción, y en menor proporción en la zona norte y sur.

6. Visualización de resultados:

A continuación, en el siguiente mapa se pueden ubicar las diferentes viviendas con sus caracteristicas principales (Tipo, Zona, Área Construida, Precio)

geo <- vivienda_sin_outliers %>% 
  filter(!is.na(longitud), !is.na(latitud), !is.na(cluster_km))

if (nrow(geo) > 0) {
  # Crear objeto sf
  sf_pts <- st_as_sf(geo, coords = c('longitud','latitud'), crs = 4326)
  
  # Paleta de colores para clusters K-means
  pal <- colorFactor(palette = 'Set1', domain = sf_pts$cluster_km)
  
  # Mapa
  leaflet(sf_pts) %>%
    addTiles() %>%
    addCircleMarkers(
      radius = 4,
      color = ~pal(cluster_km),
      stroke = FALSE,
      fillOpacity = 0.8,
      popup = ~paste0(
        '<b>', tipo, '</b><br/>Zona: ', zona,
        '<br/>Estrato: ', estrato,
        '<br/>Área: ', round(areaconst), ' m²',
        '<br/>Precio: ', scales::comma(preciom), 'M'
      )
    ) %>%
    addLegend(
      "bottomright",
      pal = pal,
      values = ~cluster_km,
      title = "Clusters K-means",
      opacity = 1
    )
} else {
  cat('No hay coordenadas válidas para mapas.\n')
}
print(cluster_profile)
## # A tibble: 4 × 7
##   cluster_km preciom areaconst estrato banios habitaciones parqueaderos
##   <fct>        <dbl>     <dbl>   <dbl>  <dbl>        <dbl>        <dbl>
## 1 1              600     172         6      4            3            2
## 2 2              160      66.4       4      2            3            1
## 3 3              300      98         5      2            3            1
## 4 4              371     220         4      4            4            2

7. Conclusiones y recomendaciones:

  • El precio y el estrato socioeconómico de la vivienda está determinado en mayor medida por las características de la vivienda representadas en el área construida, la cantidad de habitaciones, cantidad de baños y cantidad de parqueaderos.

  • La mayoría de las viviendas se encuentran en el grupo 1 y 4, que corresponden a viviendas de excelentes características con un costo representativo y por otro lado a viviendas de características intermedias. Por lo tanto, podría inferirse que una oportunidad de mercado es dirigir la oferta inmobiliaria hacia los estratos altos y medios de la ciudad.

  • La oferta inmobiliaria puede plantearse con casas hacia un grupo selecto de clientes clase alta y con apartamentos hacia clientes clase media.

  • La base de datos caracteriza las viviendas de la ciudad en su mayoría hacia la zona norte, sur y oeste, lo que representa la zona este como potencial para explorar nuevas opciones de mercado.

  • Aunque se realizó la eliminación de outliers, el ejercicio puede realizarse sin eliminarlos para evaluar un sector exclusivo del mercado, donde posiblemente se encuentren viviendas de alto costo con características especiales.