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 preferencias del mercado.

Solución

Inicialmente cargamos la base de datos “vivienda” y visualizamos el contenido de manera resumida.

ruta <- "C:/Users/jmont/OneDrive/Escritorio/3. ESTUDIOS/Metodos Estadisticos para la toma de decisiones/vivienda.csv"

datos_org <- read.csv(ruta, fileEncoding = "UTF-8")

str(datos_org)
## 'data.frame':    8322 obs. of  13 variables:
##  $ id       : int  8312 8311 8307 8296 8297 8298 8299 8300 8286 8287 ...
##  $ zona     : chr  "Zona Oeste" "Zona Oeste" "Zona Oeste" "Zona Sur" ...
##  $ piso     : int  4 1 NA 2 NA NA 2 NA NA 2 ...
##  $ estrato  : int  6 6 5 3 5 5 6 5 5 5 ...
##  $ preciom  : int  1300 480 1200 220 330 1350 305 480 275 285 ...
##  $ areaconst: num  318 300 800 150 112 390 125 280 74 120 ...
##  $ parquea  : int  2 1 4 1 2 8 2 4 1 2 ...
##  $ banios   : int  4 4 7 2 4 10 3 4 2 4 ...
##  $ habitac  : int  2 4 5 4 3 10 3 4 3 3 ...
##  $ tipo     : chr  "Apartamento" "Casa" "Casa" "Casa" ...
##  $ barrio   : chr  "arboleda" "normandía" "miraflores" "el guabal" ...
##  $ longitud : num  -76576 -76571 -76568 -76565 -76565 ...
##  $ latitud  : num  3454 3454 3455 3417 3408 ...

Se evidencia que pueden existir valores atipicos, como nulos o campos vacios que pueden generar problemas en el analisis por lo cual se identifican de la siguiente manera:

valores_nulos <- sapply(datos_org, function(x) sum(is.na(x)))

print(valores_nulos)
##        id      zona      piso   estrato   preciom areaconst   parquea    banios 
##         3         3      2638         3         2         3      1605         3 
##   habitac      tipo    barrio  longitud   latitud 
##         3         3         3         3         3

Existe omisión de información en todas las variables, sin embargo, de manera significativa en “piso” y “parqueadero”.

Puesto que existen variables de tipo categórico, las cuales no pueden ser utilizadas en el análisis de componentes principales, por ejemplo, zona, estrato, tipo y barrio, por lo cual se descartaran con el e objetivo de trabajar aquellas de tipo numérico continuo y aquellas que no presenten datos nulos.

Debido a que no es claro que implicaciones tiene incluir las variables latitud, longitud, piso y parqueadero, ya hacen referencia a una ubicación espacial y tienen problemas de consistencia, se incluirán, ya que se trata de un análisis no supervisado, pero se conformaran 4 grupos con el objetivo de comparar las diferencias y como afecta incluir diferentes variables.

En el grupo 1: Se excluye

“id”,“zona”,“estrato”,“tipo”,“barrio”

En el grupo 2: Se excluye

“id”,“zona”,“estrato”,“tipo”,“barrio”,“latitud”,“longitud”

En el grupo 3: Se excluye

“id”,“zona”,“estrato”,“tipo”,“barrio”,“latitud”,“longitud”,“preciom”

En el grupo 4: Se excluye

“id”,“zona”,“estrato”,“tipo”,“barrio”,“latitud”,“longitud”,“preciom”,“piso”

datos_filtrados1 <- datos_org[complete.cases(datos_org), ]

datos_filtrados1 <- datos_filtrados1[, !(names(datos_filtrados1) %in% c("id","zona","estrato","tipo","barrio"))]

datos_filtrados2 <- datos_org[complete.cases(datos_org), ]

datos_filtrados2 <- datos_filtrados2[, !(names(datos_filtrados2) %in% c("id","zona","estrato","tipo","barrio","latitud","longitud"))]

datos_filtrados3 <- datos_org[complete.cases(datos_org), ]

datos_filtrados3 <- datos_filtrados3[, !(names(datos_filtrados3) %in% c("id","zona","estrato","tipo","barrio","latitud","longitud","preciom"))]

datos_filtrados4 <- datos_org[complete.cases(datos_org), ]

datos_filtrados4 <- datos_filtrados4[, !(names(datos_filtrados4) %in% c("id","zona","estrato","tipo","barrio","latitud","longitud","preciom","piso"))]

Es importante tener en cuenta que la media y varianza de cada variable, dista de manera significativa una de otra luego es necesario estandarizar la información con el objetivo de que algunas variables en específico no dominen la mayoría de los componentes por dicha condición.

A continuación, se aplica el Código que nos permite obtener los componentes principales de los cuales extraemos la “Rotación”, la cual hace referencia a el valor de los loadings 𝜙 para cada componente (eigenvector).

