1.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.

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:

  1. Análisis de Componentes Principales: Reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y oferta del mercado.
  2. Análisis de Conglomerados: Agrupar las propiedades residenciales en segmentos homogéneos con características similares para entender las dinámicas de las ofertas específicas en diferentes partes de la ciudad y en diferentes estratos socioeconómicos.
  3. Análisis de Correspondencia : Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio), para identificar patrones de comportamiento de la oferta en mercado inmobiliario.
  4. Visualización de resultados: Presentar gráficos, mapas y otros recursos visuales para comunicar los hallazgos de manera clara y efectiva a la dirección de la empresa.

El informe final debe incluir análisis detallados de los resultados obtenidos, las conclusiones clave y las recomendaciones específicas para guiar las decisiones estratégicas de la empresa inmobiliaria. Se espera que este análisis de datos proporcione ventajas competitivas en el mercado, optimizando la inversión y maximizando los beneficios en un entorno altamente competitivo y en constante cambio.

2.Desarrollo

2.1.Exploración de los datos

Se importa el data set y se imprimen los primeros registros.

data("vivienda") #Import data viviendas
vivienda <- as.data.frame(vivienda) #Convert to dataframe

# Set factor attributes
vivienda$zona <- as.factor(vivienda$zona)
vivienda$piso <- as.numeric(vivienda$piso) #Convert to num
vivienda$estrato <- as.factor(vivienda$estrato)
vivienda$tipo <- as.factor(vivienda$tipo)
vivienda$barrio <- tolower(vivienda$barrio) #Convert to lower
vivienda$barrio <- chartr("áéíóú", "aeiou", vivienda$barrio) #Remove special characters
vivienda$barrio <- gsub(" ", "", vivienda$barrio) #Remove spaces
vivienda$barrio <- as.factor(vivienda$barrio) #Convert to a factor

# Print sample
formattable(head(vivienda))
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20dejulio -76.51168 3.43382
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20dejulio -76.51237 3.43369
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20dejulio -76.51537 3.43566
5992 Zona Sur 2 4 400 280 3 5 3 Casa 3dejulio -76.54000 3.43500
1212 Zona Norte 1 5 260 90 1 2 3 Apartamento acopi -76.51350 3.45891
1724 Zona Norte 1 5 240 87 1 3 3 Apartamento acopi -76.51700 3.36971

Se resume el comportamiento de las variables numéricas.

options(digits = 2)
descr(vivienda[, -c(1)])[c(1:7, 10, 14:15), ]
          areaconst  banios habitaciones latitud longitud parqueaderos    piso
Mean         174.93    3.11          3.6 3.4e+00 -7.7e+01         1.84    3.77
Std.Dev      142.96    1.43          1.5 4.3e-02  1.7e-02         1.12    2.61
Min           30.00    0.00          0.0 3.3e+00 -7.7e+01         1.00    1.00
Q1            80.00    2.00          3.0 3.4e+00 -7.7e+01         1.00    2.00
Median       123.00    3.00          3.0 3.4e+00 -7.7e+01         2.00    3.00
Q3           229.00    4.00          4.0 3.5e+00 -7.7e+01         2.00    5.00
Max         1745.00   10.00         10.0 3.5e+00 -7.6e+01        10.00   12.00
CV             0.82    0.46          0.4 1.2e-02 -2.3e-04         0.61    0.69
N.Valid     8319.00 8319.00       8319.0 8.3e+03  8.3e+03      6717.00 5684.00
Pct.Valid     99.96   99.96        100.0 1.0e+02  1.0e+02        80.71   68.30
          preciom
Mean       433.89
Std.Dev    328.65
Min         58.00
Q1         220.00
Median     330.00
Q3         540.00
Max       1999.00
CV           0.76
N.Valid   8320.00
Pct.Valid   99.98

Se resumen los valores de las variables cualitativas

