Enunciado

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 consisten 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: - 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. - 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. - 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. - 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.

Solución

Exploración de los datos

  • Dimensión del dataframe
dimension <- dim(datos_inmobiliarios)
dimension 
[1] 8322   13

Se cuenta con 8322 registros y 13 dimensiones.

  • Veamos el resumen estadístico de las variables.
summary(datos_inmobiliarios)
       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      

Aquí podemos observar las medidas de centralidad de las variables, además de las medidas de dispersión.

El estudio inmobiliario no tendrá en cuenta la ubicación por tanto se pueden eliminar latitud, longitud y barrio.

  • Eliminación de variables no requeridas en el estudio
# Eliminar latitud, longitud y barrio
datos_inmobiliarios <- subset(datos_inmobiliarios, select = -c(longitud, latitud, barrio))
head(datos_inmobiliarios,5)
# A tibble: 5 × 10
     id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1147 Zona O… <NA>        3     250        70            1      3            6
2  1169 Zona O… <NA>        3     320       120            1      2            3
3  1350 Zona O… <NA>        3     350       220            2      2            4
4  5992 Zona S… 02          4     400       280            3      5            3
5  1212 Zona N… 01          5     260        90            1      2            3
# ℹ 1 more variable: tipo <chr>
  • Identificación de la estructura del dataset y los tipos de datos
