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.

Analisis exploratorio

Durante esta etapa vamos a realizar el analisis exploratorio de los datos donde la limpieza de datos es un proceso crucial en el análisis de datos que implica la identificación, corrección y eliminación de errores, inconsistencias y valores atípicos. Este proceso es fundamental para garantizar la calidad y confiabilidad de los datos antes de realizar análisis estadísticos o modelos predictivos.

Carga de Librerias y Dataset

# Importar Librerias 
library(paqueteMODELOS)
library(dplyr)
library(naniar)
library(ggplot2)
library(leaflet)
library(tidyr)
library(mice)
library(FactoMineR)
library(factoextra)
library(ggplot2)
library(factoextra)

# Cargar los datos
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>
# Definir las características (features) excluyendo 'preciom'
features <- dplyr::select(vivienda, -preciom, -id)

# Definir la variable objetivo (target)
target <- vivienda$preciom

Inspección de Valores Missing

# Conteo de valores NA por variable y graficar datos faltantes
DataNA <- features %>%
  summarise_all(~sum(is.na(.))) %>%
  pivot_longer(cols = everything(), names_to = "variable", values_to = "n_missings")

print(DataNA)
## # A tibble: 11 × 2
##    variable     n_missings
##    <chr>             <int>
##  1 zona                  3
##  2 piso               2638
##  3 estrato               3
##  4 areaconst             3
##  5 parqueaderos       1605
##  6 banios                3
##  7 habitaciones          3
##  8 tipo                  3
##  9 barrio                3
## 10 longitud              3
## 11 latitud               3
gg_miss_var(features)

De la grafica se peude determinar que Piso y Parqueaderos son los que tienen mayor numero de de datos faltantes, se realizara un analisis de estos datos faltante vs el precio promedio de venta para ver cuanto puede afectar la imputacion de estos datos.

# Imputación de valores faltantes con una nueva categoría "Desconocido"
features <- features %>%
  mutate(parqueaderos = ifelse(is.na(parqueaderos), "Desconocido", parqueaderos),
         piso = ifelse(is.na(piso), "Desconocido", piso))
# Graficar la distribución de 'preciom' según valores faltantes en 'parqueaderos' y 'piso'
ggplot(features, aes(x = parqueaderos == "Desconocido", y = target)) +
  geom_boxplot() +
  labs(x = "Parqueaderos desconocidos", y = "Precio", title = "Distribución de precios según parqueaderos desconocidos")

ggplot(features, aes(x = piso == "Desconocido", y = target)) +
  geom_boxplot() +
  labs(x = "Piso desconocido", y = "Precio", title = "Distribución de precios según piso desconocido")

Nota:

Una forma efectiva de manejar estos datos faltantes en variables categóricas, como “piso” o “parqueadero”, es crear una nueva categoría que represente estos valores faltantes en lugar de eliminarlos. Esto permite conservar todas las observaciones en el dataset sin perder información.

# Mostrar los valores únicos de 'piso' y 'parqueaderos'
valores_unicos_piso <- unique(features$piso)
print(valores_unicos_piso)
##  [1] "Desconocido" "02"          "01"          "03"          "04"         
##  [6] "05"          "06"          "07"          "08"          "09"         
## [11] "10"          "11"          "12"
valores_unicos_parq <- unique(features$parqueaderos)
print(valores_unicos_parq)
##  [1] "1"           "2"           "3"           "Desconocido" "4"          
##  [6] "7"           "5"           "8"           "6"           "9"          
## [11] "10"

Para las variables numericas y categoricas restantes los datos faltantes son pocos estos se imputaran por la medidana y la moda.

# Imputar valores faltantes en otras variables numéricas con la mediana
features <- features %>%
  mutate(across(c(areaconst, banios, habitaciones, longitud, latitud), 
                ~ifelse(is.na(.), median(., na.rm = TRUE), .)))

# Imputar con la moda
mode_imputation <- function(x) {
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}

features <- features %>%
  mutate(
    zona = ifelse(is.na(zona), mode_imputation(zona), zona),
    estrato = ifelse(is.na(estrato), mode_imputation(estrato), estrato),
    tipo = ifelse(is.na(tipo), mode_imputation(tipo), tipo),
    barrio = ifelse(is.na(barrio), mode_imputation(barrio), barrio)
  )

Verificar que el dataset no tenga datos faltantes:

# Recuento final de valores NA
DataNA <- features %>%
  summarise_all(~sum(is.na(.))) %>%
  pivot_longer(cols = everything(), names_to = "variable", values_to = "n_missings")