summary(vivienda[, c("zona", "estrato", "tipo")])
           zona      estrato              tipo     
 Zona Centro : 124   3   :1453   Apartamento:5100  
 Zona Norte  :1920   4   :2129   Casa       :3219  
 Zona Oeste  :1198   5   :2750   NA's       :   3  
 Zona Oriente: 351   6   :1987                     
 Zona Sur    :4726   NA's:   3                     
 NA's        :   3                                 

Se observa que los valores únicos de las variables tienen buen comportamiento y no presentan errores de escritura.

Datos faltantes

Se analizan los valores vacíos por atributo.

empty_vivienda <- gg_miss_var(vivienda, show_pct = TRUE) +
  geom_text(data = miss_var_summary(vivienda),
            aes(y = pct_miss, label = round(as.numeric(pct_miss), 2),
                vjust = 1.5), size = 3) +
  labs(y = "% Prop datos faltantes", x = "")
empty_vivienda

Se observa que el atributo “piso” tiene el 31.7% de sus registros vacíos, al ser una proporción superior al 10% no se recomendaría eliminarlos, sino más bien imputarlos, de igual manera sucede con el atributo “parqueaderos”. El resto de atributos, contienen registros vacíos que se podrían omitir.

Definición de Datos Atípicos

Se realizan boxplots para analizar el comportamiento de valores atípicos por atributo.


# Piso as number
vivienda$piso <- as.numeric(vivienda$piso)

# Creat boxplot
box_pisos <- boxplot(as.numeric(vivienda$piso) ~ vivienda$tipo,
                           xlab = NA,
                           ylab = "Pisos")

# Add stats
stats <- box_pisos$stats
n <- ncol(stats)

for (i in 1:n) {
  text(i, stats[1, i], labels = round(stats[1, i], 2), pos = 4, col = "blue")
  text(i, stats[2, i], labels = round(stats[2, i], 2), pos = 4, col = "blue")
  text(i, stats[3, i], labels = round(stats[3, i], 2), pos = 4, col = "blue")
  text(i, stats[4, i], labels = round(stats[4, i], 2), pos = 4, col = "blue")
  text(i, stats[5, i], labels = round(stats[5, i], 2), pos = 4, col = "blue")
}

A pesar de que el atributo “piso” se tratará como una variable discreta, según el método del criterio del rango intercuartílico multiplicado por 1.5, una casa con más de 4 pisos se considerará como un dato atípico, lo cual es lógico y por tal razón, se mantendrá ese criterio para la limpieza de datos del atributo piso.

# Creat boxplot
box_preciom <- boxplot(vivienda$preciom,
                       xlab = "Precio m^2",
                       ylab = "Decenas de miles $COP")

# Add stats
text(1, box_preciom$stats,
     labels = round(box_preciom$stats, 2),
     pos = 2,
     col = "blue")

Como no se cuenta con información acerca de la unidad del atributo “preciom”, se supone que hace referencia al precio del metro cuadrado y, para obtener valores cercanos a la realidad, se establece la unidad en decenas de miles de pesos colombianos, así, por ejemplo, un registro cuyo preciom sea igual a 250 y cuyo atributo areaconst (cuya unidad se supone en m^2) sea igual a 70, es razonable que cueste $175.000.000 COP obtenidos al multiplicar 250 x 10x1000 x70 (es decir, preciom por decenas de miles por areaconst).

En este atributo, se observan datos atípicos por encima de 1015 y por debajo de 58, como el precio del m^2 puede estar influenciado por los demás atributos, no se elegirá el criterio del rango intercuartíclico para el tratamiento de datos atípicos, más bien se utilizará la técnica de Isulation Forest la cual separa aquellas observaciones con características distintas al resto por medio de la combinación de múltiples árboles llamados isolation trees.

# Creat boxplot
box_areaconst <- boxplot(vivienda$areaconst ~ vivienda$tipo,
                         xlab = NA,
                         ylab = "Area construida m^2",
                         ylim = c(-10, 1750))