str(datos_inmobiliarios)
tibble [8,322 × 10] (S3: 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" ...

El dataset depurado presenta:

  • 3 variables categóricas -> zona, piso, tipo
  • 6 variables numéricas -> estrato, preciom, areaconst, parqueaderos, banios, habitaciones

El id es numérica pero no ofrece información relevante para el caso de estudio.

Aunque el estrato es un número, puede ser considerada como categórica también, ya que no puedo extraer información de métricas como media o mediana, siendo la moda la única relevante, en caso de imputación.

A partir de este punto se podrá realizar la preparación de la base de datos

Preprocesamiento de la base de datos

En esta sección se tendrá un mayor conocimiento de los datos y se evaluarán las variables con datos faltantes para determinar qué método de imputación será aplicado en cada caso.

  • Revisión de datos faltantes
md.pattern(datos_inmobiliarios, rotate.names=TRUE)

     preciom id zona estrato areaconst banios habitaciones tipo parqueaderos
4808       1  1    1       1         1      1            1    1            1
1909       1  1    1       1         1      1            1    1            1
876        1  1    1       1         1      1            1    1            0
726        1  1    1       1         1      1            1    1            0
1          1  0    0       0         0      0            0    0            0
2          0  0    0       0         0      0            0    0            0
           2  3    3       3         3      3            3    3         1605
     piso     
4808    1    0
1909    0    1
876     1    1
726     0    2
1       0    9
2       0   10
     2638 4266

En esta distribución de datos faltantes podemos ver que:

  • hay un registro con todos los datos nulos excepto preciom
  • hay dos registros con todos los datos nulos
  • hay 4808 registros con datos completos
  • hay 1909 registros con la variable tipo faltante
  • hay 876 registros con la variable parqueadero faltante
  • hay 726 registros con tipo y parquedaro faltantes

Entendiendo los datos

Se evaluarán cada una de las variables y una revisión de la tabla de frecuencia de cada una para entender un poco mejor como están distribuidos los datos.

  • Parqueaderos
table(datos_inmobiliarios$parqueaderos)

   1    2    3    4    5    6    7    8    9   10 
3155 2475  520  384   68   68   18   17    4    8 

Se sabe que hay 1605 datos con parqueadero faltante, pero no hay un valor de 0 para parqueadero en el dataframe, esto puede ser que no se contaba con la opción de viviendas sin parqueadero y por eso los datos faltantes. En el proceso de imputación se utilizará el valor 0 para los parqueaderos, ya que incluirlo bien sea por la moda o el promedio puede esconder los casos en los cuales las viviendas no tienen parqueadero, que también es un caso válido y tienen además impacto en las características que serán consideradas posteriormente.

  • Baños
table(datos_inmobiliarios$banios)

   0    1    2    3    4    5    6    7    8    9   10 
  45  496 2946 1993 1456  890  314  107   48   15    9 

Aunque se tiene un total de 45 viviendas sin baño, eso podría ser válido y por tanto no se modificará dicho valor que podría considerarse como algo inusual.

  • Habitaciones
table(datos_inmobiliarios$habitaciones)

   0    1    2    3    4    5    6    7    8    9   10 
  66   59  926 4097 1729  679  318  173  138   83   51 

Aquí también encontramos viviendas con 0 habitaciones, de nuevo si bien es un dato no esperado, se decide no modificarlo para el análisis.

A continuación revisaremos que las variables categóricas tengan los valores normalizados

  • Tipo de vivienda
table(datos_inmobiliarios$tipo)

Apartamento        Casa 
       5100        3219 
  • Zona
table(datos_inmobiliarios$zona)

 Zona Centro   Zona Norte   Zona Oeste Zona Oriente     Zona Sur 
         124         1920         1198          351         4726 
  • Piso
table(datos_inmobiliarios$piso)

  01   02   03   04   05   06   07   08   09   10   11   12 
 860 1450 1097  607  567  245  204  211  146  130   84   83 

Esta es otra variable con datos faltantes, de esta tabla de frecuencias vemos que 2 es la moda, y considerando que la mayoría de los datos corresponde a apartamentos en la zona sur, se utilizará la moda como valor para hacer la imputación de datos faltantes en los registros para piso.

  • Estrato
table(datos_inmobiliarios$estrato)

   3    4    5    6 
1453 2129 2750 1987 

En cuanto a las variables categóricas se comprueba que todas están normalizadas y no es necesario hacer algún tipo de modficación en ninguna de ellas.

Limpieza del dataframe

  • Eliminación de registros con id nulo

En la revisión de datos faltantes se encontró que hay 3 registros con solo un dato (preciom) por lo tanto se procede a su eliminación

# Eliminar registros con 'id' nulo
datos_inmobiliarios <- datos_inmobiliarios %>%
  filter(!is.na(id))
md.pattern(datos_inmobiliarios, rotate.names=TRUE)

     id zona estrato preciom areaconst banios habitaciones tipo parqueaderos
4808  1    1       1       1         1      1            1    1            1
1909  1    1       1       1         1      1            1    1            1
876   1    1       1       1         1      1            1    1            0
726   1    1       1       1         1      1            1    1            0
      0    0       0       0         0      0            0    0         1602
     piso     
4808    1    0
1909    0    1
876     1    1
726     0    2
     2635 4237

Ahora ya solo quedan datos faltantes en la variables piso y parqueadero.

  • Imputación parqueaderos

Como se indicó anteriormente se realizará una imputación de los datos faltantes por el valor 0 para parqueadero

datos_inmobiliarios$parqueaderos[is.na(datos_inmobiliarios$parqueaderos)] <- 0
grafico <-md.pattern(datos_inmobiliarios, rotate.names = TRUE)

Ahora solo nos queda revisar el atributo piso para su imputación

  • Imputación piso

En la sección de exploración de datos se encontró que 2 es la moda para piso, sin embargo, considerando que cerca del 38% de los datos corresponde a casas, resulta interesante cuál es el valor más común tanto para casas como para apartamentos.

# Función para calcular la moda
calculate_mode <- function(x) {
  mfv(x, na_rm = TRUE)  # mfv() devuelve el valor más frecuente
}

# Calcular la moda de la variable 'piso' por 'tipo'
mode_by_type <- datos_inmobiliarios %>%
  group_by(tipo) %>%
  summarise(
    moda_piso = calculate_mode(piso)
  )

# Mostrar el resultado
print(mode_by_type)
# A tibble: 2 × 2
  tipo        moda_piso
  <chr>       <chr>    
1 Apartamento 03       
2 Casa        02       

Vemos que para las casas la moda es 2 mientras que para los apartamentos la moda es 3.

Ahora exploremos que impacto tiene unificar la imputación al piso 2 como la moda general para la variable piso o si es relevante imputar por tipo de vivienda.

# Filtrar los registros para los pisos 2 y 3 y contar las frecuencias por tipo
frecuencias_tipo_por_piso <- datos_inmobiliarios %>%
  filter(piso %in% c("2", "3")) %>%
  count(tipo, piso)

# Mostrar las frecuencias
print(frecuencias_tipo_por_piso)
# A tibble: 0 × 3
# ℹ 3 variables: tipo <chr>, piso <chr>, n <int>

Como podemos ver en los apartamentos tenemos casi 4 veces más inmuebles en el piso 3 que en el piso 2, por lo tanto no sería correcto darle más importancia al piso 2 que al 3.

Debido a esto se decide imputar asi:

  • Casa -> 2
  • Apartamento -> 3
datos_inmobiliarios <- datos_inmobiliarios %>%
  mutate(piso = as.numeric(piso))

datos_inmobiliarios <- datos_inmobiliarios %>%
  mutate(piso = case_when(
    tipo == "Casa" & is.na(piso) ~ 2,
    tipo == "Apartamento" & is.na(piso) ~ 3,
    TRUE ~ piso
  ))
  • Validación datos faltantes
total_faltantes <- colSums(is.na(datos_inmobiliarios))
datos_faltantes <- data.frame(Atributo = names(total_faltantes), Faltantes = total_faltantes)
print(datos_faltantes)
                 Atributo Faltantes
id                     id         0
zona                 zona         0
piso                 piso         0
estrato           estrato         0
preciom           preciom         0
areaconst       areaconst         0
parqueaderos parqueaderos         0
banios             banios         0
habitaciones habitaciones         0
tipo                 tipo         0

En este punto ya contamos con un dataset al que se le han eliminado/imputado los datos faltantes.

Examinamos los valores de cada registro de la base de datos con su tipo de variable, en la siguiente tabla

glimpse(datos_inmobiliarios)
Rows: 8,319
Columns: 10
$ id           <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
$ zona         <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
$ piso         <chr> "2", "2", "2", "2", "1", "1", "1", "1", "2", "2", "2", "2…
$ estrato      <dbl> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
$ preciom      <dbl> 250, 320, 350, 400, 260, 240, 220, 310, 320, 780, 750, 62…
$ areaconst    <dbl> 70, 120, 220, 280, 90, 87, 52, 137, 150, 380, 445, 355, 2…
$ parqueaderos <dbl> 1, 1, 2, 3, 1, 1, 2, 2, 2, 2, 0, 3, 2, 2, 1, 4, 2, 2, 2, …
$ banios       <dbl> 3, 2, 2, 5, 2, 3, 2, 3, 4, 3, 7, 5, 6, 2, 4, 4, 4, 3, 2, …
$ habitaciones <dbl> 6, 3, 4, 3, 3, 3, 3, 4, 6, 3, 6, 5, 6, 2, 5, 5, 4, 3, 3, …
$ tipo         <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…

Relación entre algunas variables

Considerando que se busca analizar las dinámicas de las ofertas en diferentes partes de la ciudad y su relación con el precio y el estrato socioeconómico, se continuará con el análisis exploratorio de estas relacciones para tener una idea de su comportamiento.

  • Distribución de inmuebles por zona en relación al precio
boxplot(datos_inmobiliarios$preciom~ datos_inmobiliarios$zona, data = datos_inmobiliarios, col = c("pink", "cyan", "orange","green","purple" ), ylab = "Precio", xlab = "Zona")

  • Distribución de inmuebles por estrato en relación al precio
boxplot(datos_inmobiliarios$preciom ~ datos_inmobiliarios$estrato, data = datos_inmobiliarios, col = c("purple", "green", "red", "pink"), ylab = "Precio", xlab = "Estrato")

De estos diagramas boxplot podemos identificar que en las zonas oeste, norte y sur se encuentran los inmuebles con mayor precio y además con un número significativo de outliers, mientras que en la zona centro y zona oriente vemos los precios más estables con menor número de outliers.

Con respecto a la relación de precio y estrato solo el estrato 6 considera como datos válidos viviendas alrededor de los 1000 millones mientras que para los demás estratos esta oferta por encima de los 1000 millones es considerado como un dato atípico, a pesar que es significativo el número de datos atípicos en dichos estratos (3,4,5).

Punto 1 - Análisis de componentes principales (PCA)

El PCA es una técnica de reducción de dimensionalidad para transformar un conjunto de variables posiblemente correlacionadas en un conjunto de variables no correlacionadas llamadas componentes principales.

Lo primero es calcular la matriz de correlacción para entender las relaciones lineales. Además debemos convertir las variables categóricas en variables numéricas

matriz de correlación

var_numericas <- datos_inmobiliarios[, c("preciom","estrato", "areaconst", "habitaciones", "banios","parqueaderos")]
correlacion<-round(cor(var_numericas), 1)
corrplot(correlacion, method="number", type="upper")

  • para el preciom podemos observar una correlación fuerte con areaconst, banios, estrato y parqueaderos, indica que un aumento en cualquiera de esas características podría impactar directamente en el precio.
  • para el estrato vemos una correlación moderada con banios, preciom y parqueaderos, esto sugiere que entre mayor sea el estrato se encontrarán propiedades con más baños y parqueaderos, por ende serán más costosas.
  • para el área construida la correlación es fuerte con preciom y moderada con baños y habitaciones, indicando que a mayor área encotraremos mayor número de habitaciones y baños y por ende tendrán mayor precio.
  • para habitaciones encontramos correlación moderada con area y baños.
  • para baños encontramos una correlación fuerte con precio y area construida, esto indica que propiedades con más baños suelen ser más grandes y costosas.
  • para parqueaderos vemos una correlación moderada con precio, estrato, área construida y baños, esto sugiere que propiedades con más parqueaderos suelen estar ubicadas en estratos más altos, con más área y con más baños, por lo tanto son las más costosas también.

Conclusión

  • relaciones significativas entre preciom, areaconst, banios y parqueaderos.
  • estas correlaciones sugieren cierta redundancia en estas variables, sugiere que preciom puede estar mostrando información que también está reflejada en areaconst, banios y parqueaderos.
  • se espera que estas variables altamente correlacionadas contribuyan principalmente a las primeras componentes principales.
  • este dataset es un buen candidato para aplicar PCA

Codificación de variables categóricas

  • Codificación zona
datos_inmobiliarios_num <- datos_inmobiliarios %>%
  mutate(zona = case_when(
    zona == "Zona Centro" ~ 1,
    zona == "Zona Norte" ~ 2,
    zona == "Zona Oeste" ~ 3,
    zona == "Zona Oriente" ~ 4,
    zona == "Zona Sur" ~ 5,
    TRUE ~ NA_real_  # Si existe alguna categoría no especificada, será convertida a NA
  ))
head(datos_inmobiliarios_num,5)
# A tibble: 5 × 10
     id  zona piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <dbl> <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1147     4 2           3     250        70            1      3            6
2  1169     4 2           3     320       120            1      2            3
3  1350     4 2           3     350       220            2      2            4
4  5992     5 2           4     400       280            3      5            3
5  1212     2 1           5     260        90            1      2            3
# ℹ 1 more variable: tipo <chr>
  • Codificación tipo
datos_inmobiliarios_num <- datos_inmobiliarios_num %>%
  mutate(tipo = case_when(
    tipo == "Casa" ~ 1,
    tipo == "Apartamento" ~ 2,
    TRUE ~ NA_real_  # Si existe alguna categoría no especificada, será convertida a NA
  ))
head(datos_inmobiliarios_num,5)
# A tibble: 5 × 10
     id  zona piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <dbl> <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1147     4 2           3     250        70            1      3            6
2  1169     4 2           3     320       120            1      2            3
3  1350     4 2           3     350       220            2      2            4
4  5992     5 2           4     400       280            3      5            3
5  1212     2 1           5     260        90            1      2            3
# ℹ 1 more variable: tipo <dbl>
  • Codificación piso
datos_inmobiliarios_num$piso <- as.numeric(as.factor(datos_inmobiliarios_num$piso))
head(datos_inmobiliarios_num,5)
# A tibble: 5 × 10
     id  zona  piso estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <dbl> <dbl>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1147     4     5       3     250        70            1      3            6
2  1169     4     5       3     320       120            1      2            3
3  1350     4     5       3     350       220            2      2            4
4  5992     5     5       4     400       280            3      5            3
5  1212     2     1       5     260        90            1      2            3
# ℹ 1 more variable: tipo <dbl>

Ahora el dataset está preparado para realizar el análisis de componentes prinicpales

PCA

  • Escalamiento de las variables

  • Validamos que todos los datos sean numéricos

str(datos_inmobiliarios_num)
tibble [8,319 × 10] (S3: tbl_df/tbl/data.frame)
 $ id          : num [1:8319] 1147 1169 1350 5992 1212 ...
 $ zona        : num [1:8319] 4 4 4 5 2 2 2 2 2 2 ...
 $ piso        : num [1:8319] 5 5 5 5 1 1 1 1 5 5 ...
 $ estrato     : num [1:8319] 3 3 3 4 5 5 4 5 5 5 ...
 $ preciom     : num [1:8319] 250 320 350 400 260 240 220 310 320 780 ...
 $ areaconst   : num [1:8319] 70 120 220 280 90 87 52 137 150 380 ...
 $ parqueaderos: num [1:8319] 1 1 2 3 1 1 2 2 2 2 ...
 $ banios      : num [1:8319] 3 2 2 5 2 3 2 3 4 3 ...
 $ habitaciones: num [1:8319] 6 3 4 3 3 3 3 4 6 3 ...
 $ tipo        : num [1:8319] 1 1 1 1 2 2 2 2 1 1 ...
datos_inmobiliarios_num_scaled <- scale(datos_inmobiliarios_num)
datos_inmobiliarios_num_scaled <- as.data.frame(datos_inmobiliarios_num_scaled)
tail(datos_inmobiliarios_num_scaled)
              id     zona       piso    estrato    preciom   areaconst
8314 -0.21651936 0.815085  0.1399649  1.3275950  1.3268694  0.49008841
8315  0.06995241 0.815085 -0.2882690  1.3275950  3.7001064  1.99396219
8316  0.29355029 0.815085 -0.2882690 -1.5872276 -0.2248625  0.04941842
8317  0.93977731 0.815085 -0.2882690  1.3275950  4.1564981  1.57427649
8318  1.18169606 0.815085  0.1399649  1.3275950  1.7224089  0.09838176
8319  1.65678951 0.815085 -0.2882690  0.3559875  0.2923815 -0.23037205
     parqueaderos      banios habitaciones       tipo
8314   -0.3875522  0.62223934   -0.4147626  0.7944184
8315    0.4168506  2.02259346    0.2703863 -1.2586312
8316   -0.3875522 -0.07793773    3.6961306 -1.2586312
8317    1.2212534  2.02259346    0.9555352 -1.2586312
8318    1.2212534  1.32241640    0.2703863  0.7944184
8319    0.4168506  0.62223934    0.2703863 -1.2586312

Cálculo de PCA

variablesNumericas <- datos_inmobiliarios_num_scaled %>% dplyr::select(piso, preciom, areaconst, parqueaderos, banios, habitaciones, estrato, zona)
datos_inm.pca <- prcomp(variablesNumericas)
datos_inm.pca
Standard deviations (1, .., p=8):
[1] 1.8532329 1.1685063 0.9970460 0.9515347 0.6750110 0.6451422 0.4906759
[8] 0.4335791

Rotation (n x k) = (8 x 8):
                     PC1        PC2         PC3         PC4          PC5
piso          0.06721048 -0.4387639  0.15296528  0.87396713  0.082377461
preciom      -0.47468922 -0.1573824  0.14857876 -0.09321109 -0.018440279
areaconst    -0.44120593  0.2299892  0.05875703  0.04282434  0.192814444
parqueaderos -0.40808803 -0.2432729 -0.01670285 -0.10785949  0.731011225
banios       -0.46560386  0.1393367 -0.02893869  0.17083853 -0.322119268
habitaciones -0.28627095  0.5727749 -0.12289083  0.37899533 -0.141173171
estrato      -0.32704957 -0.5343313  0.07429409 -0.19378484 -0.545529825
zona         -0.05705210 -0.1940220 -0.96402043  0.06041290 -0.005060835
                    PC6         PC7          PC8
piso          0.0937694  0.01090957  0.007370742
preciom       0.3300217 -0.21059695 -0.752018290
areaconst     0.6614891  0.27014081  0.446581137
parqueaderos -0.4677809  0.05096568  0.081092115
banios       -0.2236328 -0.68215047  0.338634514
habitaciones -0.3524877  0.45169871 -0.288147519
estrato      -0.1681758  0.45859516  0.168110224
zona          0.1528523 -0.01690652 -0.049397840
  • PC1 explica la mayor parte de la varianza en los datos y parece estar fuertemente influenciado por las variables banios, preciom, areaconst y parqueadero, lo cual tiene sentido ya que son las variables con las correlaciones más altas.
  • PC2 explica la segunda mayor cantidad de varianza con cargas importantes en tipo, estrato, esta relación no se mostró tan claramente en la matriz de correlación ya que era moderada.
  • PC3 tiene una gran carga en zona, lo que sugiere que está capturando la variabilidad relacionada con al ubicación de la vivienda.
fviz_eig(datos_inm.pca, addlabels = TRUE)

prop_varianza <- datos_inm.pca$sdev^2/sum(datos_inm.pca$sdev^2)
round(prop_varianza*100, 2)
[1] 42.93 17.07 12.43 11.32  5.70  5.20  3.01  2.35
prop_varianza_acum <- cumsum(prop_varianza)
round(prop_varianza_acum*100, 2)
[1]  42.93  60.00  72.42  83.74  89.44  94.64  97.65 100.00

Gráfico de varianza acumulada

ggplot(data = data.frame(prop_varianza_acum, pc = 1:8),
       aes(x = pc, y = prop_varianza_acum, group = 1)) +
  geom_point() +
  geom_line() +
  theme_bw() +
  labs(x = "Componente principal",
       y = "Prop. varianza explicada acumulada")

De esta gráfica se sugiere usar las primeras 4 componentes que explican el 83.74% de la varianza observada.

  • Gráfico de sedimentación
scree(variablesNumericas,main ="Grafico de Sedimentacion")

fviz_pca_var(datos_inm.pca,
col.var = "contrib", # 
gradient.cols = c("#FF7F00",  "#034D94"),
repel = TRUE     
)

  • Contribución PC1 En esta gráfica de componentes principales se puede inferir que el primer componente principal está asociado principalmente con las variables preciom, banios, areaconst y parqueaderos.
fviz_contrib(datos_inm.pca, choice = "var", axes = 1, top = 10) # PC1

  • Contribución PC2
fviz_contrib(datos_inm.pca, choice = "var", axes = 2, top = 10) # PC2

En PC2 vemos mayor contribución de habitacciones, estrato y piso

  • Contribución PC3
fviz_contrib(datos_inm.pca, choice = "var", axes = 3, top = 10) # PC3

Determinado prinicpalmente por la zona.

  • Contribución PC4
fviz_contrib(datos_inm.pca, choice = "var", axes = 4, top = 10) # PC4

# Obtener las puntuaciones (scores) en la primera componente principal (PC1)
scores <- datos_inm.pca$x

# Seleccionar los índices de los valores más altos y más bajos en PC1 y PC2
casos_extremos <- c(
  which.max(scores[,1]),  # Índice del valor máximo en PC1
  which.min(scores[,1]),  # Índice del valor mínimo en PC1
  which.max(scores[,2]),  # Índice del valor máximo en PC2
  which.min(scores[,2])   # Índice del valor mínimo en PC2
)
casos1 <- rbind(datos_inm.pca$x[casos_extremos[1],1:2],datos_inm.pca$x[casos_extremos[2],1:2]) # CP1
rownames(casos1) = c(as.character(casos_extremos[1]), as.character(casos_extremos[2]))
casos1 <- as.data.frame(casos1)

casos2 <- rbind(datos_inm.pca$x[casos_extremos[3],1:2],datos_inm.pca$x[casos_extremos[4],1:2]) # CP2
rownames(casos2) = c(as.character(casos_extremos[3]), as.character(casos_extremos[4]))
casos2 <- as.data.frame(casos2)

fviz_pca_ind(datos_inm.pca, col.ind = "#DEDEDE", gradient.cols = c("#2E9FDF",  "#E7B800", "#FC4E07")) +
geom_point(data = casos1, aes(x = PC1, y = PC2), color = c("#2E9FDF",  "#E7B800"), size = 3) +
geom_point(data = casos2, aes(x = PC1, y = PC2), color = c("#FF7F00",  "#034D94"), size = 3)

inm_caso_extremo <- datos_inmobiliarios[casos_extremos, ]
inm_caso_extremo
# A tibble: 4 × 10
     id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1    69 Zona O… 2           3     165        80            0      0            0
2  5684 Zona S… 2           6    1800      1586           10      4            5
3   534 Zona N… 3           3     370      1440            1      4           10
4  5908 Zona S… 3           5     950       280           10      0            0
# ℹ 1 more variable: tipo <chr>

Conclusiones

Del análisis de componentes principales se concluye:

  • La primer componente principal explica el 42.9% de la varianza observada, relacionando las variables baños, parqueaderos, área construida y precio.

  • La segunda componente explica el 17.1% de la varianza, considerando la relación entre habitaciones, estrato y piso.

  • En la visualización 2D se nota que las variables zona y piso no parecen mostrar un patrón o tendencia signficativos.

  • Mediante la revisión de varianzas y diagrama de sedimentación se concluye que las primeras 4 componentes principales son suficientes para explicar la mayor cantidad de varianza (83,74%) y lograr una reducción de dimensionalidad significativa. En el caso más extremo podrían retenerse las primeras 3 componentes principales que explican el 72% de la varianza.

  • Desde el punto de vista de negocio, el enfoque en las variables explicadas por la primer componente principal serán las que mayor impacto tengan en el precio de los inmuebles, ya que el área, el número de baños y la cantidad de parqueaderos son los que más incidencia tienen en el precio.

  • De forma indirecta también se ha establecido que la cantidad de parqueaderos está muy relacionada con la ubicación del inmueble, es decir su estrato.

Punto 2 - Análisis de Conglomerados

En este análisis se requiere agrupar las propiedades en grupos homogéneos, con características similares para poder concluir qué elementos difieren la oferta de los inmuebles en diferentes zonas de la ciudad.

cluster_vars <- datos_inmobiliarios_num %>% dplyr::select(piso, preciom, areaconst, parqueaderos, banios, habitaciones, estrato, tipo, zona)
cluster_vars
# A tibble: 8,319 × 9
    piso preciom areaconst parqueaderos banios habitaciones estrato  tipo  zona
   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>   <dbl> <dbl> <dbl>
 1     5     250        70            1      3            6       3     1     4
 2     5     320       120            1      2            3       3     1     4
 3     5     350       220            2      2            4       3     1     4
 4     5     400       280            3      5            3       4     1     5
 5     1     260        90            1      2            3       5     2     2
 6     1     240        87            1      3            3       5     2     2
 7     1     220        52            2      2            3       4     2     2
 8     1     310       137            2      3            4       5     2     2
 9     5     320       150            2      4            6       5     1     2
10     5     780       380            2      3            3       5     1     2
# ℹ 8,309 more rows

Escalamiento de las variables

inm_escalado_cluster <- scale(cluster_vars)
inm_escalado_cluster <- as.data.frame(inm_escalado_cluster)

Determinar el numero de clusters

Para evaluar el núero de clusters se usarán varios métodos

Método del criterio del gráfico de silueta

set.seed(123)
fviz_nbclust(inm_escalado_cluster, kmeans, method = "silhouette") +
  labs(subtitle = "Silhouette method")

  • Según este gráfico, deberían considerarse solo 2 clusters para agrupar las características de la oferta inmobiliaria.

Método del codo

set.seed(123)
wss <- numeric()
for(h in 1:10){
  b<-kmeans(inm_escalado_cluster,h)
  wss[h]<-b$tot.withinss #scintra
}
wss 
 [1] 74862.00 54366.68 46299.19 42870.90 37931.99 34037.43 31875.83 30459.20
 [9] 28455.26 27349.08
wss1 <- data.frame(cluster=c(1:10),wss)
wss1
   cluster      wss
1        1 74862.00
2        2 54366.68
3        3 46299.19
4        4 42870.90
5        5 37931.99
6        6 34037.43
7        7 31875.83
8        8 30459.20
9        9 28455.26
10      10 27349.08
# Graficar el método del codo
plot(1:10, wss, type = "b", pch = 19, frame = FALSE,
     xlab = "Número de clusters K",
     ylab = "Suma de las distancias dentro de los clusters",
     main = "Método del Codo")

Se determina que el número de clusters es 3 porque es donde se encuentra el codo aproximadamente.

de <- dist(inm_escalado_cluster, method = "manhattan")
hc_emp <- hclust(de, method = "complete" )

cluster_assigments <- cutree(hc_emp, k = 3)
assigned_cluster <- inm_escalado_cluster %>% mutate(cluster = as.factor(cluster_assigments))
table(assigned_cluster$cluster)

   1    2    3 
2887 5426    6 
color3 = c("#2E9FDF",  "#E7B800","#FC4E07")
color4 = c("#2E9FDF",  "#E7B800", "#FC4E07","#CC79A7")
color5 = c("#E69F00","#2E9FDF",  "#E7B800", "#FC4E07","#CC79A7")
color6 = c("#E69F00","#2E9FDF",  "#E7B800", "#FC4E07","#CC79A7","#0072B2")
ggplot(assigned_cluster, aes(x = cluster)) +
  geom_bar(fill = color3) +  
  labs(title = "Distribución de viviendas en cada clúster",
       x = "Clúster",
       y = "Cantidad de viviendas") +
  theme_minimal() +
  geom_text(stat='count', aes(label=..count..), vjust=-0.5)
Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
ℹ Please use `after_stat(count)` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
generated.

colors <- brewer.pal(n = 10, name = "Set3") 

ggplot(assigned_cluster, aes(x = preciom, y = areaconst, color = factor(cluster))) +
  geom_point() +
  labs(title = "Relación entre precio y área construida por clúster",
       x = "Precio",
       y = "Área construida",
       color = "Clúster") +
  theme(legend.position = "bottom")

# Dibujar el dendrograma
plot(hc_emp, labels = FALSE, hang = -1, main = "Dendrograma del Clustering Jerárquico")
rect.hclust(hc_emp, k = 3, border = 2:7) # Agrega rectángulos alrededor de los clusters

set.seed(123)  # Fijar la semilla para la reproducibilidad
kmeans_result <- kmeans(inm_escalado_cluster, centers = 3, nstart = 25)
assigned_cluster2 <- inm_escalado_cluster %>%
  as.data.frame() %>%
  mutate(cluster = as.factor(kmeans_result$cluster))
table(assigned_cluster2$cluster)

   1    2    3 
2223 1571 4525 
ggplot(assigned_cluster2, aes(x = preciom, y = areaconst, color = cluster)) +
  geom_point(size = 1) +
  labs(title = "Clusters Identificados por K-means",
       x = "Área Construida",
       y = "Precio M2") +
  theme_minimal()

# Realizar el análisis de conglomerados con k-means
num_clusters <- 3
kmeans_result <- kmeans(datos_inm.pca$x[, 1:2], centers = num_clusters)

# Agregar la información de los clusters al conjunto de datos original
datos_inmobiliarios_num_scaled$cluster <- as.factor(kmeans_result$cluster)

# Graficar las puntuaciones de los componentes principales coloreadas por cluster
ggplot(datos_inmobiliarios_num_scaled, aes(x = datos_inm.pca$x[, 1], y = datos_inm.pca$x[, 2], color = cluster)) +
  geom_point() +
  labs(title = "Resultados del Análisis de Conglomerados")

Conclusiones

  • La cantidad de clusters que sugieren los métodos no son concluyentes, está entre 2 y 3, se escoge k=3, esto también sugiere que las viviendas no poseen tanta información que permita tener un mayor número de clusters.
  • Con el modelo K-means se generaron 3 grupos con características bien definidas
  • El apoyo de los gráficos permite tener una mejor intuición de los datos obtenidos en las métricas
  • Se pueden emplear estas agrupaciones para hacer un análisis estadístico más detallado y así poder indentificar que otras características comparten estas ofertas para poder segmentar aun más cada grupo.

Punto 3 - Analisis de Correspondencia

Este análisis aplica solo a variables categóricas, en nuestro dataset, nos interesa conocer la relación entre zona, estrato y tipo.

datos_inm_categoricos <- datos_inmobiliarios %>% dplyr::select(zona, estrato, tipo)
tablaZonaEstrato <- table(datos_inm_categoricos$zona, datos_inm_categoricos$estrato)
tablaZonaEstrato
              
                  3    4    5    6
  Zona Centro   105   14    4    1
  Zona Norte    572  407  769  172
  Zona Oeste     54   84  290  770
  Zona Oriente  340    8    2    1
  Zona Sur      382 1616 1685 1043

Chi-cuadrado para Zona vs Estrato

chisq.test(tablaZonaEstrato)

    Pearson's Chi-squared test

data:  tablaZonaEstrato
X-squared = 3830.4, df = 12, p-value < 2.2e-16

El resultado indica que se rechaza la hipótesis de independencia (hipótesis nula) de las variables Zona~estrato (p-value: 0.0000), indicando grado tipo de relación entre ellas.

Análisis de correspondencia

resultados_ac <- CA(tablaZonaEstrato)

De este gráfico podemos concluir lo siguiente: - Las viviendas del Oeste son estrato 6 - Las viviendas de la zona norte y sur son principalmente del estrato 4 y 5 - Las viviendas del oriente y centro son en su mayoría estrato 3

valores_prop <-resultados_ac$eig
valores_prop
      eigenvalue percentage of variance cumulative percentage of variance
dim 1 0.32215213              69.965515                          69.96551
dim 2 0.12745096              27.680002                          97.64552
dim 3 0.01084108               2.354483                         100.00000
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
ylab("Porcentaje de varianza explicado") + xlab("Ejes")

Este gráfico nos muestra el porcentaje de varianza de cada componente,

  • el primer componente explica el 70% del total de la varianza y la segunda el 27.7%.
  • las dos primeras componentes explican el 97.7% de la varianza de los datos.

Análisis adicional

Con el fin de obtener un mayor grado de detalle en cuanto a la relación de zona y estrato se procede a evaluar el efecto del tipo de inmueble, para esto se va a crear una columna que relacione el tipo y la zona y se compararán contra el estrato.

datos_inmobiliarios_categoricas <- datos_inm_categoricos %>%
  mutate(zona_tipo = paste(zona, tipo, sep = "-"))
datos_inmobiliarios_categoricas
# A tibble: 8,319 × 4
   zona         estrato tipo        zona_tipo             
   <chr>          <dbl> <chr>       <chr>                 
 1 Zona Oriente       3 Casa        Zona Oriente-Casa     
 2 Zona Oriente       3 Casa        Zona Oriente-Casa     
 3 Zona Oriente       3 Casa        Zona Oriente-Casa     
 4 Zona Sur           4 Casa        Zona Sur-Casa         
 5 Zona Norte         5 Apartamento Zona Norte-Apartamento
 6 Zona Norte         5 Apartamento Zona Norte-Apartamento
 7 Zona Norte         4 Apartamento Zona Norte-Apartamento
 8 Zona Norte         5 Apartamento Zona Norte-Apartamento
 9 Zona Norte         5 Casa        Zona Norte-Casa       
10 Zona Norte         5 Casa        Zona Norte-Casa       
# ℹ 8,309 more rows
tablaZonaEstrato2 <- table(datos_inmobiliarios_categoricas$zona_tipo, datos_inmobiliarios_categoricas$estrato)
tablaZonaEstrato2
                          
                              3    4    5    6
  Zona Centro-Apartamento    14    7    3    0
  Zona Centro-Casa           91    7    1    1
  Zona Norte-Apartamento    337  246  498  117
  Zona Norte-Casa           235  161  271   55
  Zona Oeste-Apartamento     29   58  231  711
  Zona Oeste-Casa            25   26   59   59
  Zona Oriente-Apartamento   58    2    1    1
  Zona Oriente-Casa         282    6    1    0
  Zona Sur-Apartamento      201 1091 1033  462
  Zona Sur-Casa             181  525  652  581

Análisis de correspondencia

resultados_ac <- CA(tablaZonaEstrato2)

En este caso encontramos un poco más de información con respecto a la distribución de casas y apartamentos por zona y estrato.

  • el estrato 6 corresponde en su mayoría a los apartamentos y las casas del oeste y con una pequeña presencia de casas en el sur.
  • Vemos como apartamentos del sur y el norte están predominantemente en estratos 4 y 5
  • los apartamentos de la zona norte y la casas del norte están el estrato 4 y 5
  • la zona oriente tanto para casas como para apartamentos corresponden al estrato 3
  • los apartamentos de la zona centro no son principalmente del estrato 3 también se aprecia tendencia a 4 y 5.
summary(resultados_ac)

Call:
CA(X = tablaZonaEstrato2) 

The chi square of independence between the two variables is equal to 4109.091 (p-value =  0 ).

Eigenvalues
                       Dim.1   Dim.2   Dim.3
Variance               0.329   0.153   0.012
% of var.             66.652  30.903   2.444
Cumulative % of var.  66.652  97.556 100.000

Rows
                           Iner*1000     Dim.1     ctr    cos2     Dim.2
Zona Centro-Apartamento  |     3.831 |   1.112   1.083   0.931 |  -0.009
Zona Centro-Casa         |    45.211 |   1.854  12.545   0.914 |   0.526
Zona Norte-Apartamento   |    25.991 |   0.351   5.395   0.683 |  -0.130
Zona Norte-Casa          |    21.813 |   0.468   5.769   0.871 |  -0.129
Zona Oeste-Apartamento   |   144.507 |  -0.672  16.970   0.387 |   0.847
Zona Oeste-Casa          |     1.965 |  -0.142   0.125   0.209 |   0.246
Zona Oriente-Apartamento |    29.934 |   1.908   8.240   0.906 |   0.589
Zona Oriente-Casa        |   154.700 |   2.012  42.725   0.909 |   0.612
Zona Sur-Apartamento     |    53.336 |  -0.181   3.347   0.207 |  -0.345
Zona Sur-Casa            |    12.653 |  -0.232   3.802   0.989 |   0.017
                             ctr    cos2     Dim.3     ctr    cos2  
Zona Centro-Apartamento    0.000   0.000 |   0.303   2.198   0.069 |
Zona Centro-Casa           2.177   0.074 |   0.221   4.860   0.013 |
Zona Norte-Apartamento     1.587   0.093 |  -0.201  48.091   0.223 |
Zona Norte-Casa            0.951   0.067 |  -0.126  11.336   0.063 |
Zona Oeste-Apartamento    58.066   0.613 |  -0.006   0.040   0.000 |
Zona Oeste-Casa            0.807   0.627 |  -0.126   2.676   0.164 |
Zona Oriente-Apartamento   1.696   0.086 |   0.170   1.791   0.007 |
Zona Oriente-Casa          8.515   0.084 |   0.173   8.648   0.007 |
Zona Sur-Apartamento      26.159   0.749 |   0.084  19.779   0.045 |
Zona Sur-Casa              0.043   0.005 |   0.017   0.580   0.006 |

Columns
                           Iner*1000     Dim.1     ctr    cos2     Dim.2
3                        |   257.273 |   1.186  74.686   0.956 |   0.255
4                        |    56.732 |  -0.134   1.398   0.081 |  -0.429
5                        |    27.145 |  -0.121   1.476   0.179 |  -0.217
6                        |   152.790 |  -0.556  22.440   0.484 |   0.574
                             ctr    cos2     Dim.3     ctr    cos2  
3                          7.429   0.044 |   0.017   0.419   0.000 |
4                         30.813   0.829 |   0.141  42.197   0.090 |
5                         10.232   0.575 |  -0.142  55.236   0.246 |
6                         51.527   0.515 |   0.033   2.148   0.002 |

Conclusiones

  • Al realizar el analisis de correspondencia simple se encuentra una relación clara entre el estrato y la zona, sin embargo, al detallar por tipo se encuentra una mayor clasificación para cada uno de los estratos.
  • En el caso de la oferta inmobiliaria y dependiendo del tipo de cliente podemos estimar que aquellos que deseen los mejores servicios, relacionados con el estrato 6 por ejemplo tienen la opción de los apartamentos y casas del oeste y las casas del sur de la ciudad.
  • Para un público buscando una vivienda más asequible las zonas de la ciudad serían oriente y centro, aunque con mayor participación de casas en esta última, ya que los apartamentos pueden estar ubicados en zonas específicas con estratos 4 y 5.
  • Con el análisis adicional podemos inferir que al incluir más información en el análisis podemos extraer muchos más datos que nos permitan concluir estas relaciones entre las variables categóricas y podamos así encontrar relaciones de características de las viviendas para hacerlas más interesantes para los clientes.

Estrategias y recomendaciones como:

  • Resaltar las características de los apartamentos que los hacen convenientes:

    • externas como: ubicación, cercanía a servicios, clínicas, centros comerciales, colegios y transporte púbilico
    • internas como amenidades entre las cuales se tienen piscinas, gimnasio, áreas sociales además de la seguridad y estacionamiento.
  • Enfocar la oferta como inversión, dada la cantidad de proyectos inmobiliarios, especialmente al sur de la ciudad es un mercado muy atractivo para inversionistas, dada la rentabilidad que pueden lograr en dichas zonas.

  • Utilización de medios digitales como marketing segmentado usando plataformas como Google Ads, redes sociales, portales online, creación de Tours virtuales como apoyo al proceso de venta.

  • En cuanto a las casas, se sabe que es alrededor de un 30% de la oferta, y concentrándo el esfuerzo en las zonas Norte, Sur y Oeste tenemos las siguientes estrategias.

    • Enfocar el marketing en estilo de vida, enfatizando la calidad de vida que ofrece vivir en una casa, espacio libre, privacidad, libertad para personalizarla o ampliarla orientado a familias y en menor medida personas que buscan mayor espacio y privacidad en sus hogares.

    • A diferencia de la venta de apartamentos, en las casas lo más importante es ofrecer la experiencia al cliente, por lo tanto se recomienda eventos de puertas abiertas, que permita a los potenciales compradores visitar las casas y experimentarlas de primera mano.

    • la oferta de vivienda está principalmente concentrada en Apartamentos, en su mayoría de 3 habitaciones y al menos un parqueadero, en estratos 4, 5 y 6.

  • Es importante segmentar bien este mercado, partiendo de las siguientes ideas:

  • Apartamentos de 3 habitaciones en estrato 4 y 5 con 1 parqueadero

    • Orientado a familias pequeñas o parejas que buscan un apartamento espacioso y bien ubicado.
    • Inversionistas que buscan darle una orientación comercial, bien sea para alquier de corto plazo, a través de herramientas como Airbnb, especialmente en las zonas gastronómicas o culturales de la ciudad (zona oeste)
  • Apartamentos de 3 o 4 habitaciones con 2 parqueaderos en estrato 6

    • Orientado a familias más grandes o parejas que buscan un estilo de vida lujoso y exclusivo