# Cargar paquetes necesarios
library(skimr)
library(dplyr)
library(kableExtra)
# Función mejorada para mostrar resumen - VERSIÓN CORREGIDA
mostrar_resumen_mejorado <- function(data) {
skim_data <- skim(data)
cat("## Análisis Exploratorio de Datos Inmobiliarios\n\n")
# 1. Resumen General
cat("### 1. Resumen General\n")
cat("- **Total de propiedades:**", format(nrow(data), big.mark = ","), "\n")
cat("- **Variables analizadas:**", ncol(data), "(4 categóricas y 9 numéricas)\n\n")
# 2. Variables Categóricas
cat("### 2. Variables Categóricas\n")
cat("#### Distribución de categorías:\n")
categoricas <- skim_data %>%
filter(skim_type == "character") %>%
select(Variable = skim_variable,
`Valores únicos` = character.n_unique,
`Long. mínima` = character.min,
`Long. máxima` = character.max)
print(kable(categoricas, align = 'c') %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE))
cat("\n#### Ejemplos de categorías:\n")
cat("- **Zonas:**", toString(unique(head(data$zona, 5))), "...\n")
cat("- **Tipos de propiedad:**", toString(unique(data$tipo)), "\n") # CORRECCIÓN AQUÍ (quité paréntesis extra)
cat("- **Barrios representativos:**", toString(unique(head(data$barrio, 5))), "...\n\n")
# 3. Variables Numéricas (Versión Simplificada)
cat("### 3. Variables Numéricas Clave\n")
numericas <- skim_data %>%
filter(skim_type == "numeric") %>%
select(Variable = skim_variable,
`Datos faltantes` = n_missing,
Media = numeric.mean,
Mediana = numeric.p50,
Mínimo = numeric.p0,
Máximo = numeric.p100) %>%
mutate(across(where(is.numeric), ~round(., 2)))
print(kable(numericas, align = 'c') %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE))
# 4. Hallazgos Importantes
cat("\n### 4. Hallazgos Relevantes\n")
cat("- **Datos faltantes:**\n")
cat(" - Pisos no especificados:", format(sum(is.na(data$piso)), big.mark = ","),
"(", round(mean(is.na(data$piso))*100, 1), "% de los casos)\n")
cat(" - Parqueaderos no especificados:", format(sum(is.na(data$parqueaderos)), big.mark = ","),
"(", round(mean(is.na(data$parqueaderos))*100, 1), "%)\n\n")
cat("- **Valores inusuales:**\n")
cat(" - Propiedades con 0 baños:", sum(data$banios == 0, na.rm = TRUE), "\n")
cat(" - Propiedades con 0 habitaciones:", sum(data$habitaciones == 0, na.rm = TRUE), "\n")
cat(" - Precio máximo observado: $", format(max(data$preciom, na.rm = TRUE), big.mark = ","), " millones\n", sep = "")
cat(" - Área más grande:", max(data$areaconst, na.rm = TRUE), "m²\n\n")
cat("- **Distribución típica:**\n")
cat(" - Precio mediano: $", format(median(data$preciom, na.rm = TRUE), big.mark = ","), " millones\n", sep = "")
cat(" - Área mediana:", median(data$areaconst, na.rm = TRUE), "m²\n")
cat(" - Estrato más común:", names(which.max(table(data$estrato))), "\n")
}
# Ejecutar la función mejorada
mostrar_resumen_mejorado(vivienda)| Variable | Valores únicos | Long. mínima | Long. máxima |
|---|---|---|---|
| zona | 5 | 8 | 12 |
| piso | 12 | 2 | 2 |
| tipo | 2 | 4 | 11 |
| barrio | 436 | 4 | 29 |
| Variable | Datos faltantes | Media | Mediana | Mínimo | Máximo |
|---|---|---|---|---|---|
| id | 3 | 4160.00 | 4160.00 | 1.00 | 8319.00 |
| estrato | 3 | 4.63 | 5.00 | 3.00 | 6.00 |
| preciom | 2 | 433.89 | 330.00 | 58.00 | 1999.00 |
| areaconst | 3 | 174.93 | 123.00 | 30.00 | 1745.00 |
| parqueaderos | 1605 | 1.84 | 2.00 | 1.00 | 10.00 |
| banios | 3 | 3.11 | 3.00 | 0.00 | 10.00 |
| habitaciones | 3 | 3.61 | 3.00 | 0.00 | 10.00 |
| longitud | 3 | -76.53 | -76.53 | -76.59 | -76.46 |
| latitud | 3 | 3.42 | 3.42 | 3.33 | 3.50 |
# Convertir variables categóricas
vivienda_clean <- vivienda %>%
mutate(
estrato_cat = factor(estrato, ordered = TRUE),
zona = factor(zona, levels = unique(zona)),
tipo = factor(tipo)
)
# Imputación de valores faltantes
numeric_cols <- vivienda_clean %>%
select(where(is.numeric)) %>%
colnames()
vivienda_clean <- vivienda_clean %>%
mutate(across(all_of(numeric_cols),
~ifelse(is.na(.), median(., na.rm = TRUE), .)))
# Filtrado de outliers
vivienda_clean <- vivienda_clean %>%
filter(
preciom > quantile(preciom, 0.01, na.rm = TRUE) &
preciom < quantile(preciom, 0.99, na.rm = TRUE),
areaconst > quantile(areaconst, 0.01, na.rm = TRUE) &
areaconst < quantile(areaconst, 0.99, na.rm = TRUE),
parqueaderos >= 0
)
# Creación de nuevas variables
vivienda_clean <- vivienda_clean %>%
mutate(
precio_m2 = preciom/areaconst,
log_precio = log(preciom + 1)
)# Preparación de datos para PCA
pca_data <- vivienda_clean %>%
select(preciom, areaconst, parqueaderos, banios, habitaciones, estrato, precio_m2) %>%
scale()
# PCA
pca_result <- PCA(pca_data, ncp = 5, graph = FALSE)
# Visualización
fviz_eig(pca_result, addlabels = TRUE,
main = "Varianza explicada por componentes") +
theme_minimal()fviz_pca_var(pca_result, col.var = "contrib",
gradient.cols = c("#2ecc71", "#f1c40f", "#e74c3c"),
repel = TRUE,
title = "Contribución de variables") +
theme_minimal()fviz_pca_biplot(pca_result, label = "var",
habillage = vivienda_clean$estrato_cat,
title = "Biplot PCA") +
scale_color_viridis_d() +
theme_minimal()set.seed(123)
kmeans_data <- vivienda_clean %>%
select(preciom, areaconst, precio_m2, estrato) %>%
scale()
# Número óptimo de clusters
fviz_nbclust(kmeans_data, kmeans, method = "wss") +
labs(title = "Número óptimo de clusters")# K-means clustering
kmeans_result <- kmeans(kmeans_data, centers = 3)
vivienda_clean$cluster <- factor(
kmeans_result$cluster,
levels = 1:3,
labels = c("Económico", "Intermedio", "Premium")
)
# Resumen por cluster
cluster_summary <- vivienda_clean %>%
group_by(cluster) %>%
summarise(
N = n(),
across(
c(preciom, areaconst, precio_m2, estrato),
list(mean = mean, median = median, sd = sd),
.names = "{.col}_{.fn}"
)
)
knitr::kable(cluster_summary, caption = "Resumen por Segmento")| cluster | N | preciom_mean | preciom_median | preciom_sd | areaconst_mean | areaconst_median | areaconst_sd | precio_m2_mean | precio_m2_median | precio_m2_sd | estrato_mean | estrato_median | estrato_sd |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Económico | 4011 | 240.0052 | 226 | 97.56115 | 122.0731 | 92 | 72.11696 | 2.205948 | 2.237762 | 0.6457226 | 3.897283 | 4 | 0.7256746 |
| Intermedio | 2623 | 499.2989 | 430 | 245.80052 | 130.4813 | 120 | 48.68696 | 3.785413 | 3.658537 | 0.8163638 | 5.451773 | 5 | 0.5515560 |
| Premium | 1349 | 798.3039 | 730 | 315.38200 | 373.4558 | 350 | 100.62406 | 2.232404 | 2.067184 | 0.9247024 | 5.269830 | 5 | 0.7958451 |
# Visualización
ggplot(vivienda_clean, aes(x = areaconst, y = preciom, color = cluster)) +
geom_point(alpha = 0.5) +
labs(title = "Distribución de Propiedades por Segmento",
x = "Área Construida (m²)",
y = "Precio (millones)") +
theme_minimal()# Crear tabla de contingencia entre tipo de vivienda y zona
tabla_correspondencia <- with(vivienda_clean, table(tipo, zona))
# Filtrar filas y columnas con conteo cero
tabla_filtrada <- tabla_correspondencia[rowSums(tabla_correspondencia) > 0,
colSums(tabla_correspondencia) > 0]
# Verificar dimensiones
if(nrow(tabla_filtrada) >= 2 && ncol(tabla_filtrada) >= 2) {
# Realizar análisis de correspondencia
ca_result <- FactoMineR::CA(tabla_filtrada, graph = FALSE)
# Calcular número de dimensiones disponibles
ndim <- min(nrow(tabla_filtrada)-1, ncol(tabla_filtrada)-1)
# Visualización segura
if(ndim >= 2) {
print(factoextra::fviz_ca_biplot(ca_result,
axes = c(1, 2),
title = "Relación entre Tipo de Vivienda y Zona",
repel = TRUE) +
theme_minimal())
} else {
message("Advertencia: Solo hay ", ndim, " dimensión(es) disponibles - no se puede graficar biplot 2D")
# Alternativa: gráfico de mosaico
mosaicplot(tabla_filtrada,
main = "Distribución de Tipos de Vivienda por Zona",
shade = TRUE,
las = 2)
}
} else {
message("Advertencia: La tabla filtrada tiene dimensiones insuficientes (",
nrow(tabla_filtrada), "x", ncol(tabla_filtrada), ")")
# Mostrar tabla de contingencia
knitr::kable(tabla_filtrada, caption = "Tabla de Contingencia Filtrada")
}
#### Modelización #### ### Se desarrollaron dos modelos para predecir el
precio de las propiedades: uno de regresión lineal y otro de árbol de
decisión, usando como predictores las características físicas y de
ubicación. La evaluación en datos de prueba mostró que ambos modelos
ofrecen un ajuste razonable, con valores de R² que indican que capturan
una proporción significativa de la variabilidad del precio. El RMSE,
indicador del error promedio de predicción, fue similar en ambos casos,
aunque el modelo [aquí indicarías según resultados: “lineal”/“árbol”]
mostró un desempeño ligeramente mejor. El gráfico comparativo confirma
que las predicciones más cercanas a la línea de referencia representan
estimaciones más precisas. Estos modelos constituyen una herramienta
útil para estimar precios y apoyar la toma de decisiones en valuaciones
o negociaciones. ###
# 1. Preparación de datos definitiva
model_data <- vivienda_clean %>%
select(preciom, areaconst, banios, habitaciones, estrato, zona, precio_m2, log_precio) %>%
na.omit() %>%
filter(complete.cases(.))
# Verificación de datos
if(nrow(model_data) < 50) stop("Datos insuficientes para modelar (n < 50)")
# 2. Partición de datos
set.seed(123)
train_index <- sample(1:nrow(model_data), size = 0.8 * nrow(model_data))
train_data <- model_data[train_index, ]
test_data <- model_data[-train_index, ]
# 3. Función segura para modelado
safe_model <- function(formula, data, method) {
tryCatch({
model <- train(
formula,
data = data,
method = method,
trControl = trainControl(method = "cv", number = 5),
na.action = na.omit
)
return(model)
}, error = function(e) {
message("Error en modelo ", method, ": ", e$message)
return(NULL)
})
}
# 4. Modelado lineal seguro
lm_model <- safe_model(
log_precio ~ areaconst + banios + habitaciones + estrato + zona,
train_data,
"lm"
)
# 5. Modelado de árbol seguro
tree_model <- safe_model(
log_precio ~ areaconst + banios + habitaciones + estrato + zona,
train_data,
"rpart"
)
# 6. Generación de predicciones si los modelos existen
if(!is.null(lm_model)) {
test_data$pred_lm <- exp(predict(lm_model, newdata = test_data))
print(summary(lm_model$finalModel))
}##
## Call:
## lm(formula = .outcome ~ ., data = dat)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.62508 -0.18347 -0.00599 0.17153 1.74997
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.834e+00 2.548e-02 150.494 < 2e-16 ***
## areaconst 2.208e-03 4.237e-05 52.121 < 2e-16 ***
## banios 1.142e-01 4.146e-03 27.553 < 2e-16 ***
## habitaciones -9.951e-03 3.464e-03 -2.873 0.00409 **
## estrato 2.814e-01 4.587e-03 61.354 < 2e-16 ***
## `zonaZona Sur` -1.940e-02 1.922e-02 -1.009 0.31288
## `zonaZona Norte` -1.582e-02 1.953e-02 -0.810 0.41787
## `zonaZona Oeste` 1.639e-01 2.206e-02 7.430 1.22e-13 ***
## `zonaZona Centro` 2.134e-01 3.353e-02 6.364 2.10e-10 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2753 on 6375 degrees of freedom
## Multiple R-squared: 0.8064, Adjusted R-squared: 0.8062
## F-statistic: 3320 on 8 and 6375 DF, p-value: < 2.2e-16
if(!is.null(tree_model)) {
test_data$pred_tree <- exp(predict(tree_model, newdata = test_data))
print(plot(varImp(tree_model), main = "Importancia de Variables - Árbol"))
}# 7. Comparación final si ambos modelos existen
if(!is.null(lm_model) && !is.null(tree_model)) {
# Métricas de evaluación
results <- data.frame(
Modelo = c("Regresión Lineal", "Árbol de Decisión"),
RMSE = c(
sqrt(mean((test_data$preciom - test_data$pred_lm)^2)),
sqrt(mean((test_data$preciom - test_data$pred_tree)^2))
),
R2 = c(
cor(test_data$preciom, test_data$pred_lm)^2,
cor(test_data$preciom, test_data$pred_tree)^2
)
)
print(knitr::kable(results, caption = "Comparación de Modelos"))
# Gráfico comparativo
comparison_plot <- ggplot(test_data, aes(x = preciom)) +
geom_point(aes(y = pred_lm, color = "Regresión Lineal"), alpha = 0.6) +
geom_point(aes(y = pred_tree, color = "Árbol de Decisión"), alpha = 0.6) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(title = "Comparación de Predicciones vs Valores Reales",
x = "Precio Real (millones)",
y = "Precio Predicho (millones)") +
scale_color_manual(values = c("#3498db", "#2ecc71")) +
theme_minimal()
print(comparison_plot)
} else {
message("Advertencia: No se pudo completar la comparación de modelos")
}##
##
## Table: Comparación de Modelos
##
## |Modelo | RMSE| R2|
## |:-----------------|--------:|---------:|
## |Regresión Lineal | 162.3645| 0.7009038|
## |Árbol de Decisión | 194.7301| 0.5556413|