cat("\n","CASO 1", "\n", "\n")
## 
##  CASO 1 
## 
pca1 <- prcomp(datos_filtrados1, scale = TRUE)

pca1$rotation
##                   PC1          PC2         PC3          PC4         PC5
## piso      -0.12419429  0.140030507  0.83265378  0.498751850 -0.02622018
## preciom    0.45612502  0.023479760  0.31182058 -0.270749207  0.35923399
## areaconst  0.47594221 -0.061642530 -0.03761373  0.009885332  0.48462313
## parquea    0.43038334 -0.002030926  0.23093194 -0.386247494 -0.74604671
## banios     0.47336703 -0.079030917  0.05244521  0.199989342  0.04673895
## habitac    0.35238408 -0.143580250 -0.34778227  0.696851175 -0.27506320
## longitud  -0.08419934 -0.690401263  0.12451401 -0.047655559 -0.01128925
## latitud    0.09713146  0.687401741 -0.12451707  0.028076038 -0.02909333
##                    PC6         PC7          PC8
## piso       0.136988029 -0.05847574  0.005373496
## preciom   -0.160129955  0.67005452 -0.131140106
## areaconst  0.605929735 -0.40065364  0.075052848
## parquea    0.208907244 -0.09627282  0.052468864
## banios    -0.721333925 -0.44611873  0.073501341
## habitac    0.155221025  0.37993452 -0.067472472
## longitud   0.019514556 -0.12103938 -0.695214341
## latitud    0.001606765 -0.14068771 -0.693620592

Del caso 1 podemos evideciar que:

El primer componente se recoge información principalmente para aquellas variables distintas de latitud, longitud y piso.

El segundo componente recoge información principalmente de las variables latitud y longitud.

El tercer componente recoge información principalmente de la variable piso.

cat("\n","CASO 2", "\n", "\n")
## 
##  CASO 2 
## 
pca2 <- prcomp(datos_filtrados2, scale = TRUE)

pca2$rotation
##                  PC1         PC2          PC3         PC4        PC5
## piso      -0.1303842  0.83216185  0.517488231 -0.02596413  0.1358974
## preciom    0.4559601  0.32247810 -0.264482021  0.35698936 -0.1622554
## areaconst  0.4798166 -0.04280053  0.009048408  0.48839255  0.6010120
## parquea    0.4315969  0.23714602 -0.378193665 -0.74696838  0.2128396
## banios     0.4785793  0.03420384  0.205573515  0.04099560 -0.7232466
## habitac    0.3599478 -0.37983485  0.690566723 -0.27150467  0.1600102
##                   PC6
## piso      -0.05975193
## preciom    0.68146114
## areaconst -0.41002386
## parquea   -0.10579715
## banios    -0.45029456
## habitac    0.38725009

Del caso 2 podemos evideciar que:

El primer componente se recoge información principalmente para las variables de area construida y baños.

El segundo componente recoge información principalmente de las variable piso.

El tercer componente recoge información principalmente de la variable habitaciones.

cat("\n","CASO 3", "\n", "\n")
## 
##  CASO 3 
## 
pca3 <- prcomp(datos_filtrados3, scale = TRUE)


pca3$rotation
##                  PC1         PC2         PC3        PC4         PC5
## piso      -0.1904344  0.93126788  0.28712908 -0.0787083 -0.08852557
## areaconst  0.5275034  0.05200875 -0.06855877 -0.8430171 -0.06047379
## parquea    0.4519911  0.27881703 -0.62946144  0.3813402 -0.41990835
## banios     0.5267006  0.17326595  0.10299285  0.2760560  0.77830006
## habitac    0.4514038 -0.14924878  0.71135599  0.2479917 -0.45434803

Del caso 3 podemos evidenciar que:

El primer componente se recoge información principalmente para las variables de área construida y baños.

El segundo componente recoge información principalmente de las variables piso con una diferencia considerable.

El tercer componente recoge información principalmente de la variable habitaciones.

cat("\n","CASO 4", "\n", "\n")
## 
##  CASO 4 
## 
pca4 <- prcomp(datos_filtrados4, scale = TRUE)


pca4$rotation
##                  PC1         PC2        PC3         PC4
## areaconst -0.5343302 -0.05990300  0.8402398 -0.06999985
## parquea   -0.4655553 -0.67883567 -0.3045267  0.47927434
## banios    -0.5409825  0.03127607 -0.4032278 -0.73740563
## habitac   -0.4528658  0.73117411 -0.1966417  0.47077496

Del caso 4 podemos evidenciar que:

El primer componente se recoge información principalmente de las variables área construida y baños.

El segundo componente recoge información principalmente de la variable habitaciones.

El tercer componente recoge información principalmente de la variable área construida.

A continuación, se realiza un análisis gráfico de las primeras dos componentes con el fin de interpretar de mejor manera los resultados.

