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: - 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.
dimension <- dim(datos_inmobiliarios)
dimension
[1] 8322 13
Se cuenta con 8322 registros y 13 dimensiones.
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.
# 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>
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:
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
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.
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:
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.
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.
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.
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
table(datos_inmobiliarios$tipo)
Apartamento Casa
5100 3219
table(datos_inmobiliarios$zona)
Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
124 1920 1198 351 4726
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.
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.
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.
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
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:
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
))
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…
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.
boxplot(datos_inmobiliarios$preciom~ datos_inmobiliarios$zona, data = datos_inmobiliarios, col = c("pink", "cyan", "orange","green","purple" ), ylab = "Precio", xlab = "Zona")
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).
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
var_numericas <- datos_inmobiliarios[, c("preciom","estrato", "areaconst", "habitaciones", "banios","parqueaderos")]
correlacion<-round(cor(var_numericas), 1)
corrplot(correlacion, method="number", type="upper")
Conclusión
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>
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>
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
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
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
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.
scree(variablesNumericas,main ="Grafico de Sedimentacion")
fviz_pca_var(datos_inm.pca,
col.var = "contrib", #
gradient.cols = c("#FF7F00", "#034D94"),
repel = TRUE
)
fviz_contrib(datos_inm.pca, choice = "var", axes = 1, top = 10) # PC1
fviz_contrib(datos_inm.pca, choice = "var", axes = 2, top = 10) # PC2
En PC2 vemos mayor contribución de habitacciones, estrato y piso
fviz_contrib(datos_inm.pca, choice = "var", axes = 3, top = 10) # PC3
Determinado prinicpalmente por la zona.
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>
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.
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
inm_escalado_cluster <- scale(cluster_vars)
inm_escalado_cluster <- as.data.frame(inm_escalado_cluster)
Para evaluar el núero de clusters se usarán varios métodos
set.seed(123)
fviz_nbclust(inm_escalado_cluster, kmeans, method = "silhouette") +
labs(subtitle = "Silhouette method")
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")
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
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.
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,
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.
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 |
Estrategias y recomendaciones como:
Resaltar las características de los apartamentos que los hacen convenientes:
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
Apartamentos de 3 o 4 habitaciones con 2 parqueaderos en estrato 6