# Add stats
stats <- box_areaconst$stats
n <- ncol(stats)

for (i in 1:n) {
  text(i, stats[1, i], labels = round(stats[1, i], 2), pos = 1, col = "blue", cex = 0.7)
  text(i, stats[2, i], labels = round(stats[2, i], 2), pos = 2, col = "blue", cex = 0.7)
  text(i, stats[3, i], labels = round(stats[3, i], 2), pos = 4, col = "blue", cex = 0.7)
  text(i, stats[4, i], labels = round(stats[4, i], 2), pos = 2, col = "blue", cex = 0.7)
  text(i, stats[5, i], labels = round(stats[5, i], 2), pos = 2, col = "blue", cex = 0.7)
}

En el atributo de areaconst también se observan datos atípicos. Como dicho atributo puede estar influenciado por los demás atributos, no se elegirá el criterio del rango intercuartíclico para el tratamiento de datos atípicos, sino la técnica de Isulation Forest.

# Define frame
par(mfrow = c(1, 3))

# Parqueaderos
box_parqueaderos <- boxplot(vivienda$parqueaderos,
                            xlab = NA,
                            ylab = "Parqueaderos")
# Add stats
text(1, box_parqueaderos$stats,
     labels = round(box_parqueaderos$stats, 2),
     pos = 3,
     col = "blue")


# Banios
box_banios <- boxplot(vivienda$banios,
                      xlab = NA,
                      ylab = "banios")
# Add stats
text(1, box_banios$stats,
     labels = round(box_banios$stats, 2),
     pos = 3,
     col = "blue")


# Habitaciones
box_habitaciones <- boxplot(vivienda$habitaciones,
                      xlab = NA,
                      ylab = "habitaciones")
# Add stats
text(1, box_habitaciones$stats,
     labels = round(box_habitaciones$stats, 2),
     pos = 3,
     col = "blue")


par(mfrow = c(1, 1))

En el resto de atributos tratados como variables discretas, se observa que hay registros que no cuentan con baño o habitaciones, como el estudio va dirigido a viviendas (y toda vivienda deberá tener por lo menos 1 baño y 1 habitación), los registros que tengan un valor cero en estos atributos, no serán tenidos en cuenta. A parte de lo mencionado, no se realizará una limpieza de datos adicional en estos atributos.

2.2.Preprocesamiento de Datos

2.2.1.Tratamiento de Datos faltantes

Como se determinó en la Exploración de los Datos, aquellos registros que tienen datos faltantes en una proporción inferior al 10% no serán tenidos en cuenta y se eliminarán del dataset, por lo que sólo se imputarán datos en los atributos “piso” y “parqueaderos”.

Para el atributo “piso” se analiza el comportamiento de los datos faltantes respecto al resto de atributos.

source("Script Faltantes Piso.r")

Se observa que los datos faltantes del atributo “piso” presentan una distribucion casi uniforme respecto a los atributos “estrato” y “tipo”, así entonces, se interpreta que la relación de datos faltantes es aleatoria (MAR) y podrían reemplazarse por la media o eliminarse sin afectar la distribución de los datos. Por otra parte, la relación de los datos faltantes del atributo “piso” con los atributos “zona” y “habitaciones” no fue aleatoria (MNAR), pues no todas las zonas tuvieron la misma probabilidad de obtener datos faltantes del atributo “piso”, igualmente sucedió con “habitaciones”, por lo tanto, se tiene en cuenta esta relación para imputar los datos faltantes del atributo “piso” utilizando el método de la mediana.

El valor con el que se imputarán los datos faltantes del atributo “piso” se calculará obteniendo la mediana de los registros que se encuentren en la misma zona y que tengán un mismo número de habitaciones.

Para el atributo “parqueadero” se analiza el comportamiento de los datos faltantes respecto al resto de atributos.

source("Script Faltantes Parqueo.r")