cat("\n","CASO 1", "\n", "\n")
## 
##  CASO 1 
## 
biplot(x = pca1, scale = 0, cex = 0.8, col = c("blue4", "brown3"))

Del caso 1 podemos evidenciar que:

Los vectores de latitud y longitud tienen una longitud muy alta frente a las demás variables, lo que representa una variabilidad alta.

Los vectores de latitud y longitud no tienen una correlación clara con alguna variable con la que se pueda definir un agrupamiento claro.

Existen tres grupos de puntos que evidencian diferencias entre ellos, estas diferencias se atribuyen al efecto de las variables analizadas en conjunto.

cat("\n","CASO 2", "\n", "\n")
## 
##  CASO 2 
## 
biplot(x = pca2, scale = 0, cex = 0.8, col = c("blue4", "brown3"))

Del caso 2 podemos evidenciar que:

El vector de piso tiene una longitud considerable frente a las demás variables, lo que representa una variabilidad alta.

Los vectores de parqueadero y precio de metro tienen una correlación clara, al igual que los vectores baños y área construida

No existen grupos claros entre los datos, lo que quiere decir la latitud y longitud estaban generando que se encontraran diferencias entre los datos, sin embargo, puede deberse a su alta variabilidad y poca correlación con otras variables.

cat("\n","CASO 3", "\n", "\n")
## 
##  CASO 3 
## 
biplot(x = pca3, scale = 0, cex = 0.8, col = c("blue4", "brown3"))

Del caso 3 podemos evidenciar que:

El vector de piso tiene mantiene una longitud considerable frente a las demás variables, lo que representa una variabilidad alta.

Los vectores de parqueadero, baño y área construida tienen una correlación clara, aunque también cercana a la variable habitaciones.

Se evidencia la presencia de dos grupos uno de ellos relacionado con las variables parqueadero, baño, área construida y habitaciones, y otro relacionado a otras variables como piso.

cat("\n","CASO 4", "\n", "\n")
## 
##  CASO 4 
## 
biplot(x = pca4, scale = 0, cex = 0.8, col = c("blue4", "brown3"))

Del caso 4 podemos evidenciar que:

Los vectores de las variables parqueadero y habitaciones mantienen una longitud considerable frente a las demás variables, lo que representa una variabilidad alta.

Los vectores de baño y área construida tienen una correlación significativa.

Se evidencia la presencia de tres grupos uno de ellos relacionado con las variables baño y área construida, otro para habitaciones, y otro relacionado con parqueadero.

cat("\n","CASO 1", "\n", "\n")
## 
##  CASO 1 
## 
prop_varianza1 <- pca1$sdev^2/sum(pca1$sdev^2)
prop_varianza1
## [1] 0.42350746 0.22983660 0.13049934 0.09109221 0.04324466 0.03807277 0.02336174
## [8] 0.02038521
x_label <- enc2utf8("Componente principal")
y_label <- enc2utf8("Proporcion de varianza explicada")

data <- data.frame(prop_varianza1, pc = 1:8)

ggplot(data, aes(x = pc, y = prop_varianza1)) +
  geom_col(width = 0.3) +
  scale_y_continuous(limits = c(0, 1)) +
  theme_bw() +
  labs(x = x_label, y = y_label)

Para el caso 1 la varianza explicada por el componente 1 y 2 llega a representar el 65 %.

cat("\n","CASO 2", "\n", "\n")
## 
##  CASO 2 
## 
prop_varianza2 <- pca2$sdev^2/sum(pca2$sdev^2)
prop_varianza2
## [1] 0.56033324 0.17819573 0.12193581 0.05767291 0.05079814 0.03106417
data <- data.frame(prop_varianza2, pc = 1:6)

ggplot(data, aes(x = pc, y = prop_varianza2)) +
  geom_col(width = 0.3) +
  scale_y_continuous(limits = c(0, 1)) +
  theme_bw() +
  labs(x = x_label, y = y_label)

Para el caso 2 la varianza explicada por el componente 1 y 2 llega a representar el 74 %.

cat("\n","CASO 3", "\n", "\n")
## 
##  CASO 3 
## 
prop_varianza3 <- pca3$sdev^2/sum(pca3$sdev^2)
prop_varianza3
## [1] 0.55049329 0.19391328 0.13356773 0.06396025 0.05806545
data <- data.frame(prop_varianza3, pc = 1:5)

ggplot(data, aes(x = pc, y = prop_varianza3)) +
  geom_col(width = 0.3) +
  scale_y_continuous(limits = c(0, 1)) +
  theme_bw() +
  labs(x = x_label, y = y_label)

Para el caso 3 la varianza explicada por el componente 1 y 2 llega a representar el 74 %.

Lo anterior nos indica que la variable precio m, la cual fue omitida para el caso 3, no representa un aporte significativo a los componentes 1 y 2.

