knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(comment = "")
library(readxl)
library(dplyr) # <- Esta línea es necesaria
vivienda <- read_excel("vivienda.xlsx")
glimpse(vivienda)
Rows: 8,322
Columns: 13
$ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
$ zona <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
$ piso <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
$ 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, NA, 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…
$ barrio <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
$ longitud <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
$ latitud <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
se convierte la variable piso en formato numero por que esta en formato texto
# Convertir 'piso' a numérico (R interpretará caracteres no convertibles como NA)
vivienda$piso <- as.numeric(vivienda$piso)
convertir la varibale estrato a categorica factor
# Convertir 'estrato' a factor ordenado (categórico)
vivienda$estrato <- factor(vivienda$estrato,
levels = sort(unique(na.omit(vivienda$estrato))),
ordered = TRUE)
str(vivienda$piso)
num [1:8322] NA NA NA 2 1 1 1 1 2 2 ...
str(vivienda$estrato)
Ord.factor w/ 4 levels "3"<"4"<"5"<"6": 1 1 1 2 3 3 2 3 3 3 ...
se guarda la base de datos modificada
# Guardar la base modificada con otro nombre
vivienda_1 <- vivienda
head(vivienda_1, 20)
# A tibble: 20 × 13
id zona piso estrato preciom areaconst parqueaderos banios habitaciones
<dbl> <chr> <dbl> <ord> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1147 Zona … NA 3 250 70 1 3 6
2 1169 Zona … NA 3 320 120 1 2 3
3 1350 Zona … NA 3 350 220 2 2 4
4 5992 Zona … 2 4 400 280 3 5 3
5 1212 Zona … 1 5 260 90 1 2 3
6 1724 Zona … 1 5 240 87 1 3 3
7 2326 Zona … 1 4 220 52 2 2 3
8 4386 Zona … 1 5 310 137 2 3 4
9 1209 Zona … 2 5 320 150 2 4 6
10 1592 Zona … 2 5 780 380 2 3 3
11 4057 Zona … 2 6 750 445 NA 7 6
12 4460 Zona … 2 4 625 355 3 5 5
13 6081 Zona … 2 5 750 237 2 6 6
14 7497 Zona … 2 6 520 98 2 2 2
15 7824 Zona … 2 4 600 160 1 4 5
16 7987 Zona … 2 5 420 200 4 4 5
17 3495 Zona … 3 5 490 118 2 4 4
18 5424 Zona … 3 4 320 108 2 3 3
19 6271 Zona … 3 5 385 103 2 2 3
20 6857 Zona … 3 3 100 49 NA 1 2
# ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
se verifica que las categorias de la varibale son unicas
head(unique(vivienda_1$barrio), 5)
[1] "20 de julio" "3 de julio" "acopi" "agua blanca" "aguacatal"
se identifica los valores faltantes
vivienda_2 <- vivienda_1
colSums(is.na(vivienda_2))
id zona piso estrato preciom areaconst
3 3 2638 3 2 3
parqueaderos banios habitaciones tipo barrio longitud
1605 3 3 3 3 3
latitud
3
se eliminan las filas con valores faltantes en la columna id y ya que son pocos ( 3)
# Eliminar solo las filas donde la columna 'id' tiene NA
vivienda_3 <- vivienda_2 %>% filter(!is.na(id))
nrow(vivienda_3)
[1] 8319
se calculan las estadisticas descriptivas para varibales cuantitativas.
# 1. Eliminar la columna 'id'
vivienda_1_sin_id <- vivienda_3 %>% select(-id)
# 2. Identificar variables cuantitativas (numéricas)
vars_cuantitativas <- vivienda_1_sin_id %>% select(where(is.numeric))
# 3. Identificar variables cualitativas (factor u ordinal o carácter)
vars_cualitativas <- vivienda_1_sin_id %>% select(where(~ is.factor(.) || is.character(.)))
# 4. Estadísticas descriptivas para cuantitativas
summary(vars_cuantitativas)
piso preciom areaconst parqueaderos
Min. : 1.000 Min. : 58.0 Min. : 30.0 Min. : 1.000
1st Qu.: 2.000 1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 1.000
Median : 3.000 Median : 330.0 Median : 123.0 Median : 2.000
Mean : 3.771 Mean : 433.9 Mean : 174.9 Mean : 1.835
3rd Qu.: 5.000 3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 2.000
Max. :12.000 Max. :1999.0 Max. :1745.0 Max. :10.000
NA's :2635 NA's :1602
banios habitaciones longitud latitud
Min. : 0.000 Min. : 0.000 Min. :-76.59 Min. :3.333
1st Qu.: 2.000 1st Qu.: 3.000 1st Qu.:-76.54 1st Qu.:3.381
Median : 3.000 Median : 3.000 Median :-76.53 Median :3.416
Mean : 3.111 Mean : 3.605 Mean :-76.53 Mean :3.418
3rd Qu.: 4.000 3rd Qu.: 4.000 3rd Qu.:-76.52 3rd Qu.:3.452
Max. :10.000 Max. :10.000 Max. :-76.46 Max. :3.498
# 5. Estadísticas descriptivas para cualitativas
summary(vars_cualitativas)
zona estrato tipo barrio
Length:8319 3:1453 Length:8319 Length:8319
Class :character 4:2129 Class :character Class :character
Mode :character 5:2750 Mode :character Mode :character
6:1987
como ya se calculaorn las estadisticas descriptivas se prodece aplicar un metodo de imputacion reemplazando los valores faltantes en las variables categoricas con la moda y en las varibales numericas con la mediana.
id_col <- vivienda_3 %>% select(id)
# 2. Separar variables cuantitativas y cualitativas (excluyendo id)
vars_cuantitativas <- vivienda_3 %>% select(where(is.numeric), -id)
vars_cualitativas <- vivienda_3 %>% select(where(~ is.factor(.) || is.character(.)))
# 3. Función para imputar con mediana
imputar_mediana <- function(x) {
x[is.na(x)] <- median(x, na.rm = TRUE)
return(x)
}
# 4. Función para imputar con moda
moda <- function(x) {
ux <- na.omit(unique(x))
ux[which.max(tabulate(match(x, ux)))]
}
imputar_moda <- function(x) {
x[is.na(x)] <- moda(x)
return(x)
}
# 5. Imputar
vars_cuantitativas_imputadas <- vars_cuantitativas %>% mutate(across(everything(), imputar_mediana))
vars_cualitativas_imputadas <- vars_cualitativas %>% mutate(across(everything(), imputar_moda))
# 6. Volver a unir todo (id + categóricas + numéricas)
vivienda_4 <- bind_cols(id_col, vars_cualitativas_imputadas, vars_cuantitativas_imputadas)
# Ver estructura final
glimpse(vivienda_4)
Rows: 8,319
Columns: 13
$ id <dbl> 1147, 1169, 1350, 5992, 1212, 1724, 2326, 4386, 1209, 159…
$ zona <chr> "Zona Oriente", "Zona Oriente", "Zona Oriente", "Zona Sur…
$ estrato <ord> 3, 3, 3, 4, 5, 5, 4, 5, 5, 5, 6, 4, 5, 6, 4, 5, 5, 4, 5, …
$ tipo <chr> "Casa", "Casa", "Casa", "Casa", "Apartamento", "Apartamen…
$ barrio <chr> "20 de julio", "20 de julio", "20 de julio", "3 de julio"…
$ piso <dbl> 3, 3, 3, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, …
$ 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, 2, 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, …
$ longitud <dbl> -76.51168, -76.51237, -76.51537, -76.54000, -76.51350, -7…
$ latitud <dbl> 3.43382, 3.43369, 3.43566, 3.43500, 3.45891, 3.36971, 3.4…
colSums(is.na(vivienda_4))
id zona estrato tipo barrio piso
0 0 0 0 0 0
preciom areaconst parqueaderos banios habitaciones longitud
0 0 0 0 0 0
latitud
0
el dataset ya no tiene valores faltantes en ninguna de sus columnas.
# 1. Seleccionar solo las variables numéricas, excluyendo 'id'
vivienda_cuantitativas <- vivienda_4 %>%
select(where(is.numeric)) %>%
select(-id)
# 2. Convertir a formato largo para ggplot2
vivienda_long <- vivienda_cuantitativas %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")
# 3. Crear el boxplot separado por variable
ggplot(vivienda_long, aes(x = Variable, y = Valor, fill = Variable)) +
geom_boxplot() +
facet_wrap(~ Variable, scales = "free") + # 🔹 separar cada boxplot
theme_minimal() +
labs(title = "Boxplots Individuales de Variables Cuantitativas",
x = "Variable",
y = "Valor") +
theme(
axis.text.x = element_blank(), # 🔹 ocultar texto en eje X (ya está en el facet)
legend.position = "none",
plot.title = element_text(hjust = 0.5) # Centrar título
)
se puedene videnciar que si se puede visualizar que si hay valores
atipicos por lo tanto se va aa dar tratamiento a los valores
atipicos.
Para dar tratamiento a los valores atípicos, se utiliza el método de capping o Winsorización, que consiste en limitar los valores extremos de las variables cuantitativas a determinados percentiles (en este caso, los percentiles 5 y 95). De esta manera, se atenúa el efecto de los valores atípicos sin eliminarlos completamente, reemplazándolos por los valores límites definidos, lo cual permite conservar la estructura general de los datos y minimizar el sesgo que podrían generar en los análisis estadísticos.
# Cargar el paquete si vas a usar Winsorize
# install.packages("DescTools")
library(DescTools)
Warning: package 'DescTools' was built under R version 4.4.3
# Crear una copia del data frame para no modificar el original
vivienda_5 <- vivienda_4
# Aplicar capping del 5% y 95% a cada variable numérica
numeric_vars <- sapply(vivienda_5, is.numeric)
vivienda_5[numeric_vars] <- lapply(vivienda_5[numeric_vars], function(x) {
p5 <- quantile(x, 0.10, na.rm = TRUE)
p95 <- quantile(x, 0.90, na.rm = TRUE)
x[x < p5] <- p5
x[x > p95] <- p95
return(x)
})
Después de aplicar el método de capping, se procede a graficar los boxplots con el fin de verificar que los valores atípicos han sido controlados de manera adecuada
# Eliminar 'id' si es numérica y está en el conjunto
vars_sin_id <- names(vivienda_5)[sapply(vivienda_5, is.numeric) & names(vivienda_5) != "id"]
# Formato largo solo con variables numéricas sin 'id'
vivienda_long <- pivot_longer(
vivienda_5,
cols = all_of(vars_sin_id),
names_to = "variable",
values_to = "valor"
)
# Graficar boxplots uno por uno
for (var in unique(vivienda_long$variable)) {
p <- ggplot(
vivienda_long %>% filter(variable == var),
aes(x = variable, y = valor, fill = variable)
) +
geom_boxplot() +
theme_minimal() +
labs(
title = paste("Boxplot de", var, "con capping"),
x = "Variable",
y = "Valor"
) +
theme(
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 14)
)
print(p) # muestra cada gráfico individual
}
se visualiza en el grafico que se le dio tratmaiento alos valores atipicos.
vivienda_5[vivienda_5$id %in% vivienda_5$id[duplicated(vivienda_5$id)], ]
# A tibble: 1,664 × 13
id zona estrato tipo barrio piso preciom areaconst parqueaderos banios
<dbl> <chr> <ord> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 7487. Zona … 6 Apar… acopi 2 520 98 2 2
2 7487. Zona … 4 Casa acopi 2 600 160 1 4
3 7487. Zona … 5 Casa acopi 2 420 200 3 4
4 833. Zona … 6 Apar… acopi 5 820 350 1 4
5 833. Zona … 3 Casa acopi 3 230 160 2 2
6 833. Zona … 5 Apar… acopi 3 430 105 2 3
7 833. Zona … 3 Casa acopi 3 190 350 2 2
8 833. Zona … 3 Casa acopi 3 180 120 2 3
9 833. Zona … 3 Casa acopi 3 500 210 2 5
10 833. Zona … 3 Apar… acopi 3 199 176 2 2
# ℹ 1,654 more rows
# ℹ 3 more variables: habitaciones <dbl>, longitud <dbl>, latitud <dbl>
se puede observar que la base de datos tiene id repetidos y esot puede ser inconsistencia a la base de datos
paso 2 analisis de componentes principales
Estandarizacion
# PCA solo con variables numéricas (sin 'id')
vars_sin_id <- names(vivienda_5)[sapply(vivienda_5, is.numeric) & names(vivienda_5) != "id"]
pca_res <- PCA(vivienda_5[vars_sin_id], graph = FALSE)
ELECCION DEL NUMERO DE COMPONENTES PRINCIPALES
# --- 1. Scree plot ---
fviz_eig(pca_res,
addlabels = TRUE,
barfill = "#56B4E9",
barcolor = "#1F78B4",
linecolor = "#E69F00") +
labs(title = "Varianza explicada por componente",
x = "Componentes principales",
y = "Porcentaje de varianza explicada") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5)) # Centrar título
el grafico muestra la varianza exolicada por cada componente principal en un analisis de componentes principales pca, evidencia que el primer componente explica del 44 porciento de la variabilidad de los datos, seguido por el segundo con 15.5 porciento y el tercero con 12.7 porciento a partir del cuarto componente 9.7 porciento la contribucion disminuye progresivamente, llegando a valores al 5 porciento desde el sexto en adelante, lo que sugiere que la mayor parte de la informacion se concentra en los tres primero componentes 72.2 porciento acumulado y que los restantes variabilidad marginal por que lo que podria descartarse para reducir la dimencionalida dy perdida significaiva de la informacion…
# --- 2. Biplot profesional ---
fviz_pca_var(pca_res,
col.var = "contrib",
gradient.cols = c("red", "#56B4E9", "#009E73"),
repel = TRUE,
arrowsize = 0.5,
labelsize = 4,
title = "Biplot - Variables") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5)) # Centrar título
En este plano, variables como precio, parqueaderos, baños, areaconst y
habitaciones se alinean fuertemente con Dim1, indicando que este
componente está asociado principalmente a características físicas y de
valor de las propiedades; en cambio, longitud y latitud se relacionan
con Dim2, reflejando una dimensión geográfica. La variable piso tiene
una contribución moderada y se proyecta en sentido opuesto a las
características de tamaño y precio, sugiriendo una relación inversa. La
intensidad y longitud de las flechas representan la importancia de cada
variable en la construcción de los componentes, destacándose precio,
areaconst y longitud como las de mayor contribución.
paso 3: Analisis de conglomerados
Estandarizacion
# Escalar todas las columnas numéricas (ya limpias)
# Seleccionar solo variables numéricas
vivienda_num <- dplyr::select_if(vivienda_5, is.numeric)
# Escalar
vivienda_scaled <- scale(vivienda_num)
Metodo del del codo
# Método del codo con título centrado
fviz_nbclust(vivienda_scaled, kmeans, method = "wss") +
ggtitle("Método del Codo") +
theme(plot.title = element_text(hjust = 0.5))
la grafica muestra una fuerte disminucion de la varianza interna hasta k igual a dos, junto a partir del cual la reduccion e mas suave, el numero optimo de conglomerados para el analisis es 2.
fviz_nbclust(vivienda_scaled, kmeans, method = "silhouette") +
ggtitle("Optimal Number of Clusters - Silhouette Method") +
theme(plot.title = element_text(hjust = 0.5))
la grafica muestra, del valor mas alto del ancho promedio de silueta que se alcanza en k igaul a 2 lo que indica que 2 conglomerados producen la mejor separacion y coversion interna entre grupos en estos datos.
set.seed(123)
km_res <- kmeans(vivienda_scaled, centers = 2, nstart = 25)
# Ver distribución de observaciones
table(km_res$cluster)
1 2
4977 3342
el resultado indica que el algoritmo k-reans agrupo los datos en dos cluster con 4977 observaciones en el primero y 3342 en el segundo, mostrando que el primer grupo es mas numeroso.
fviz_cluster(km_res, data = vivienda_scaled,
geom = "point",
ellipse.type = "convex",
palette = c("green", "red")) # naranja y azul
el grafico de dispercion muestra informacion en dos cluster: el cluster 1 en verde se concentra en valores negativos de la primera dimension mientras que el cluster 2 el rojo ocupa valores positivos la frontera entre ambos grupos esta claramente delimitada lo que indica una buena diferenciacion segun las dos primeras dimensiones principales, que explican en conjunto en el 61.8 porciento d ela variabilidad de los datos.
aggregate(vivienda_5[ , sapply(vivienda_5, is.numeric)],
by = list(Cluster = km_res$cluster),
mean)
Cluster id piso preciom areaconst parqueaderos banios
1 1 3387.364 3.531846 254.2449 99.11312 1.444043 2.373518
2 2 5310.632 3.027229 619.6864 251.44524 2.209156 4.152902
habitaciones longitud latitud
1 3.004018 -76.52449 3.419481
2 4.059844 -76.53635 3.415958
Este resultado muestra los valores promedio de las variables para cada uno de los dos clusters formados. El Cluster 1 agrupa propiedades con menor precio promedio aproximadamente 254 millones, menor área construida aproximadamente 99 m², menos baños y parqueaderos, así como menor número de habitaciones, mientras que el Cluster 2 agrupa propiedades con precios más altos de apróximadamente 620 millones, áreas construidas más amplias apróximadamente 251 m² y más comodidades (baños, parqueaderos y habitaciones). En resumen, los clusters diferencian claramente viviendas más pequeñas y económicas frente a viviendas más grandes y costosas.
# Añadir columna de cluster a vivienda_5
vivienda_5$cluster <- km_res$cluster
ggplot(vivienda_5, aes(x = zona, fill = factor(cluster))) +
geom_bar(position = "dodge") +
labs(fill = "Cluster", x = "Zona", y = "Cantidad de propiedades") +
scale_fill_manual(values = c("#E69F00", "#56B4E9")) + # Colores personalizados
theme_minimal()
el cluster amarillo predomina claramente en la zona sur y zona norte, mientras que el cluster dos azul tiene mayor presenta en la zona oete y tambien aparece en la zona sur, aunque en menor cantidad que el cluster 1. en la zona centro y zona oriente, ambos clsuter tienen baja representacion esto indica que las zonas geograficas tienen uan fuerte influencia en la pertencia al cluster, con el cluster 1 concentrado en zonas con el mayor numero de propiedades en general y el cluster dos mas equilibrada su distribucion destacando en la zona oeste
Paso 4: Analisis de Correspondencia se convierte en tipo factor la variable tipo zona:
vivienda_5$tipo <- as.factor(vivienda_5$tipo)
vivienda_5$zona <- as.factor(vivienda_5$zona)
se contruye una tabla cruzada con las vairbales involucradas en el analisis
library(FactoMineR)
tabla <- table(vivienda_5$tipo, vivienda_5$zona)
colnames(tabla) <- c("Zona Centro", "Zona Norte", "Zona Oeste", "Zona Oriente", "Zona Sur" )
tabla
Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
Apartamento 24 1198 1029 62 2787
Casa 100 722 169 289 1939
en la zona norte oeste y suer predonminan los apartamentos, con cantindades significativamente mayores que en las casas lo que sugiere una amyor densidad residencial o urbanizacion vertical en estas areas en contraste, la zona centro y oriente presentan mas casas que apartamentos indicando un patron de vivienda ams tradicional o de menor densidad. Estos dos reflejan diferencias claras en la distribucion del tipo de vivienda segun la zona, lo que puede ser util para planificaicon urbana y analisis demograficos.
chisq.test(tabla)
Pearson's Chi-squared test
data: tabla
X-squared = 690.93, df = 4, p-value < 2.2e-16
este valor de la prueba del chi cuadrado es de 690.93 con cuatro graados de libertad y un p-value practicamente de cero lo que indica que existe una asocion significativo entre la socidad geografica y el tipo de vivienda esto significa que la distribucion de casa y apartamentos varia segun la zona rechanzando la hipotesis de estas variables sean independientes
se realiza el analisis de correspondencia que cosniste en estimar las coordenadas, para cada uno de los niveles de ambas variables
resultados_ac <- CA(tabla)
para medir el grado de representatividad del proceso calcula los valores de la varianza acumulada para ello los valores propios de los valores de la matriz de discrepancia
valores_prop <-resultados_ac$eig ; valores_prop
eigenvalue percentage of variance cumulative percentage of variance
dim 1 0.08305442 100 100
library(pheatmap)
Warning: package 'pheatmap' was built under R version 4.4.3
# Asegúrate de que la tabla sea matriz
tabla <- as.matrix(tabla)
pheatmap(tabla,
cluster_rows = FALSE, # No agrupa filas
cluster_cols = FALSE, # No agrupa columnas
display_numbers = TRUE, # Muestra los números en las celdas
number_format = "%.0f", # Números sin decimales
number_color = "black", # Color de los números
color = colorRampPalette(c("white", "lightblue", "darkblue"))(100), # Paleta
fontsize = 12, # Tamaño de fuente general
fontsize_number = 10, # Tamaño de números en celdas
border_color = "grey60", # Bordes más discretos
main = "Mapa de calor de la tabla de contingencia" # Título
)
se observa que la mayor concentracion de viviendas tanto apartamentos como casa esta en la zona sur; especialmente lo apartamentos cuya cifra 2787 destaca sobre el resto y se presenta con el color azul intenso. la zona centro por el contrario tien una cantidad singificativa menor de viviendas de ambos tipos zona centro, evidenciada por el color blanco en las demas zonas la distribucion es intermedia, con una mayor presencia de apartamentos respecto a casas, excepto en la zona oritnete donde hay mas casas que apartamentos. esta visualizaciion permite identificar las zonas con mayor desarrollo habitacional y las referencias de tipo de vivienda en cada area lo que puede hacer util para el analisis urbanos o decisiones de mercado inmobiliario.