print(DataNA)
## # A tibble: 11 × 2
##    variable     n_missings
##    <chr>             <int>
##  1 zona                  0
##  2 piso                  0
##  3 estrato               0
##  4 areaconst             0
##  5 parqueaderos          0
##  6 banios                0
##  7 habitaciones          0
##  8 tipo                  0
##  9 barrio                0
## 10 longitud              0
## 11 latitud               0

Gráficos

# Gráfico de barras para 'zona'
ggplot(features, aes(x = zona)) +
  geom_bar() +
  labs(title = "Distribución de Zona", x = "Zona", y = "Frecuencia") +
  theme_minimal()

# Gráfico de barras para 'estrato'
ggplot(features, aes(x = as.factor(estrato))) +
  geom_bar() +
  labs(title = "Distribución de Estrato", x = "Estrato", y = "Frecuencia") +
  theme_minimal()

# Gráfico de barras para 'tipo'
ggplot(features, aes(x = tipo)) +
  geom_bar() +
  labs(title = "Distribución de Tipo de Vivienda", x = "Tipo", y = "Frecuencia") +
  theme_minimal()

Análisis de Componentes Principales:

El PCA se utilizó para reducir la dimensionalidad del conjunto de datos y revelar la estructura subyacente de las variables que influyen en la variabilidad de los precios de las viviendas. Se seleccionaron variables numéricas: estrato, areaconst, banios, habitaciones, longitud, y latitud.

# Seleccionar las variables numéricas, excluyendo 'preciom'
features_numeric <- features %>%
  select_if(is.numeric) %>%
  na.omit()  # Eliminar NA antes de la estandarización

# Verificar el resultado
head(features_numeric)
## # A tibble: 6 × 6
##   estrato areaconst banios habitaciones longitud latitud
##     <dbl>     <dbl>  <dbl>        <dbl>    <dbl>   <dbl>
## 1       3        70      3            6    -76.5    3.43
## 2       3       120      2            3    -76.5    3.43
## 3       3       220      2            4    -76.5    3.44
## 4       4       280      5            3    -76.5    3.44
## 5       5        90      2            3    -76.5    3.46
## 6       5        87      3            3    -76.5    3.37

Nota:

Los datos fueron estandarizados antes de aplicar el PCA para asegurar que todas las variables contribuyeran equitativamente al análisis.

# Estandarizar los datos 
viviendaZ <- scale(features_numeric)
res.pca <- prcomp(viviendaZ)
fviz_eig(res.pca, addlabels = TRUE)

# Realizar PCA
pca_result <- PCA(viviendaZ, scale.unit = TRUE, ncp = 5, graph = FALSE)