cat("\n","CASO 4", "\n", "\n")
## 
##  CASO 4 
## 
prop_varianza4 <- pca4$sdev^2/sum(pca4$sdev^2)
prop_varianza4
## [1] 0.67168539 0.17354286 0.08108199 0.07368976
data <- data.frame(prop_varianza4, pc = 1:4)

ggplot(data, aes(x = pc, y = prop_varianza4)) +
  geom_col(width = 0.3) +
  scale_y_continuous(limits = c(0, 1)) +
  theme_bw() +
  labs(x = x_label, y = y_label)

Para el caso 4 la varianza explicada por el componente 1 y 2 llega a representar el 84 %.

Se recomienda seguir capturando información de latitud, longitud sin embargo esto requiere un análisis especial. Se recomienda realizar depuración y control sobre la variable piso, ya que no siempre se registra de manera completa, tampoco es claro que se esta registrando, si el numero de pisos que tiene una casa, o la planta de ubicación de la unidad inmobiliaria. Como solución se puede incluir en dos variables “número de pisos” y “planta de ubicación” Por otro lado, las variables parqueadero, número de habitaciones, baños y área construida, tienen mucha relevancia pero debe controlarse la consistencia del número de habitaciones ya que presenta muchas inconsistencias.

En cuanto a análisis de componentes principales, existe una importancia y correlación fuerte entre las variables la cual organizar en orden de relevancia así:
1. Área construida y baños 2. Número de habitaciones 3. Parqueadero 4. Pisos 5. Latitud y Longitud

2. Análisis de Conglomerados:

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

Solución

Debido a que contamos con variables numéricas y categóricas en el conjunto de datos inicial, iniciaremos realizando un análisis de conglomerados para las variables categóricas.

Iniciamos filtrando los datos vacíos y quitando las variables categóricas.

datos_filtrados5 <- datos_org[complete.cases(datos_org), ]
datos_filtrados5 <- datos_filtrados5[, !(names(datos_filtrados5) %in% c("id","areaconst","parquea","banios","habitac","longitud","latitud","preciom","piso"))]

A continuación, se realizan el análisis de conglomerados, seleccionando como número de clúster “4” ya que se obtiene le mejor resultado luego de aplicar las validaciones de Coeficiente de Silhouette.

# distancia euclidiana
dist_df5z <- dist(datos_filtrados5, method = 'euclidean')
## Warning in dist(datos_filtrados5, method = "euclidean"): NAs introducidos por
## coerción
# Cluster jerarquico con el método complete
hc_df5z <- hclust(dist_df5z, method = 'complete')

# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_df5z, k = 4)

# asignamos los clusters
assigned_cluster <- datos_filtrados5 %>% mutate(cluster = as.factor(cluster_assigments))

plot(hc_df5z, cex = 0.6, main = "Dendograma de Empresas", las = 1,
     ylab = "Distancia euclidiana", xlab = "Grupos", hang = -1)

# Agregar las etiquetas del eje x
etiquetas_eje_x <- datos_filtrados5$zona
axis(side = 1, at = 1:length(etiquetas_eje_x), labels = etiquetas_eje_x, las = 2,cex.axis = 0.7)

# Dibujar los rectángulos de agrupación
rect.hclust(hc_df5z, k = 4, border = 2:5)

