library(paqueteMODELOS)
## Cargando paquete requerido: boot
## Cargando paquete requerido: broom
## Warning: package 'broom' was built under R version 4.5.3
## Cargando paquete requerido: GGally
## Warning: package 'GGally' was built under R version 4.5.3
## Cargando paquete requerido: ggplot2
## Warning: package 'ggplot2' was built under R version 4.5.3
## Cargando paquete requerido: gridExtra
## Warning: package 'gridExtra' was built under R version 4.5.3
## Cargando paquete requerido: knitr
## Cargando paquete requerido: summarytools
## Warning: package 'summarytools' was built under R version 4.5.3
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.5.3
## Warning: package 'dplyr' was built under R version 4.5.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.1.6
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ lubridate 1.9.5 ✔ tibble 3.3.1
## ✔ purrr 1.2.1 ✔ tidyr 1.3.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::combine() masks gridExtra::combine()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ tibble::view() masks summarytools::view()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(FactoMineR)
## Warning: package 'FactoMineR' was built under R version 4.5.3
library(factoextra)
## Warning: package 'factoextra' was built under R version 4.5.3
## Welcome to factoextra!
## Want to learn more? See two factoextra-related books at https://www.datanovia.com/library/principal-component-methods
library(cluster)
## Warning: package 'cluster' was built under R version 4.5.3
data("vivienda")
str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ id : num [1:8322] 1147 1169 1350 5992 1212 ...
## $ zona : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
## $ piso : chr [1:8322] NA NA NA "02" ...
## $ estrato : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
## $ preciom : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
## $ areaconst : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
## $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
## $ banios : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
## $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
## $ tipo : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
## $ barrio : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
## $ longitud : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
## $ latitud : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
## - attr(*, "spec")=
## .. cols(
## .. id = col_double(),
## .. zona = col_character(),
## .. piso = col_character(),
## .. estrato = col_double(),
## .. preciom = col_double(),
## .. areaconst = col_double(),
## .. parqueaderos = col_double(),
## .. banios = col_double(),
## .. habitaciones = col_double(),
## .. tipo = col_character(),
## .. barrio = col_character(),
## .. longitud = col_double(),
## .. latitud = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
summary(vivienda)
## 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
head(vivienda)
## # A tibble: 6 × 13
## 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
## 6 1724 Zona N… 01 5 240 87 1 3 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
La base contiene 8.322 viviendas y 13 variables, incluyendo variables numéricas (precio, área construida, baños, habitaciones, etc.) y categóricas (zona, tipo, barrio, piso).
#Tabla con el calculo de medidas de tendencia central junto con el coneto de datos faltantes por variables
library(dplyr)
library(knitr)
library(kableExtra)
##
## Adjuntando el paquete: 'kableExtra'
## The following object is masked from 'package:dplyr':
##
## group_rows
library(tibble)
library(tidyr)# para kable
# Supongamos que trabajas con el dataset vivenda
vars_cuantitativas_reales <- vivienda %>%
select(preciom,areaconst, banios, parqueaderos, habitaciones,estrato)
df_long <- vars_cuantitativas_reales %>%
pivot_longer(cols = everything(),
names_to = "Variable",
values_to = "Valor")
tabla_resumen <- df_long %>%
group_by(Variable) %>%
summarise(
Media = mean(Valor, na.rm = TRUE),
Mediana = median(Valor, na.rm = TRUE),
SD = sd(Valor, na.rm = TRUE),
Min = min(Valor, na.rm = TRUE),
Q1 = quantile(Valor, 0.25, na.rm = TRUE),
Q3 = quantile(Valor, 0.75, na.rm = TRUE),
Max = max(Valor, na.rm = TRUE),
NA_count = sum(is.na(Valor))
)
# Mostrar con kable
kable(tabla_resumen, caption = "Resumen de variables numéricas con NA") %>%
kable_styling(bootstrap_options = c("striped"))
| Variable | Media | Mediana | SD | Min | Q1 | Q3 | Max | NA_count |
|---|---|---|---|---|---|---|---|---|
| areaconst | 174.934938 | 123 | 142.964126 | 30 | 80 | 229 | 1745 | 3 |
| banios | 3.111311 | 3 | 1.428210 | 0 | 2 | 4 | 10 | 3 |
| estrato | 4.633610 | 5 | 1.029222 | 3 | 4 | 5 | 6 | 3 |
| habitaciones | 3.605361 | 3 | 1.459537 | 0 | 3 | 4 | 10 | 3 |
| parqueaderos | 1.835194 | 2 | 1.124909 | 1 | 1 | 2 | 10 | 1605 |
| preciom | 433.891947 | 330 | 328.647244 | 58 | 220 | 540 | 1999 | 2 |
Como se ve en la tabla, la variable “Parqueaderos” maneja una cantidad considerbale de datos faltantes NA, 1605. Estos deben representar en realidad valores iguales a 0 que no fueron diligenciados.
Para evitar perder información relevante se reemplazarán todos los NA de la variable Parqueadores por valores de 0.
Para las demás variables numericas, al tener en promedio menos de 3 datos faltantes, se decidirá por eliminar esas filas.
df2 <- vivienda %>%
mutate(parqueaderos = ifelse(is.na(parqueaderos), 0, parqueaderos))
Para poder trabajar con las variables numericas, se deberá realizar un proceso de estandarización:
datos_num <- df2 %>%
select(preciom, areaconst, parqueaderos,
banios, habitaciones,estrato) %>%
na.omit()
datos_std <- scale(datos_num)
summary(datos_std)
## preciom areaconst parqueaderos banios
## Min. :-1.1437 Min. :-1.0138 Min. :-1.1920 Min. :-2.17847
## 1st Qu.:-0.6508 1st Qu.:-0.6640 1st Qu.:-0.3876 1st Qu.:-0.77811
## Median :-0.3161 Median :-0.3633 Median :-0.3876 Median :-0.07794
## Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.00000
## 3rd Qu.: 0.3228 3rd Qu.: 0.3782 3rd Qu.: 0.4169 3rd Qu.: 0.62224
## Max. : 4.7620 Max. :10.9822 Max. : 6.8521 Max. : 4.82330
## habitaciones estrato
## Min. :-2.4702 Min. :-1.5872
## 1st Qu.:-0.4148 1st Qu.:-0.6156
## Median :-0.4148 Median : 0.3560
## Mean : 0.0000 Mean : 0.0000
## 3rd Qu.: 0.2704 3rd Qu.: 0.3560
## Max. : 4.3813 Max. : 1.3276
pca <- prcomp(datos_std, center = TRUE, scale. = TRUE)
summary(pca)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6
## Standard deviation 1.8481 1.1222 0.6780 0.65924 0.49098 0.43579
## Proportion of Variance 0.5692 0.2099 0.0766 0.07243 0.04018 0.03165
## Cumulative Proportion 0.5692 0.7791 0.8557 0.92817 0.96835 1.00000
Como podemos ver, los componentes 1 y 2 explican la mayor parte de la variación, con un 76%. Eso significa que con esas dos nuevas variables se captura una gran parte de la información de las 6 variables originales.
library(FactoMineR)
library(factoextra)
library(cluster)
fviz_eig(pca, addlabels = TRUE)
fviz_pca_biplot(pca,
repel = TRUE,
col.var = "red",
col.ind = "gray")
pca$rotation
## PC1 PC2 PC3 PC4 PC5 PC6
## preciom 0.4781754 0.1883533 -0.0359721 -0.36062553 0.21967777 -0.7458339
## areaconst 0.4417250 -0.2388377 -0.2878930 -0.61783592 -0.27859384 0.4534506
## parqueaderos 0.4096354 0.2607395 -0.6565763 0.57091588 -0.04760414 0.0700729
## banios 0.4668745 -0.1921461 0.3633148 0.18500105 0.67876421 0.3437503
## habitaciones 0.2847775 -0.6813261 0.2111757 0.34936005 -0.44521273 -0.2997240
## estrato 0.3306904 0.5816950 0.5551021 0.07749341 -0.46143880 0.1587620
fviz_nbclust(datos_std, kmeans, method = "wss")
set.seed(123)
km <- kmeans(datos_std, centers = 3)
km$size
## [1] 999 2084 5236
datos_cluster <- datos_num
datos_cluster$grupo <- factor(km$cluster)
fviz_cluster(km,
data = datos_std,
ellipse.type = "convex",
ggtheme = theme_minimal())
aggregate(datos_num,
by = list(Grupo = km$cluster),
mean)
## Grupo preciom areaconst parqueaderos banios habitaciones estrato
## 1 1 445.3784 297.4792 1.214214 4.408408 6.365365 3.953954
## 2 2 856.1209 296.3820 2.796545 4.508637 3.909789 5.716411
## 3 3 263.6673 103.2166 1.009549 2.307678 2.957601 4.332315
El grupo 1 representa viviendas grandes, con muchas habitaciones y baños, pero de precio medio. Podrían ser casas amplias en estratos medios.
El grupo 2 representa las viviendas más costosas, con más parqueaderos y en estratos altos. Son propiedades de lujo o de mayor nivel socioeconómico.
El grupo 3 corresponde a viviendas más pequeñas y económicas, con menos habitaciones y área reducida.
library(FactoMineR)
library(factoextra)
library(dplyr)
# Seleccionamos variables categóricas
barrios_top <- vivienda %>%
count(barrio, sort = TRUE) %>%
slice(1:15) %>%
pull(barrio)
vivienda2 <- vivienda %>%
filter(barrio %in% barrios_top)
tabla <- vivienda2 %>%
select(tipo, zona, barrio) %>%
mutate(across(everything(), as.factor))
# Análisis de correspondencias múltiples
ca <- MCA(tabla, graph = FALSE)
fviz_mca_biplot(ca, repel = TRUE)
Para que el MCA fuese mucho más legible, se decidió por filtrar los barrios a un maximo 15 categorías. Se evidencia que la distribución del tipo de vivienda en la ciudad no es homogénea:
Norte y Oeste: predominio de apartamentos, asociados a barrios como La Flora, Prados del Norte, Santa Teresita y Normandía.
Sur: mayor diversidad, con barrios de casas tradicionales (Ciudad Jardín, Pance) y otros de alta densidad de apartamentos (Valle del Lili).
Centro y Oriente: baja oferta, con poca representatividad en el mercado.
tabla2 <- table(vivienda2$barrio, vivienda2$tipo,
vivienda2$zona)
tabla2
## , , = Zona Centro
##
##
## Apartamento Casa
## acopi 0 0
## aguacatal 0 0
## ciudad jardín 0 0
## el caney 0 0
## el ingenio 0 0
## el limonar 0 0
## el refugio 0 0
## la flora 0 0
## la hacienda 0 0
## los cristales 0 0
## normandía 0 0
## pance 0 0
## prados del norte 1 0
## santa teresita 0 0
## valle del lili 0 0
##
## , , = Zona Norte
##
##
## Apartamento Casa
## acopi 87 70
## aguacatal 0 0
## ciudad jardín 3 0
## el caney 0 0
## el ingenio 0 0
## el limonar 0 0
## el refugio 0 0
## la flora 266 99
## la hacienda 0 0
## los cristales 0 0
## normandía 0 0
## pance 0 0
## prados del norte 93 31
## santa teresita 2 0
## valle del lili 3 1
##
## , , = Zona Oeste
##
##
## Apartamento Casa
## acopi 0 0
## aguacatal 97 11
## ciudad jardín 0 0
## el caney 0 0
## el ingenio 0 0
## el limonar 0 0
## el refugio 0 0
## la flora 0 0
## la hacienda 0 0
## los cristales 137 17
## normandía 149 4
## pance 0 0
## prados del norte 1 0
## santa teresita 242 12
## valle del lili 0 0
##
## , , = Zona Oriente
##
##
## Apartamento Casa
## acopi 0 0
## aguacatal 0 0
## ciudad jardín 0 0
## el caney 0 0
## el ingenio 0 0
## el limonar 0 0
## el refugio 0 0
## la flora 0 0
## la hacienda 0 0
## los cristales 0 0
## normandía 0 0
## pance 1 0
## prados del norte 0 0
## santa teresita 0 0
## valle del lili 0 0
##
## , , = Zona Sur
##
##
## Apartamento Casa
## acopi 1 0
## aguacatal 1 0
## ciudad jardín 218 295
## el caney 124 84
## el ingenio 128 74
## el limonar 59 76
## el refugio 77 43
## la flora 1 0
## la hacienda 108 56
## los cristales 0 0
## normandía 1 0
## pance 205 203
## prados del norte 0 0
## santa teresita 6 0
## valle del lili 837 167
ggplot(vivienda,
aes(preciom)) +
geom_histogram(bins = 30) +
theme_minimal()
## Warning: Removed 2 rows containing non-finite outside the scale range
## (`stat_bin()`).
Distribución de precios
ggplot(vivienda,
aes(zona, preciom)) +
geom_boxplot() +
coord_flip()
## Warning: Removed 2 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
Precio por zona
ggplot(vivienda,
aes(longitud, latitud,
color = preciom)) +
geom_point(alpha = 0.5) +
scale_color_viridis_c() +
theme_minimal()
## Warning: Removed 3 rows containing missing values or values outside the scale range
## (`geom_point()`).
Mapa simple con coordenadas
vivienda$precio_m2 <- vivienda$preciom / vivienda$areaconst
aggregate(precio_m2 ~ zona, data = vivienda, mean)
## zona precio_m2
## 1 Zona Centro 1.745886
## 2 Zona Norte 2.448327
## 3 Zona Oeste 3.644159
## 4 Zona Oriente 1.442864
## 5 Zona Sur 2.720418
aggregate(preciom ~ zona, data = vivienda, mean)
## zona preciom
## 1 Zona Centro 309.6935
## 2 Zona Norte 345.6083
## 3 Zona Oeste 677.5801
## 4 Zona Oriente 228.5299
## 5 Zona Sur 426.5184
aggregate(areaconst ~ zona, data = vivienda, mean)
## zona areaconst
## 1 Zona Centro 194.0424
## 2 Zona Norte 161.1222
## 3 Zona Oeste 196.3992
## 4 Zona Oriente 192.3307
## 5 Zona Sur 173.3122
Tablas relacionales donde se agrupan variables que permitan determinar relaciones y patrones relevantes. ## Conclusiones
Los dos primeros componentes principales explican el 76% de la variabilidad.
Esto significa que variables como precio, área construida, parqueaderos y baños concentran la mayor parte de la información relevante para entender el mercado. En la práctica, la empresa puede simplificar sus modelos de valoración priorizando estas características.
Se identificaron tres grupos principales de vivienda:
Grupo 1: Casas grandes de precio medio en estratos medios.
Grupo 2: Viviendas de lujo, con más parqueaderos y ubicadas en estratos altos.
Grupo 3: Viviendas pequeñas y económicas, con menor área y menos habitaciones. Esta segmentación permite diseñar estrategias diferenciadas de venta y marketing según el perfil socioeconómico.
En el Norte y Oeste predominan los apartamentos, asociados a barrios como La Flora, Prados del Norte y Normandía.
En el Sur se observa mayor diversidad: barrios de casas tradicionales (Ciudad Jardín, Pance) y zonas de alta densidad de apartamentos (Valle del Lili).
El Centro y Oriente tienen baja representatividad en la oferta. Esto evidencia que la tipología de vivienda está fuertemente ligada a la zona, lo cual es clave para orientar inversiones.
Optimizar inversión: Priorizar proyectos en zonas con alta demanda de apartamentos (Norte y Oeste) y diversificar en el Sur según el perfil del barrio.
Segmentar campañas de marketing: Dirigir mensajes diferenciados para viviendas de lujo vs. viviendas económicas.
Valorar atributos clave: Precio, área construida y número de baños/parqueaderos deben ser los principales factores en la fijación de precios.
Explorar oportunidades en zonas subrepresentadas: El Centro y Oriente pueden ser nichos de inversión si se desarrollan proyectos innovadores que respondan a la baja oferta actual.