Para los datos faltantes del atributo “parqueaderos” se observa una relación MAR con el atributo “tipo” y una MNAR con el resto de atributos, por lo que se procederá de manera similar a la imputación planteada para el atributo “piso”.

Una vez se realizó la imputacion, se verifica que no haya datos faltantes nuevamente.

colSums(is.na(vivienda_clean))
          id         zona         piso      estrato      preciom    areaconst 
           0            0            0            0            0            0 
parqueaderos       banios habitaciones         tipo       barrio     longitud 
           0            0            0            0            0            0 
     latitud 
           0 

2.2.2.Limpieza de Datos

Como se mencionó en la Exploración de Datos, no se considerarán viviendas que no cuenten con baños ni habitaciones, de igual manera, se determinó que las casas con más de 4 pisos quedarán excluidas del análisis por considerarse atípicas.

#Clean DataSet
vivienda_clean <- subset(vivienda_clean,
                         habitaciones != 0
                         & banios != 0
                         & !(tipo == "casa" & piso > 4))

2.2.3.Tratamiento de Datos Atípicos

Se utilizará la técnico Isolation Tree para la determinación de datos atípicos. Los atributos Latitud y Longitud no son de interés, por lo que se excluirán del análisis.

set.seed(123)

# Import library
library(isotree)
vivienda_clean <- vivienda_clean[, -c(11:13)] #Select all columns without Latitud y longitud

#Create the model
modelo_iforest <- isolation.forest(vivienda_clean, ntrees = 100, sample_size = 256)

# Calculate puntuaciones of anomalies for each observation
puntuaciones <- predict(modelo_iforest, vivienda_clean, type = "score")

# Add puntuaciones to the dataframe
vivienda_clean$anomaly_score <- puntuaciones

# Define the limit to declare outlier
umbral <- 0.6

#Get dataset with no outliers
vivienda_clean <- subset(vivienda_clean, vivienda_clean$anomaly_score < umbral)

#Calculate prop removed data
vivienda_clean_nrow <- nrow(vivienda_clean)
vivienda_nrow <- nrow(vivienda)
prop_clean <- round(vivienda_clean_nrow/vivienda_nrow, 4)*100

Luego de la imputación de datos faltantes y tratamiento de datos atípicos, la proporción de datos remanentes respecto al dataset original fue de 97.44% con lo que se corrobora la poca pérdida de información durante el preprocesamiento de los datos.

2.3.Análisis Multivariado

Inicialmente, se analisa el precio de la vivienda por zona a través de un análisis bivariado por medio del siguiente diagrama de cajas.

precio_zona_box <- ggplot(vivienda_clean, aes(x = zona, y = preciom)) +
geom_boxplot(outlier.shape = NA, notch = TRUE) +
stat_boxplot(geom = "errorbar", width = 0.15) +
labs(x = "", y = "Decenas de miles $COP")
print(precio_zona_box)

Para ejecutar el análsis multivariado, se estandarizan las variables numéricas (excluyendo el atributo “estrato” ya que se tratará como categórico) y la variable “preciom” ya que se tratará como variable de respuesta.

vivienda_num_z <- scale(vivienda_clean[, c(3, 6:9)]) #Scale num variables
vivienda_z <- cbind(vivienda_clean[, c(1:2, 4:5, 10)], vivienda_num_z) #Join categoric columns

Posteriormente, los atributos categóricos “zona” y “tipo” se tratan con la técnica one-hot enconding, aunque aumentará la dimensionalidad del dataset, se cuenta con un número de registros muy superior al número de atributos con los que se contará.

vivienda_z["Zona_Centro"] <- ifelse(vivienda_z$zona == "Zona Centro", 1, 0)
vivienda_z["Zona_Norte"] <- ifelse(vivienda_z$zona == "Zona Norte", 1, 0)
vivienda_z["Zona_Oeste"] <- ifelse(vivienda_z$zona == "Zona Oeste", 1, 0)
vivienda_z["Zona_Oriente"] <- ifelse(vivienda_z$zona == "Zona Oriente", 1, 0)
vivienda_z["Zona_Sur"] <- ifelse(vivienda_z$zona == "Zona Sur", 1, 0)
vivienda_z["Casa"] <- ifelse(vivienda_z$tipo == "Casa", 1, 0)
vivienda_z["Apartamento"] <- ifelse(vivienda_z$tipo == "Apartamento", 1, 0)