# Visualización de componentes principales
fviz_pca_var(pca_result, col.var = "cos2", gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"), repel = TRUE)

Resultados:

El PCA reveló que los dos primeros componentes principales explican aproximadamente el 65.3% de la varianza total (Dim1: 40.6%, Dim2: 24.7%). El primer componente está altamente correlacionado con variables relacionadas al tamaño de la propiedad como areaconst, habitaciones y baños. El segundo componente refleja variaciones en la ubicación geográfica (longitud, latitud) y en la calidad de vida representada por el estrato.

Interpretación:

Las variables de tamaño (habitaciones, baños) y estrato tienen un impacto significativo en la variabilidad del precio de las propiedades.

Este resultado sugiere que la empresa debe centrar sus estrategias de valoración y promoción de propiedades en estos factores para maximizar el valor percibido y la competitividad en el mercado.

Análisis de Conglomerados:

El Análisis de Conglomerados se llevó a cabo utilizando el método K-means para identificar segmentos de mercado homogéneos basados en características similares de las propiedades.

# Método del Codo para determinar el número óptimo de clusters
fviz_nbclust(viviendaZ, kmeans, method = "wss")

# Método de la Silueta
fviz_nbclust(viviendaZ, kmeans, method = "silhouette")

# Aplicar K-means con el número óptimo de clusters (por ejemplo, 3)
set.seed(123)  # Para reproducibilidad
km_res <- kmeans(viviendaZ, centers = 3, nstart = 25)

# Visualizar los clusters en el espacio PCA
fviz_cluster(km_res, data = viviendaZ, ellipse.type = "norm", geom = "point", stand = FALSE, palette = "jco")

Resultados:

Se determinó el número óptimo de clusters usando el método del codo son 3 y el método de la silueta indica 2. La discrepancia entre k=2 y k=3 indica que hay una posible estructura en los datos que podría ser capturada por 2 clusters con alta calidad (cohesión interna), pero visualmente (y tal vez en términos de interpretación de los segmentos del mercado), 3 clusters también podrían ser una opción válida. En la práctica, elegir entre k=2 o k=3 dependería del balance entre la simplicidad del modelo y la necesidad de una segmentación más detallada.

Para este caso de analisis se determino el uso de K=3.

  • Cluster 1: Propiedades más grandes y de mayor estrato ubicadas en zonas premium.
  • Cluster 2: Propiedades medianas, mayormente apartamentos en zonas centrales y norte de la ciudad.
  • Cluster 3: Propiedades pequeñas en estratos medios y bajos, predominantemente en la zona sur y oriente

Interpretación:

Cada cluster representa un segmento de mercado distinto, con necesidades y preferencias particulares. La empresa puede diseñar estrategias de marketing personalizadas para cada segmento, como:

  • Promociones para propiedades premium en el Cluster 1
  • Campañas de accesibilidad y financiamiento para propiedades en el Cluster 3.

Análisis de Correspondencia :

El Análisis de Correspondencia se utilizó para investigar las relaciones entre variables categóricas como zona, banios, habitaciones, y tipo de vivienda.

Correspondencia entre Zona y Baños

# Cálculo de la tabla de correspondencia
tabla_contingencia <- table(features$zona, features$banios)
print(tabla_contingencia)
##               
##                   0    1    2    3    4    5    6    7    8    9   10
##   Zona Centro     3   16   43   26   19   11    4    0    1    1    0
##   Zona Norte     12  186  811  438  270  130   48   11   11    1    2
##   Zona Oeste      7   38  237  315  350  185   43   19    2    2    0
##   Zona Oriente    5   68  108   78   52   19   13    2    4    1    1
##   Zona Sur       18  188 1747 1139  765  545  206   75   30   10    6
# Prueba X2 para determinar si existe o no una asociación significativa entre las variables
chisq.test(tabla_contingencia)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_contingencia
## X-squared = 560.98, df = 40, p-value < 2.2e-16
# Análisis de correspondencia
resultados_ac <- CA(tabla_contingencia)

# Verifica que resultados_ac tiene dimensiones y variables
print(resultados_ac)
## **Results of the Correspondence Analysis (CA)**
## The row variable has  5  categories; the column variable has 11 categories
## The chi square of independence between the two variables is equal to 560.9798 (p-value =  4.369821e-93 ).
## *The results are available in the following objects:
## 
##    name              description                   
## 1  "$eig"            "eigenvalues"                 
## 2  "$col"            "results for the columns"     
## 3  "$col$coord"      "coord. for the columns"      
## 4  "$col$cos2"       "cos2 for the columns"        
## 5  "$col$contrib"    "contributions of the columns"
## 6  "$row"            "results for the rows"        
## 7  "$row$coord"      "coord. for the rows"         
## 8  "$row$cos2"       "cos2 for the rows"           
## 9  "$row$contrib"    "contributions of the rows"   
## 10 "$call"           "summary called parameters"   
## 11 "$call$marge.col" "weights of the columns"      
## 12 "$call$marge.row" "weights of the rows"
valores_prop <-resultados_ac$eig 
print(valores_prop)
##         eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.0456251374              67.683793                          67.68379
## dim 2 0.0182336297              27.049151                          94.73294
## dim 3 0.0026529861               3.935641                          98.66858
## dim 4 0.0008974969               1.331415                         100.00000
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")

fviz_ca_biplot(resultados_ac, repel = TRUE, ggtheme = theme_minimal())

Correspondencia entre Zona y Habitaciones

# Cálculo de la tabla de correspondencia
tabla_contingencia <- table(features$zona, features$habitaciones)
print(tabla_contingencia)
##               
##                   0    1    2    3    4    5    6    7    8    9   10
##   Zona Centro     3    3   12   28   19   20   11    9    8    7    4
##   Zona Norte     29   17  220 1008  342  146   60   42   29   14   13
##   Zona Oeste      8   16  173  622  272   64   22    6    5    6    4
##   Zona Oriente    6    2   31   89   52   34   42   31   32   26    6
##   Zona Sur       20   21  490 2353 1044  415  183   85   64   30   24
# Prueba X2 para determinar si existe o no una asociación significativa entre las variables
chisq.test(tabla_contingencia)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_contingencia
## X-squared = 721.79, df = 40, p-value < 2.2e-16
# Análisis de correspondencia
resultados_ac <- CA(tabla_contingencia)

# Verifica que resultados_ac tiene dimensiones y variables
print(resultados_ac)
## **Results of the Correspondence Analysis (CA)**
## The row variable has  5  categories; the column variable has 11 categories
## The chi square of independence between the two variables is equal to 721.7885 (p-value =  6.225371e-126 ).
## *The results are available in the following objects:
## 
##    name              description                   
## 1  "$eig"            "eigenvalues"                 
## 2  "$col"            "results for the columns"     
## 3  "$col$coord"      "coord. for the columns"      
## 4  "$col$cos2"       "cos2 for the columns"        
## 5  "$col$contrib"    "contributions of the columns"
## 6  "$row"            "results for the rows"        
## 7  "$row$coord"      "coord. for the rows"         
## 8  "$row$cos2"       "cos2 for the rows"           
## 9  "$row$contrib"    "contributions of the rows"   
## 10 "$call"           "summary called parameters"   
## 11 "$call$marge.col" "weights of the columns"      
## 12 "$call$marge.row" "weights of the rows"
valores_prop <-resultados_ac$eig 
print(valores_prop)
##        eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.074547379              85.950844                          85.95084
## dim 2 0.006698030               7.722623                          93.67347
## dim 3 0.003381309               3.898545                          97.57201
## dim 4 0.002105856               2.427988                         100.00000
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")

fviz_ca_biplot(resultados_ac, repel = TRUE, ggtheme = theme_minimal())

Correspondencia entre Zona y Tipo de Vivienda

# Cálculo de la tabla de correspondencia
tabla_contingencia <- table(features$zona, features$tipo)
print(tabla_contingencia)
##               
##                Apartamento Casa
##   Zona Centro           24  100
##   Zona Norte          1198  722
##   Zona Oeste          1029  169
##   Zona Oriente          62  289
##   Zona Sur            2790 1939
# Prueba X2 para determinar si existe o no una asociación significativa entre las variables
chisq.test(tabla_contingencia)
## 
##  Pearson's Chi-squared test
## 
## data:  tabla_contingencia
## X-squared = 690.79, df = 4, p-value < 2.2e-16
# Análisis de correspondencia
resultados_ac <- CA(tabla_contingencia)

# Verifica que resultados_ac tiene dimensiones y variables
print(resultados_ac)
## **Results of the Correspondence Analysis (CA)**
## The row variable has  5  categories; the column variable has 2 categories
## The chi square of independence between the two variables is equal to 690.787 (p-value =  3.444123e-148 ).
## *The results are available in the following objects:
## 
##    name              description                   
## 1  "$eig"            "eigenvalues"                 
## 2  "$col"            "results for the columns"     
## 3  "$col$coord"      "coord. for the columns"      
## 4  "$col$cos2"       "cos2 for the columns"        
## 5  "$col$contrib"    "contributions of the columns"
## 6  "$row"            "results for the rows"        
## 7  "$row$coord"      "coord. for the rows"         
## 8  "$row$cos2"       "cos2 for the rows"           
## 9  "$row$contrib"    "contributions of the rows"   
## 10 "$call"           "summary called parameters"   
## 11 "$call$marge.col" "weights of the columns"      
## 12 "$call$marge.row" "weights of the rows"
valores_prop <-resultados_ac$eig 
print(valores_prop)
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.08300733                    100                               100

Resultados:

Se construyeron tablas de contingencia para analizar la asociación entre zona y el número de banios, habitaciones, y tipo de vivienda. Las pruebas de Chi-cuadrado confirmaron asociaciones significativas entre las variables (p-value < 2.2e-16). Los mapas biplot del análisis de correspondencia mostraron agrupaciones claras, como la fuerte asociación entre la Zona Norte y propiedades con mayor número de banios y habitaciones, o la predominancia de Casas en la Zona Oriente.

Interpretación:

El análisis revela patrones de comportamiento en la oferta de propiedades según la zona, destacando cómo ciertas características son más comunes en determinadas áreas. La empresa puede usar esta información para orientar sus esfuerzos de adquisición y desarrollo de proyectos en zonas donde ciertas características de las propiedades están en alta demanda.

Conclusiones:

  • Optimización de la Oferta: Concentrarse en las características de las propiedades que tienen un impacto significativo en el precio y la preferencia del mercado, como el tamaño y la ubicación.

  • Segmentación de Mercado: Desarrollar estrategias de marketing dirigidas a los diferentes segmentos identificados en el análisis de conglomerados, personalizando las ofertas según las características y necesidades de cada grupo.

  • Planificación Geográfica: Aprovechar las asociaciones descubiertas en el análisis de correspondencia para focalizar las inversiones en áreas donde la demanda de ciertas características es más alta, optimizando la rentabilidad de los proyectos inmobiliarios.