# Calcular el coeficiente de Silhouette
sil <- silhouette(cluster_assigments, dist(datos_filtrados5))
## Warning in dist(datos_filtrados5): NAs introducidos por coerción
sil_avg <- mean(sil[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette: ", sil_avg)
## Coeficiente de Silhouette:  1

Con el objetivo de guardar la información de los grupos se procede a crear un resumen que permita ver el cluster asociado a cada registro que comprende información de “Zona, estrato, tipo y barrio”.

# Crear un data frame con la información de clusters, zonas y estratos
cluster_summary <- assigned_cluster %>%
  group_by(cluster, zona, estrato, tipo, barrio) %>%
  summarize(num_registros = n()) %>%
  arrange(cluster)
## `summarise()` has grouped output by 'cluster', 'zona', 'estrato', 'tipo'. You
## can override using the `.groups` argument.
  # Imprimir el resumen
  print(cluster_summary)
## # A tibble: 688 × 6
## # Groups:   cluster, zona, estrato, tipo [50]
##    cluster zona       estrato tipo        barrio         num_registros
##    <fct>   <chr>        <int> <chr>       <chr>                  <int>
##  1 1       Zona Norte       6 APARTAMENTO menga                      1
##  2 1       Zona Norte       6 Apartamento acopi                      4
##  3 1       Zona Norte       6 Apartamento altos de menga             1
##  4 1       Zona Norte       6 Apartamento chipichape                 5
##  5 1       Zona Norte       6 Apartamento ciudad jardín              1
##  6 1       Zona Norte       6 Apartamento el bosque                  1
##  7 1       Zona Norte       6 Apartamento juanamb√∫                 14
##  8 1       Zona Norte       6 Apartamento la flora                   3
##  9 1       Zona Norte       6 Apartamento menga                      4
## 10 1       Zona Norte       6 Apartamento santa monica              18
## # ℹ 678 more rows

Una vez se conoce el cluster al que pertenece cada registro, se lleva esta información a la tabla de datos original que contiene todas las variables, esto se realiza por correspondencia de los atributos categóricos en la tabla de resumen con la tabla de datos origen.

# Agregar la columna de clusters al conjunto de datos originales
datos_clasif <- datos_org[complete.cases(datos_org), ]
#datos_clasif <- subset(datos_clasif, select = -cluster_AC)
datos_clasif$cluster_AC <- NA # Crear una columna vacía

# Recorrer cada fila en los datos originales
for (i in 1:nrow(datos_clasif)) {
  # ... (otro código)
  # Obtener los valores de zona, estrato, tipo y barrio para la fila actual
    zona_actual <- datos_clasif$zona[i]
    estrato_actual <- datos_clasif$estrato[i]
    tipo_actual <- datos_clasif$tipo[i]
    barrio_actual <- datos_clasif$barrio[i]
  # Filtrar el resumen de clústeres para encontrar el clúster correspondiente
    cluster_actual <- cluster_summary %>%
    filter(zona == zona_actual, estrato == estrato_actual, tipo == tipo_actual, barrio == barrio_actual) %>%
    pull(cluster)
  
  # Verificar si cluster_actual no está vacío antes de asignar
  if (length(cluster_actual) > 0) {
    # Asignar el valor del clúster a la columna correspondiente en los datos originales
    datos_clasif$cluster_AC[i] <- cluster_actual
  }
}

summary(datos_clasif)
##        id           zona                piso           estrato     
##  Min.   :   1   Length:4808        Min.   : 1.000   Min.   :3.000  
##  1st Qu.:2479   Class :character   1st Qu.: 2.000   1st Qu.:4.000  
##  Median :4474   Mode  :character   Median : 3.000   Median :5.000  
##  Mean   :4427                      Mean   : 3.886   Mean   :4.838  
##  3rd Qu.:6413                      3rd Qu.: 5.000   3rd Qu.:6.000  
##  Max.   :8316                      Max.   :12.000   Max.   :6.000  
##     preciom         areaconst         parquea           banios      
##  Min.   :  58.0   Min.   :  40.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 244.5   1st Qu.:  85.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 350.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 457.2   Mean   : 174.8   Mean   : 1.815   Mean   : 3.219  
##  3rd Qu.: 560.0   3rd Qu.: 225.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1500.0   Max.   :10.000   Max.   :10.000  
##     habitac           tipo              barrio             longitud        
##  Min.   : 0.000   Length:4808        Length:4808        Min.   :-76576.00  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:   -76.55  
##  Median : 3.000   Mode  :character   Mode  :character   Median :   -76.54  
##  Mean   : 3.564                                         Mean   :-16216.63  
##  3rd Qu.: 4.000                                         3rd Qu.:   -76.52  
##  Max.   :10.000                                         Max.   :   -76.46  
##     latitud           cluster_AC  
##  Min.   :   3.333   Min.   :1.00  
##  1st Qu.:   3.383   1st Qu.:1.00  
##  Median :   3.433   Median :3.00  
##  Mean   : 725.008   Mean   :2.62  
##  3rd Qu.:   3.487   3rd Qu.:4.00  
##  Max.   :3493.000   Max.   :4.00

Una vez clasificada la base de datos original, podemos extraer subconjuntos de datos basados en los cluster a los que pertenecen para analizar las variables de tipo numérico pero garantizando homogeneidad en cuanto a la zona y estrato a los que estos pertenecen.

subset_list <- split(datos_clasif, datos_clasif$cluster_AC)

Se obtiene entonces 4 subconjuntos con características homogéneas, los cuales se analizan a continuación, dejando únicamente aquellas variables de tipo numérico y descartando, latitud, longitud y piso, debido a los comportamientos a la alta variabilidad que presentan.

Subconjunto 1: Se identifican 2 grupos, con un coeficiente de Silhouette medio.

subset_1 <- subset_list$'1' [, !(names(subset_list$'1') %in% c("id","zona","estrato","tipo","barrio","latitud","longitud","piso"))]

subset_1 =scale(subset_1)
subset_1z = as.data.frame(subset_1)
summary(subset_1z)
##     preciom          areaconst          parquea            banios        
##  Min.   :-1.6143   Min.   :-1.1718   Min.   :-1.2933   Min.   :-3.16156  
##  1st Qu.:-0.7614   1st Qu.:-0.6514   1st Qu.:-0.4837   1st Qu.:-0.86021  
##  Median :-0.2550   Median :-0.3073   Median :-0.4837   Median :-0.09309  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000  
##  3rd Qu.: 0.5180   3rd Qu.: 0.3624   3rd Qu.: 0.3258   3rd Qu.: 0.67403  
##  Max.   : 3.1035   Max.   : 7.7354   Max.   : 5.9927   Max.   : 3.74249  
##                                                                          
##     habitac          cluster_AC  
##  Min.   :-3.5507   Min.   : NA   
##  1st Qu.:-0.5668   1st Qu.: NA   
##  Median :-0.5668   Median : NA   
##  Mean   : 0.0000   Mean   :NaN   
##  3rd Qu.: 0.4278   3rd Qu.: NA   
##  Max.   : 6.3954   Max.   : NA   
##                    NA's   :1302
# distancia euclidiana
dist_s1z <- dist(subset_1z, method = 'euclidean')

# Cluster jerarquico con el método complete
hc_s1z <- hclust(dist_s1z, method = 'complete')

# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_s1z, k = 2)

# asignamos los clusters
assigned_cluster <- subset_1z %>% mutate(cluster = as.factor(cluster_assigments))

plot(hc_s1z, cex = 0.4, main = "Dendograma", las = 1,
     ylab = "Distancia euclidiana", xlab = "Grupos", hang = -1)

rect.hclust(hc_s1z, k = 2, border = 2:5)

# Calcular el coeficiente de Silhouette
sil <- silhouette(cluster_assigments, dist(subset_1z))
sil_avg <- mean(sil[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette: ", sil_avg)
## Coeficiente de Silhouette:  0.4219258

Subconjunto 2: Se identifican 3 grupos, con un coeficiente de Silhouette alto.

subset_2 <- subset_list$'2' [, !(names(subset_list$'2') %in% c("id","zona","estrato","tipo","barrio","latitud","longitud","piso"))]

subset_2 =scale(subset_2)
subset_2z = as.data.frame(subset_2)
summary(subset_2z)
##     preciom          areaconst          parquea            banios       
##  Min.   :-1.2715   Min.   :-0.8398   Min.   :-0.3197   Min.   :-1.1542  
##  1st Qu.:-0.7015   1st Qu.:-0.6762   1st Qu.:-0.3197   1st Qu.:-0.3666  
##  Median :-0.3057   Median :-0.4357   Median :-0.3197   Median :-0.3666  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.4859   3rd Qu.: 0.3846   3rd Qu.:-0.3197   3rd Qu.: 0.4210  
##  Max.   : 8.9561   Max.   :10.4745   Max.   :11.9223   Max.   : 5.1468  
##                                                                         
##     habitac          cluster_AC 
##  Min.   :-2.0275   Min.   : NA  
##  1st Qu.:-0.5626   1st Qu.: NA  
##  Median :-0.5626   Median : NA  
##  Mean   : 0.0000   Mean   :NaN  
##  3rd Qu.: 0.4140   3rd Qu.: NA  
##  Max.   : 2.8556   Max.   : NA  
##                    NA's   :434
# distancia euclidiana
dist_s2z <- dist(subset_2z, method = 'euclidean')

# Cluster jerarquico con el método complete
hc_s2z <- hclust(dist_s2z, method = 'complete')

# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_s2z, k = 3)

# asignamos los clusters
assigned_cluster <- subset_2z %>% mutate(cluster = as.factor(cluster_assigments))

plot(hc_s2z, cex = 0.4, main = "Dendograma", las = 1,
     ylab = "Distancia euclidiana", xlab = "Grupos", hang = -1)

rect.hclust(hc_s2z, k = 3, border = 2:5)

# Calcular el coeficiente de Silhouette
sil <- silhouette(cluster_assigments, dist(subset_2z))
sil_avg <- mean(sil[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette: ", sil_avg)
## Coeficiente de Silhouette:  0.7417379

Subconjunto 3: Se identifican 2 grupos, con un coeficiente de Silhouette bajo.

subset_3 <- subset_list$'3' [, !(names(subset_list$'3') %in% c("id","zona","estrato","tipo","barrio","latitud","longitud","piso"))]

subset_3 =scale(subset_3)
subset_3z = as.data.frame(subset_3)
summary(subset_3z)
##     preciom          areaconst          parquea            banios        
##  Min.   :-1.3582   Min.   :-0.9514   Min.   :-0.7500   Min.   :-2.57965  
##  1st Qu.:-0.5965   1st Qu.:-0.5980   1st Qu.:-0.7500   1st Qu.:-0.91568  
##  Median :-0.2733   Median :-0.4252   Median : 0.2936   Median :-0.08369  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000  
##  3rd Qu.: 0.2345   3rd Qu.: 0.2384   3rd Qu.: 0.2936   3rd Qu.: 0.74830  
##  Max.   : 7.3855   Max.   : 8.1356   Max.   : 7.5991   Max.   : 4.90823  
##                                                                          
##     habitac          cluster_AC  
##  Min.   :-2.7429   Min.   : NA   
##  1st Qu.:-0.4103   1st Qu.: NA   
##  Median :-0.4103   Median : NA   
##  Mean   : 0.0000   Mean   :NaN   
##  3rd Qu.: 0.3672   3rd Qu.: NA   
##  Max.   : 5.0324   Max.   : NA   
##                    NA's   :1859
# distancia euclidiana
dist_s3z <- dist(subset_3z, method = 'euclidean')

# Cluster jerarquico con el método complete
hc_s3z <- hclust(dist_s3z, method = 'complete')

# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_s3z, k = 2)

# asignamos los clusters
assigned_cluster <- subset_3z %>% mutate(cluster = as.factor(cluster_assigments))

plot(hc_s3z, cex = 0.4, main = "Dendograma", las = 1,
     ylab = "Distancia euclidiana", xlab = "Grupos", hang = -1)

rect.hclust(hc_s3z, k = 2, border = 2:5)

# Calcular el coeficiente de Silhouette
sil <- silhouette(cluster_assigments, dist(subset_3z))
sil_avg <- mean(sil[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette: ", sil_avg)
## Coeficiente de Silhouette:  0.3732079

Subconjunto 4: Se identifican 2 grupos, con un coeficiente de Silhouette alto.

subset_4 <- subset_list$'4' [, !(names(subset_list$'4') %in% c("id","zona","estrato","tipo","barrio","latitud","longitud","piso"))]

subset_4 =scale(subset_4)
subset_4z = as.data.frame(subset_4)
summary(subset_4z)
##     preciom          areaconst          parquea            banios       
##  Min.   :-1.3141   Min.   :-0.8718   Min.   :-0.4555   Min.   :-2.4071  
##  1st Qu.:-0.6895   1st Qu.:-0.6112   1st Qu.:-0.4555   1st Qu.:-0.6256  
##  Median :-0.2854   Median :-0.4507   Median :-0.4555   Median :-0.6256  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.4494   3rd Qu.: 0.2912   3rd Qu.:-0.4555   3rd Qu.: 0.2651  
##  Max.   : 7.8708   Max.   : 6.7082   Max.   :11.9449   Max.   : 6.5003  
##                                                                         
##     habitac          cluster_AC  
##  Min.   :-2.5706   Min.   : NA   
##  1st Qu.:-0.3056   1st Qu.: NA   
##  Median :-0.3056   Median : NA   
##  Mean   : 0.0000   Mean   :NaN   
##  3rd Qu.: 0.4494   3rd Qu.: NA   
##  Max.   : 4.9794   Max.   : NA   
##                    NA's   :1213
# distancia euclidiana
dist_s4z <- dist(subset_4z, method = 'euclidean')

# Cluster jerarquico con el método complete
hc_s4z <- hclust(dist_s4z, method = 'complete')

# Determinamos a dónde pertenece cada observación
cluster_assigments <- cutree(hc_s4z, k = 2)

# asignamos los clusters
assigned_cluster <- subset_4z %>% mutate(cluster = as.factor(cluster_assigments))

plot(hc_s4z, cex = 0.4, main = "Dendograma", las = 1,
     ylab = "Distancia euclidiana", xlab = "Grupos", hang = -1)

rect.hclust(hc_s4z, k = 2, border = 2:5)

# Calcular el coeficiente de Silhouette
sil <- silhouette(cluster_assigments, dist(subset_4z))
sil_avg <- mean(sil[,3])

# Imprimir el coeficiente de Silhouette promedio
cat("Coeficiente de Silhouette: ", sil_avg)
## Coeficiente de Silhouette:  0.7351665

De estos dos grupos se realizó una división del conjunto de datos original para analizar las variables numéricas y que están más relacionadas con características internas del inmueble, como es el área, numero de dependencias y el precio. De dicho análisis se obtuvieron 9 grupos en total por lo cual estaríamos hablando de 4 grupos generales y 9 subgrupos.

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 del mercado inmobiliario.

Solución

Para iniciar, se realiza limpieza de la información con el objetivo de evitar valores duplicados.

datos_clasif2<- datos_clasif %>%
  mutate(barrio = toupper(trimws(datos_clasif$barrio)))

datos_clasif2 <- datos_clasif2 %>%
  mutate(tipo = toupper(trimws(datos_clasif$tipo)))

datos_clasif2 <- datos_clasif2 %>%
  mutate(zona = toupper(trimws(datos_clasif$zona)))

# Reemplazar valores en la columna "tipo"
datos_clasif2$tipo <- gsub("APTO", "APARTAMENTO", datos_clasif2$tipo)

Luego se procede a construir una tabla de contingencia para los datos categoricos de barrio y zona, estas dos variables como ya se explico hacen referencia a información externa al inmueble que afecta el precio del metro cuadrado, en el analisis de conglomerados se identificaron 4 grupos para las variables categoricas, lo cual coincide lo que se reporta en el analisis de correspondencia que se expone a continuación.

# Generar tabla de contingencia
tabla_contingencia1 <- table(datos_clasif2$barrio, datos_clasif2$zona)

# Crear un data frame para ggplot2
df_contingencia <- as.data.frame.matrix(tabla_contingencia1)
df_contingencia$Barrio <- rownames(df_contingencia)  # Agregar el nombre de barrio como columna

# Reorganizar el data frame para el gráfico de heatmap
df_melted <- melt(df_contingencia, id.vars = "Barrio", variable.name = "Zona", value.name = "Frecuencia")

# Crear un heatmap con ggplot2
heatmap_plot <- ggplot(df_melted, aes(x = Zona, y = Barrio, fill = Frecuencia)) +
                geom_tile() +
                scale_fill_gradient(low = "white", high = "blue") +
                labs(x = "Zona", y = "Barrio") +
                theme_minimal() +
                theme(axis.text.x = element_text(angle = 90, vjust = 0.1))

# Mostrar el heatmap
print(heatmap_plot)

# Realizar el análisis de correspondencia
resultados_ac <- CA(tabla_contingencia1)

valores_prop <-resultados_ac$eig ; valores_prop
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1  0.9824823               26.20249                          26.20249
## dim 2  0.9527724               25.41014                          51.61263
## dim 3  0.9498060               25.33102                          76.94365
## dim 4  0.8645155               23.05635                         100.00000
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")

Si bien la varianza explicada por cada componente es muy similar se podria decir que se trata de grupos homogeneos.

Ahora realizaremos el analisis con la zona y el tipo de inmueble, lo que nos arroja como resultado que en la base de datos existe un número superior de información para apartamentos en la zona sur y casi nulo en la zona norte. En el analisis se identifica que existe un solo grupo, por lo cual se podria considerar que es homogeneo el analisis entre zonas y tipo de inmueble.

# Generar tabla de contingencia
tabla_contingencia2 <- table(datos_clasif2$tipo, datos_clasif2$zona)

# Crear un data frame para ggplot2
df_contingencia <- as.data.frame.matrix(tabla_contingencia2)
df_contingencia$Tipo <- rownames(df_contingencia)  # Agregar el nombre de tipo como columna

# Reorganizar el data frame para el gráfico de heatmap
df_melted <- melt(df_contingencia, id.vars = "Tipo", variable.name = "Zona", value.name = "Frecuencia")

# Crear un heatmap con ggplot2
heatmap_plot <- ggplot(df_melted, aes(x = Zona, y = Tipo, fill = Frecuencia)) +
                geom_tile() +
                scale_fill_gradient(low = "white", high = "blue") +
                labs(x = "Zona", y = "Tipo") +
                theme_minimal() +
                theme(axis.text.x = element_text(angle = 90, vjust = 0.1))

# Mostrar el heatmap
print(heatmap_plot)

# Realizar el análisis de correspondencia
resultados_ac <- CA(tabla_contingencia2)


valores_prop <-resultados_ac$eig ; valores_prop
##       eigenvalue percentage of variance cumulative percentage of variance
## dim 1 0.07642923                    100                               100
fviz_screeplot(resultados_ac, addlabels = TRUE, ylim = c(0, 80))+ggtitle("")+
  ylab("Porcentaje de varianza explicado") + xlab("Ejes")
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

A manera de resumen, al realizar el agrupamiento por las variables categóricas zona, estrato, tipo y barrio, se obtuvieron cuatro grupos estos se pueden interpretar como agrupaciones relacionadas con variables externas al inmueble como lo es la zona, el barrio y el estrato. Se recomienda incluir más variables categóricas relacionadas con el entorno del inmueble como por ejemplo la disponibilidad de transporte, entre otras, de esta manera se lograría generar grupos de análisis más detallados.

Luego se puede concluir que para analizar el comportamiento de los precios de un inmueble debemos primero considerar las variables relacionadas con características de entorno, como ubicación (Latitud, Longitud), zonificación económica (estrato, zona, barrio), a partir de allí se generan grupos homogéneos en cuanto a la influencia externa de dichas variables y se puede analizar posteriormente el comportamiento de las variables intrínsecas del inmuebles como el área construida, planta de ubicación o numero de pisos, dependencias (baños, parqueadero, habitaciones) y se podrían considerar algunas adicionales como cocina, estado de conservación, acabados, con el fin de evaluar de mejor manera las características de determinan el precio por m2.