vivienda_z <- vivienda_z[, c(1, 3:4, 6:17)] #Select columns with numbers

2.3.1.Análisis de Componentes Principales

Para este análisis, sólo se incluyen los atributos numéricos contínuos, no tiene sentido incluir aquellos categóricos ya que la metodología se basa en las covarianzas y un atributo categórico no cuenta con esta propiedad.

El análisis arrojó 5 componentes principales cuyas varianzas explicadas se presentan en la siguiente gráfica.

pca <- prcomp(vivienda_z[, c(4:8)]) #Analisis made just in continues attributes
pca$rotation
               PC1    PC2    PC3    PC4   PC5
piso          0.22 -0.840 -0.480  0.053 -0.12
areaconst    -0.53 -0.061  0.011  0.813 -0.22
parqueaderos -0.43 -0.417  0.578 -0.421 -0.36
banios       -0.53 -0.184 -0.133 -0.142  0.80
habitaciones -0.45  0.288 -0.646 -0.373 -0.40
prop_var <- (pca$sdev^2)/sum(pca$sdev^2)
pca_bar <- barplot(prop_var,
                   names.arg = c("PC1", "PC2", "PC3",
                                 "PC4", "PC5"),
                   ylim = c(0, 0.7),
                   ylab = "Explained Variance %",
                   axes = TRUE)
points(pca_bar, prop_var)
lines(pca_bar, prop_var)
text(pca_bar, prop_var + 0.05,
     labels = round(prop_var, 3))

Se observa que las primeras 4 componentes principales explican casi el 94% de la varianza total, por lo que se reduciría la dimension de 5 a 4. Al agregar las variables categóricas, se obtendría un dataset en función de componentes principales cuyas dimensiones son 8109x14.

#Add Principal Componentes to the DataSet
vivienda_pca <- vivienda_z
vivienda_pca["PC1"] <- pca$x[, 1]
vivienda_pca["PC2"] <- pca$x[, 2]
vivienda_pca["PC3"] <- pca$x[, 3]
vivienda_pca["PC4"] <- pca$x[, 4]

#Select just Principal Componentes and Categoric Variables
vivienda_pca <- vivienda_pca[, c(1:3, 9:19)]

#print head
formattable(head(vivienda_pca))
id estrato preciom Zona_Centro Zona_Norte Zona_Oeste Zona_Oriente Zona_Sur Casa Apartamento PC1 PC2 PC3 PC4
1147 3 250 0 0 0 1 0 1 0 -0.23 1.43 -1.25 -1.031
1169 3 320 0 0 0 1 0 1 0 0.97 0.90 0.31 0.232
1350 3 350 0 0 0 1 0 1 0 -0.22 0.64 0.42 0.156
5992 4 400 0 0 0 0 1 1 0 -1.78 -0.44 1.20 0.064
1212 5 260 0 1 0 0 0 0 1 1.00 1.29 0.52 0.017
1724 5 240 0 1 0 0 0 0 1 0.61 1.15 0.42 -0.109

2.3.2.Análisis de Correspondencia

Para el análisis de correspondencia, se tomarán sólo las variables categóricas sin transformarlas mediante one-hot enconding. El dataset preparado previamente, cuenta con 3 atributos categóricos “zona”, “estrato” y “tipo” lo que nos obliga a realizar un análisis de correspondencia múltiple. Para ello, se utilizó la función MCA() de la libreria factoextra.

vivienda_mca <- vivienda_clean[, c(2, 4, 10)] #Select just categoric attributes
mca_result <- MCA(vivienda_mca, graph = FALSE) #Multiple correspondance analysis
formattable(mca_result$eig) #Print Eigenvalues
      eigenvalue percentage of variance cumulative percentage of variance
dim 1 0.563      21.11                  21.11                            
dim 2 0.4548     17.05                  38.17                            
dim 3 0.3806     14.27                  52.44                            
dim 4 0.3335     12.51                  64.95                            
dim 5 0.3235     12.13                  77.08                            
dim 6 0.2695     10.11                  87.18                            
dim 7 0.1998     7.492                  94.68                            
dim 8 0.142      5.324                  100                              

El resultado del análisis arroja 8 dimensiones con porcentajes de explicación bajos, lo que implica que la varianza explicada acumulada sólo alcance un valor superior al 95% considerando todas las dimensiones.

# Get Coordinates from variables
var_coords <- as.data.frame(mca_result$var$coord)
var_coords["Variable"] <- ifelse(startsWith(rownames(var_coords), "Zona"),
                                 "Zona",
                                 ifelse((startsWith(rownames(var_coords), "Apartamento")
                                        | startsWith(rownames(var_coords), "Casa")),
                                        "tipo",
                                        "estrato"))
names(var_coords) <- c("Dim1", "Dim2", "Dim3", "Dim4", "Dim5", "Variable")

# Create Graph
ggplot(var_coords, aes(x = Dim1, y = Dim2,
                       color = Variable,
                       shape = Variable,
                       label = rownames(var_coords))) +
  geom_point() +
  geom_text(vjust = -0.5) +
  labs(title = "Análisis de Correspondencia Múltiple (MCA)",
       x = paste("Dimensión1(", round(mca_result$eig[1, 2], 2), "%)", sep = ""),
       y = paste("Dimensión2(", round(mca_result$eig[2, 2], 2), "%)", sep = "")) +
  theme_minimal()

Al graficar la relación de atributos categóricos, se observa que la Zona Oriente y Centro tienen más relación con el estrato 3 y posteriormente con el 6 con similitud en el número de casas y apartamentos, además por las distancias que tiene respecto a los demás atributos, se observa que el número de viviendas disponibles en estas zonas es inferior a las demás.

Respecto a la Zona Oeste, se observa que en su mayoría tendrá viviendas de estrato 6 y se encontrarán más apartamentos que casas.

Finalmente, se observa que la mayor oferta de viviendas se encuentran en las Zonas Norte y Sur, donde los estratos más comunes son el 5 y el 4. En la zona Norte, se encontrarán más casas de estrato 5 que de cualquier otro estrato, mientras que en la zona sur se pueden encontrar casas y apartamentos en proporciones similares y en su mayoría de estrato 5.

2.3.3.Análisis de Conglomerados

Debido a la gran cantidad de Datos y Atributos, se decide realizar un análisis no jerárquico utilizando el método K-means. Se utilizará el DataSet con variables numéricas continuas estandarizadas y varaibles categóricas representadas con variables binarias mediante la técnica one-hot enconding. El análisis se realizará por Zona buscando caracterizar las viviendas bajo este atributo y sólo se presentarán las zonas sur y norte, las cuales contienen aproximadamente el 80% de los datos.

Zona Sur

Se selecciona el número de clústers bajo el criterio del “codo” graficando el Total Within Sum of Square, se observa que cuando k=8 aparece el cambio de pendiente.

#Dataframe
TWSS_k <- data.frame(seq(1, kmax), TWSS)
names(TWSS_k) <- c("k", "TWSS")

#Plot TWSS vs k
plot(seq(1, kmax), TWSS, type = "o",
     xlim = c(0, 20),
     xlab = "k",
     main = "TWSS vs k")
#Point k = 15
lines(c(8, 8), c(0, 11000), col = "red")
text(8, 15000, labels = "TWSS = 8758 \n k = 8", col = "red")

Se realiza el análisis MCA con k=8

mca_table
                Value
Número Clusters     8
TOTSS           31143
TWSS             8758
BSS             22386
BSS/TSS            72

En la tabla anterior se observa el rendimiento del agrupamiento donde se destaca el valor del cociente BSS/TSS lo que implica que la distribución de los clústers explica en un 72% la variación total de los datos.

En la siguiente gráfica se observa que los clusters están sobrepuestos, sin embargo, podría ser un resultado de graficarlos sólo en 2 dimensiones, pues al agregar más dimensiones puede que no ocupen el mismo espacio ya que las componentes principales de los ejes sólo explican un 75% de la varianza total.

# Plot Clusters
fviz_cluster(mca_result,
             data = vivienda_z_sur,
             ellipse.type = "euclid", # Elipse de concentración
             star.plot = FALSE, # No añadir segmentos desde los centroides a los ítems
             ggtheme = theme_minimal(),
             geom = "none", # No mostrar puntos de observaciones
             show.clust.cent = TRUE) # Mostrar solo los centroides

Zona Norte

Se selecciona el número de clústers bajo el criterio del “codo” graficando el Total Within Sum of Square, se observa que cuando k=7 aparece el cambio de pendiente.

#Dataframe
TWSS_k <- data.frame(seq(1, kmax), TWSS)
names(TWSS_k) <- c("k", "TWSS")

#Plot TWSS vs k
plot(seq(1, kmax), TWSS, type = "o",
     xlim = c(0, 20),
     xlab = "k",
     main = "TWSS vs k")
#Point k = 15
lines(c(7, 7), c(0, 4000), col = "red")
text(7, 4500, labels = "TWSS = 3391 \n k = 7", col = "red")

Se realiza el análisis MCA con k=7

mca_table
                Value
Número Clusters     7
TOTSS           11297
TWSS             3473
BSS              7823
BSS/TSS            69

En la tabla anterior se observa el rendimiento del agrupamiento donde se destaca el valor del cociente BSS/TSS lo que implica que la distribución de los clústers explica en un 69% la variación total de los datos.

En la siguiente gráfica se observa que los clusters están sobrepuestos, sin embargo, podría ser un resultado de graficarlos sólo en 2 dimensiones, pues al agregar más dimensiones puede que no ocupen el mismo espacio ya que las componentes principales de los ejes sólo explican un 73.5% de la varianza total.

# Plot Clusters
fviz_cluster(mca_result,
             data = vivienda_z_norte,
             ellipse.type = "euclid", # Elipse de concentración
             star.plot = FALSE, # No añadir segmentos desde los centroides a los ítems
             ggtheme = theme_minimal(),
             geom = "none", # No mostrar puntos de observaciones
             show.clust.cent = TRUE) # Mostrar solo los centroides

3.Conclusiones

Las conclusiones que deja el presente análisis son varias.

La primera de ellas, es que la mayor oferta de la ciudad en materia inmobiliaria es la zona sur, donde los estratos 4 y 5 tienen mayor participación y donde las casas y apartamentos tienen una proporción similar según los resultados del análisis de correspondencia. En esta zona el preciom promedio es de $414 x 10.000 millones COP.

La zona norte presenta un comportamiento similar a la zona sur, sin embargo, en esta encontraremos más oferta de casas que de apartamentos.

La zona Oeste ofrece los bienes inmobiliarios más costosos según el análisis bivariado, esto podría ser consecuencia de que en esa zona hay mayor oferta de viviendas en el estrato 6 según lo mostrado en el análisis de correspondencia.

Por último, las zonas centro y oriente son las menos atractivas en términos de oferta. En estas zonas se encontrarán viviendas en su mayoría de estrato 3 y más casas que apartamentos.

Respecto al comportamiento del preciom de las viviendas, el análisis de componentes principales, a través del peso de cada atributo sobre las componentes, nos muestra que el atributo numérico que más peso tiene sobre las componentes es el número de parqueos, seguida del número de habitaciones y el área construida.