La empresa C&A (Casas y Apartamentos) fue fundada en la ciudad de Cali hace 10 años. En la actualidad las ventas de bienes raíces en la ciudad han visto una reducción a causa de condiciones del mercado que desincentivan la compra ce inmuebles en la ciudad. Se recibió una solicitud de una empresa para la compra de dos viviendas resaltando la siguiente información:
| Características | Vivienda 1 | Vivienda 2 |
|---|---|---|
| Tipo | Casa | Apartamento |
| Área construida (m²) | 200 | 300 |
| Parqueaderos | 1 | 3 |
| Baños | 2 | 3 |
| Habitaciones | 4 | 5 |
| Estrato | 4 o 5 | 5 o 6 |
| Zona | Norte | Sur |
| Crédito preaprobado | 350 millones | 850 millones |
#install.packages("devtools") # solo la primera vez
#devtools::install_github("centromagis/paqueteMODELOS", force =TRUE)
library(paqueteMODELOS)
data("vivienda")
Para dar solución a esta problemática se hará uso de un modelo de regresión Lineal Múltiple para las casas de la Zona Norte y para los apartamentos de la Zona Sur con el objetivo de elaborar recomendaciones de posibles opciones sobre las dos ofertas recibidas.
En primer lugar se identificaron las variables disponibles en el conjunto de datos expresadas en la siguiente tabla :
Descripción de las Variables
| Variable | Descripción |
|---|---|
| zona | Ubicación de la vivienda: Zona Centro, Zona Norte, … |
| piso | Piso que ocupa la vivienda: primer piso, segundo piso, … |
| estrato | Estrato socio-económico: 3, 4, 5, 6 |
| preciom | Precio de la vivienda en millones de pesos |
| areaconst | Área construida |
| parqueaderos | Número de parqueaderos |
| banios | Número de baños |
| habitaciones | Número de habitaciones |
| tipo | Tipo de vivienda: Casa, Apartamento |
| barrio | Barrio de ubicación de la vivienda |
| longitud | Coordenada geográfica (longitud) |
| latitud | Coordenada geográfica (latitud) |
Primero, se identificaron los datos erróneos y los valores faltantes en el conjunto de datos, encontrando un total de tres valores incorrectos, así como 2.638 valores faltantes en la variable “piso” y 1.605 en “parqueaderos”. Además, se detectó la presencia de registros con inconsistencias, como viviendas sin habitaciones, baños ni parqueaderos.
library(tibble)
library(knitr)
library(kableExtra)
# Crear tabla sin nombres duplicados
df_info <- tibble(
Columna = names(vivienda),
Tipo = sapply(vivienda, function(x) paste(class(x), collapse = ", ")),
DatosFaltantes = colSums(is.na(vivienda))
)
# Mostrar tabla con kable
kable(df_info, "html") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Columna | Tipo | DatosFaltantes |
|---|---|---|
| id | numeric | 3 |
| zona | character | 3 |
| piso | character | 2638 |
| estrato | numeric | 3 |
| preciom | numeric | 2 |
| areaconst | numeric | 3 |
| parqueaderos | numeric | 1605 |
| banios | numeric | 3 |
| habitaciones | numeric | 3 |
| tipo | character | 3 |
| barrio | character | 3 |
| longitud | numeric | 3 |
| latitud | numeric | 3 |
Se eliminaron los valores faltantes y, mediante la librería mice, se analizaron los posibles patrones presentes en estos datos. Sin embargo, no se identificó ningún patrón específico en la distribución de los valores faltantes, lo que sugiere que estos podrían haberse producido de manera aleatoria.
# Contar los valores faltantes en cada fila
filas_con_muchos_na <- apply(vivienda, 1, function(x) sum(is.na(x)) > 3)
# Filtrar las filas que tienen 3 o menos valores faltantes
vivienda <- vivienda[!filas_con_muchos_na, ]
#install.packages("mice")
library(mice)
md.pattern(vivienda, rotate.names = TRUE)
## id zona estrato preciom areaconst banios habitaciones tipo barrio longitud
## 4808 1 1 1 1 1 1 1 1 1 1
## 1909 1 1 1 1 1 1 1 1 1 1
## 876 1 1 1 1 1 1 1 1 1 1
## 726 1 1 1 1 1 1 1 1 1 1
## 0 0 0 0 0 0 0 0 0 0
## latitud parqueaderos piso
## 4808 1 1 1 0
## 1909 1 1 0 1
## 876 1 0 1 1
## 726 1 0 0 2
## 0 1602 2635 4237
Adicionalmente, se convirtió la variable estrato en un factor para facilitar el uso del modelo de regresión lineal múltiple , permitiendo un mejor manejo de las categorías socioeconómicas y asegurando que el modelo las trate como variables categóricas en lugar de numéricas.
library(dplyr)
vivienda <- vivienda %>% mutate(estrato = as.factor(estrato))
library(dplyr)
glimpse(vivienda)
## 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…
## $ piso <chr> NA, NA, NA, "02", "01", "01", "01", "01", "02", "02", "02…
## $ estrato <fct> 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…
La imputación de los valores faltantes y la corrección de las inconsistencias se llevó a cabo mediante el uso de la moda, segmentando los datos por tipo de vivienda y estrato. Esto permitió asignar a cada valor faltante la categoría más frecuente dentro de su grupo, asegurando una imputación coherente con las características del mercado inmobiliario y manteniendo la integridad de los datos.
#Buscar la moda por tipo de vivienda y estrato y reemplazar los valores faltantes y erroneos por esta.
library(dplyr)
# Función para calcular la moda
moda <- function(x) {
ux <- unique(na.omit(x)) # Eliminar NA y obtener valores únicos
ux[which.max(tabulate(match(x, ux)))] # Devolver el valor más frecuente
}
# Convertir variables en formato númerico.
vivienda <- vivienda %>%
mutate(
piso = as.numeric(piso),
banios = as.numeric(banios)
)
# Calcular las modas por estrato y tipo
modas_calculadas <- vivienda %>%
group_by(tipo, estrato) %>%
summarise(
moda_habitaciones = moda(habitaciones),
moda_parqueaderos = moda(parqueaderos),
moda_piso = moda(piso),
moda_bano = moda(banios)
)
# Reemplazar los valores faltantes y cero en el dataframe original
vivienda <- vivienda %>%
left_join(modas_calculadas, by = c("tipo", "estrato")) %>%
mutate(
# Reemplazar NA en 'parqueaderos' y 'piso' por la moda.
parqueaderos = ifelse(is.na(parqueaderos), moda_parqueaderos, parqueaderos),
piso = ifelse(is.na(piso), moda_piso, piso),
# Reemplazar 0 en 'habitaciones' y 'banios' por la moda.
habitaciones = ifelse(habitaciones == 0, moda_habitaciones, habitaciones),
banios = ifelse(banios == 0, moda_bano, banios)
) %>%
select(-moda_habitaciones, -moda_parqueaderos, -moda_piso, -moda_bano) # Eliminar columnas temporales
En primer lugar, se realizó un filtro sobre las casas de la Zona Norte, arrojando un total de 722 registros para esta zona. Con el propósito de validar la ubicación de cada vivienda para las recomendaciones posteriores, se analizó la distribución geográfica de las propiedades mediante herramientas de visualización espacial, identificando posibles viviendas que tienen categória asignada de manera erronea
# Cargar librerías necesarias
library(dplyr)
library(kableExtra)
# Filtrar la base de datos para casas en la zona norte
base1 <- vivienda %>%
filter(tipo == "Casa", zona == "Zona Norte")
# Mostrar los primeros 3 registros
head(base1, 5) %>%
kable() %>%
kable_styling(full_width = FALSE)
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1209 | Zona Norte | 2 | 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51341 | 3.47968 |
| 1592 | Zona Norte | 2 | 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.51674 | 3.48721 |
| 4057 | Zona Norte | 2 | 6 | 750 | 445 | 2 | 7 | 6 | Casa | acopi | -76.52950 | 3.38527 |
| 4460 | Zona Norte | 2 | 4 | 625 | 355 | 3 | 5 | 5 | Casa | acopi | -76.53179 | 3.40590 |
| 6081 | Zona Norte | 2 | 5 | 750 | 237 | 2 | 6 | 6 | Casa | acopi | -76.54044 | 3.36862 |
# Contar la cantidad de registros por tipo de vivienda en base1
base1 %>%
count(tipo) %>%
kable(col.names = c("Tipo de Vivienda", "Cantidad")) %>%
kable_styling(full_width = FALSE)
| Tipo de Vivienda | Cantidad |
|---|---|
| Casa | 722 |
Como se observa en el mapa, existen algunas observaciones que no se encuentran dentro de la zona norte de la ciudad, y aunque la ciudad formalmente no tiene distinción de las distintas zonas, es pertinente realizar una revisión posterior de este aspecto al momento de generar recomendaciones para incurrir en errores al sugerir propiedades que no cumplan con los criterios geográficos establecidos por el cliente. Esta revisión permitirá asegurar la consistencia y precisión de las recomendaciones, alineándolas con las expectativas de ubicación y preferencias especificadas en la solicitud.
# Cargar librerías necesarias
library(leaflet)
# Crear el mapa con solo las ubicaciones
leaflet(base1) %>%
addTiles() %>%
addCircleMarkers(
~longitud, ~latitud,
radius = 3,
color = "blue",
fill = TRUE,
fillOpacity = 0.7
)
Se realizó el filtro de casas de la zona norte, encontrando un total de 722 observaciones, al realizar el mapa se evidencia la presencia de distintas propiedades que a pesar de estar categorizadas como “Zona Norte” se encuentran en otras zonas de la ciudad.
# Cargar librerías necesarias
library(dplyr)
library(ggplot2)
library(plotly)
library(reshape2)
# Matriz de correlación para variables numéricas
cor_data <- base1 %>%
select(preciom, areaconst, banios, habitaciones) %>%
cor(use = "complete.obs")
# Convertir la matriz de correlación en formato largo
cor_data_melted <- melt(cor_data)
En la matriz de correlación de las variables numéricas se observa una correlación positiva fuerte entre el precio y el área construida, con un coeficiente de correlación de 0.73, lo cual indica que a medida que aumenta el área construida, también tiende a incrementarse el precio de la vivienda. De igual manera, se aprecia una correlación positiva significativa entre el número de baños y el número de habitaciones, con un coeficiente de 0.6, lo que sugiere que las viviendas con más habitaciones suelen contar con un mayor número de baños.
Además, se identifican correlaciones positivas moderadas, destacándose la relación entre el precio y el número de baños, con un coeficiente de 0.56, indicando que un mayor número de baños podría asociarse con viviendas de mayor valor. Asimismo, la correlación entre el precio y el número de habitaciones, con un coeficiente de 0.37, sugiere que aunque la cantidad de habitaciones influye en el precio, su impacto es menos pronunciado en comparación con las demás variables analizadas.
# Gráfico de matriz de correlación con números encima
# Gráfico de matriz de correlación,
heatmap_plot <- ggplot(cor_data_melted, aes(Var1, Var2, fill = value)) +
geom_tile() +
geom_text(aes(label = round(value, 2)), color = "black", size = 3) +
scale_fill_gradient2(low = "green", high = "red", midpoint = 0) +
theme_minimal() +
ggtitle("Matriz de Correlación") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplotly(heatmap_plot)
Este gráfico muestra la relación entre el precio de propiedades y su área construida en metros cuadrados sugiriendo una fuerte correlación positiva entre el precio y el área construida: a medida que aumenta el área construida, el precio de la propiedad también tiende a aumentar.
scatter_plot <- ggplot(base1, aes(x = areaconst, y = preciom)) +
geom_point(aes(color = zona), alpha = 0.7) +
geom_smooth(method = "lm", col = "red") +
theme_minimal() +
labs(title = "Precio vs Área Construida",
x = "Área Construida (m²)", y = "Precio (millones)")
ggplotly(scatter_plot)
En el gráfico se observa una tendencia ascendente en los precios medianos a medida que aumenta el estrato. Esto sugiere que, en general, las propiedades en estratos socioeconómicos más altos tienden a tener precios más altos. La dispersión de los precios también parece aumentar con el estrato, lo que indica una mayor variabilidad en los precios de las propiedades en los estratos más altos.Los valores atípicos representan propiedades con precios significativamente diferentes del resto de las propiedades en su estrato, los cuales podrían ser propiedades de lujo o propiedades con características únicas que influyen en su precio.
library(plotly)
boxplot_estrato <- ggplot(base1, aes(x = as.factor(estrato), y = preciom, fill = as.factor(estrato))) +
geom_boxplot() +
theme_minimal() +
labs(title = "Distribución de Precios por Estrato",
x = "Estrato", y = "Precio")+
scale_fill_discrete(name = "Estrato")
ggplotly(boxplot_estrato)
En el Boxplot se observa que generalmente un aumento en el área construida mediana a medida que aumenta el estrato, aunque en menor medida en relación a los precios. Esto sugiere que, en promedio, las propiedades en estratos socioeconómicos más altos tienden a tener áreas construidas ligeramente mayores. Sin embargo, la superposición entre las cajas indica que existen propiedades de diferentes tamaños en todos los estratos.
La dispersión del área construida también parece aumentar con el estrato, lo que indica una mayor variabilidad en el tamaño de las propiedades en los estratos más altos. Esto podría deberse a la presencia de propiedades más grandes y lujosas en estos estrato
boxplot_estrato <- ggplot(base1, aes(x = as.factor(estrato), y = areaconst, fill = as.factor(estrato))) +
geom_boxplot() +
theme_minimal() +
labs(title = "Distribución de área por Estrato",
x = "Estrato", y = "Área m2")+
scale_fill_discrete(name = "Estrato")
ggplotly(boxplot_estrato)
El gráfico de violín muestra la distribución de precios y tamaños de propiedades en la Zona Norte. En este caso se observa una concentración de áreas entre los 100 y 250 metros cuadrados, mientras que en los precios por metro cuadrado se concentran en el rango de 1.500.000 COP y 3.000.000 COP.Es importante mencionar que en este caso los precios por metro cuadrado se encuentran en una escala de 10.000, lo cual es útil para realizar las posteriores interpretaciones.
library(ggplot2)
library(plotly)
# Gráfico de violín para 'preciom' por 'zona'
violin_preciom <- ggplot(base1, aes(x = zona, y = preciom, fill = zona)) +
geom_violin(trim = FALSE, alpha = 0.7) +
theme_minimal() +
labs(title = "Distribución de Precios por Zona (Violín)",
x = "Zona", y = "Precio") +
theme(legend.position = "none")
# Gráfico de violín para 'areaconst' por 'zona'
violin_areaconst <- ggplot(base1, aes(x = zona, y = areaconst, fill = zona)) +
geom_violin(trim = FALSE, alpha = 0.7) +
theme_minimal() +
labs(title = "Distribución de Precio y Área Construida por Zona (Violín)",
x = "Zona", y = "Área Construida (m²)") +
theme(legend.position = "none")
# Organizar los gráficos uno al lado del otro usando subplot
subplot(ggplotly(violin_preciom), ggplotly(violin_areaconst),
nrows = 1, shareX = FALSE, shareY = FALSE, titleX = TRUE, titleY = TRUE)
En primer lugar, se estimará el modelo de regresión lineal múltiple teniendo en cuenta las siguientes variables: área construida, estrato, número de cuartos, número de parqueaderos, número de baños. Este modelo tiene como objetivo principal determinar cómo estas variables influyen en el precio de las propiedades (preciom) y cuantificar la relación específica entre cada una de ellas y el precio.
Para garantizar la robustez y la capacidad de generalización del modelo a nuevos datos, se empleará un enfoque de validación cruzada con 10 folds. Este método consiste en dividir el conjunto de datos en 10 subconjuntos o particiones aproximadamente del mismo tamaño. Durante cada iteración, el modelo se entrena utilizando 9 de estos subconjuntos y se evalúa con el subconjunto restante. Este proceso se repite 10 veces, cambiando el subconjunto de validación en cada ocasión, lo que permite que cada partición se utilice tanto para el entrenamiento como para la validación. Al final, se promedian los resultados de cada iteración para obtener una métrica más confiable del rendimiento del modelo.
El objetivo final es obtener un modelo sólido y preciso que permita predecir con exactitud el precio de las propiedades basándose en las variables seleccionadas, minimizando el riesgo de sobreajuste y mejorando la capacidad de predicción en datos nuevos.
# Cargar librerías necesarias
library(dplyr)
library(caret)
# Definir número de folds para validación cruzada
set.seed(123) # Para reproducibilidad
control <- trainControl(method = "cv", number = 10) # 10-fold cross-validation
# Ajustar el modelo con validación cruzada
modelo_cv <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1,
method = "lm",
trControl = control
)
# Obtener el modelo final ajustado
modelo <- modelo_cv$finalModel
# Obtener el resumen del modelo final
#summary(modelo)
#print(modelo_cv)
library(dplyr)
library(caret)
library(knitr)
library(kableExtra)
# Obtener el resumen del modelo final
summary_modelo <- summary(modelo)
# Convertir los coeficientes en un data frame para usar kableExtra
coeficientes <- as.data.frame(summary_modelo$coefficients)
colnames(coeficientes) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
#Tabla con KableExtra
coeficientes %>%
kable("html", caption = "<center>Resumen de Coeficientes del Modelo Lineal</center>") %>%
kable_styling(
full_width = F,
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
position = "center" )
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 10.1161217 | 18.710312 | 0.5406709 | 0.5889030 |
| areaconst | 0.7875916 | 0.044162 | 17.8341611 | 0.0000000 |
| estrato4 | 78.9334438 | 17.260798 | 4.5729893 | 0.0000057 |
| estrato5 | 127.6976216 | 16.855092 | 7.5762046 | 0.0000000 |
| estrato6 | 318.0577184 | 26.916842 | 11.8163090 | 0.0000000 |
| habitaciones | 3.6524870 | 4.640782 | 0.7870413 | 0.4315189 |
| parqueaderos | 13.6325480 | 5.500670 | 2.4783433 | 0.0134295 |
| banios | 26.6256389 | 5.770532 | 4.6140698 | 0.0000047 |
Interpretación de los Coeficientes del Modelo Lineal
| Variable | Interpretación |
|---|---|
| (Intercept) | El intercepto es 10.12, pero no es estadísticamente significativo (p = 0.589), lo que indica que el valor base del precio por metro cuadrado (en múltiplos de 10,000) cuando todas las demás variables son cero no tiene un impacto claro en el modelo. |
| areaconst | Coeficiente de 0.79 (p < 0.001). Cada metro cuadrado adicional de área construida se asocia con un aumento de 7,900 COP por metro cuadrado en el precio, dado que “preciom” está en múltiplos de 10,000. |
| estrato4 | Coeficiente de 78.93 (p < 0.001). Las viviendas en estrato 4 tienen un precio por metro cuadrado 789,300 COP mayor que las del estrato 3 (categoría de referencia). |
| estrato5 | Coeficiente de 127.70 (p < 0.001). Las viviendas en estrato 5 tienen un precio por metro cuadrado 1,277,000 COP mayor que las del estrato 3. |
| estrato6 | Coeficiente de 318.06 (p < 0.001). Las viviendas en estrato 6 tienen un precio por metro cuadrado 3,180,600 COP mayor que las del estrato 3. |
| habitaciones | Coeficiente de 3.65 (p = 0.432). No es estadísticamente significativo, lo que sugiere que el número de habitaciones no tiene un efecto claro en el precio por metro cuadrado. |
| parqueaderos | Coeficiente de 13.63 (p = 0.013). Cada parqueadero adicional se asocia con un aumento de 136,300 COP por metro cuadrado en el precio. |
| banios | Coeficiente de 26.63 (p < 0.001). Cada baño adicional incrementa el precio por metro cuadrado en 266,300 COP. |
library(dplyr)
library(knitr)
library(kableExtra)
# Extraer las métricas del modelo
r2 <- summary(modelo)$r.squared
r2_ajustado <- summary(modelo)$adj.r.squared
f_statistic <- summary(modelo)$fstatistic[1]
df1 <- summary(modelo)$fstatistic[2]
df2 <- summary(modelo)$fstatistic[3]
p_value <- pf(f_statistic, df1, df2, lower.tail = FALSE)
# Crear un data frame con las métricas
metricas <- data.frame(
Métrica = c("R²", "R² Ajustado", "Estadístico F", "Grados de Libertad", "Valor p"),
Valor = c(round(r2, 4), round(r2_ajustado, 4), round(f_statistic, 1),
paste(df1, "y", df2), format.pval(p_value, digits = 3))
)
# Crear la tabla con kableExtra
tabla_metricas <- metricas %>%
kable("html", caption = "Métricas del Modelo Lineal") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_metricas
| Métrica | Valor |
|---|---|
| R² | 0.6672 |
| R² Ajustado | 0.664 |
| Estadístico F | 204.5 |
| Grados de Libertad | 7 y 714 |
| Valor p | <2e-16 |
A partir de las métricas del Modelo 2 se puede resaltar lo siguiente:
R² (0.7912): El coeficiente de determinación indica que aproximadamente el 79.12% de la variabilidad del precio por metro cuadrado (preciom) se explica mediante las variables predictoras del modelo (área construida, estrato, habitaciones, parqueaderos y baños). Este valor relativamente alto de R² sugiere que el modelo tiene un buen nivel de explicación respecto a la variable dependiente.
R² Ajustado (0.7907): Esta métrica ajusta el R² considerando el número de predictores del modelo. El hecho de que el R² ajustado sea muy cercano al R² original indica que las variables incluidas en el modelo son relevantes y que no se está sobreajustando el modelo con predictores innecesarios.
Estadístico F (1504.4): El estadístico F evalúa la significancia general del modelo. Un valor tan alto indica que el modelo con las variables predictoras actuales es significativamente mejor para predecir el precio por metro cuadrado que un modelo sin predictores (solo con el intercepto).
Grados de Libertad (7 y 2779): El primer valor (7) representa el número de predictores del modelo (incluyendo las categorías de variables dummy del estrato), mientras que el segundo valor (2779) corresponde al número de observaciones menos el número de parámetros estimados.
Valor p (<2e-16): Este valor extremadamente bajo indica que el modelo es altamente significativo. Existe una evidencia muy fuerte para rechazar la hipótesis nula de que las variables predictoras no tienen ningún efecto sobre el precio por metro cuadrado.
La gráfica muestra que el modelo lineal se ajusta razonablemente bien a los datos, como se indica por la distribución aleatoria de los residuos alrededor de cero. Sin embargo, la presencia de puntos dispersos en los extremos sugiere que se debe investigar más a fondo la posible heterocedasticidad y la presencia de outliers. Estos hallazgos podrían requerir ajustes en el modelo o en los datos para mejorar su rendimiento y precisión.
# Cargar librerías necesarias
library(ggplot2)
# Gráfico de residuos vs valores ajustados para el Modelo 2
ggplot(data = data.frame(fitted = fitted(modelo), residuals = residuals(modelo)),
aes(x = fitted, y = residuals)) +
geom_point(color = "blue", alpha = 0.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Residuos vs Valores Ajustados",
x = "Valores Ajustados",
y = "Residuos") +
theme_minimal()
La prueba de Breusch-Pagan arrojó un valor p extremadamente bajo (< 2.2e-16), lo que indica que el modelo no cumple con el supuesto de homocedasticidad. Esto significa que la varianza de los residuos no es constante a lo largo de los valores ajustados, lo que podría sesgar las inferencias estadísticas del modelo. Para abordar esta heterocedasticidad, se recomienda explorar transformaciones de las variables, evaluar la presencia de valores atípicos o influentes y considerar el uso de modelos con errores estándar robustos para obtener estimaciones más confiables.
# Cargar la librería necesaria
library(lmtest)
# Realizar la prueba de Breusch-Pagan para el Modelo 2
bptest(modelo)
##
## studentized Breusch-Pagan test
##
## data: modelo
## BP = 135.37, df = 7, p-value < 2.2e-16
El test de Shapiro-Wilk arrojó un valor p muy bajo (< 2.2e-16), lo que indica que los residuos del modelo no siguen una distribución normal. Esto significa que el supuesto de normalidad de los errores no se cumple. Para mitigar esta problemática se recomienda transformar la variable dependiente mediante funciones como logaritmos o raíces cuadradas para intentar normalizar los residuos. Al igual que el caso anterior es importante identificar y corregir posibles valores atípicos puesto que pueden distorsionar significativamente la distribución de los residuos.
# Histograma de los residuos del Modelo 2
ggplot(data = data.frame(residuals = residuals(modelo)),
aes(x = residuals)) +
geom_histogram(aes(y = ..density..), bins = 30, fill = "blue", alpha = 0.5) +
geom_density(color = "red") +
labs(title = "Distribución de los Residuos del Modelo 1",
x = "Residuos",
y = "Densidad") +
theme_minimal()
# Prueba de normalidad de Shapiro-Wilk para el Modelo
shapiro.test(residuals(modelo))
##
## Shapiro-Wilk normality test
##
## data: residuals(modelo)
## W = 0.82908, p-value < 2.2e-16
La prueba de Durbin-Watson aplicada al Modelo arrojó un estadístico D-W de 1.71, con una autocorrelación de primer orden de 0.145 y un valor p de 0, lo que indica la presencia de autocorrelación positiva significativa en los residuos del modelo. Esto sugiere que los errores no son independientes, sino que tienden a estar correlacionados secuencialmente, violando el supuesto de independencia de los errores en la regresión lineal. Para abordar este problema, se recomienda considerar transformaciones de las variables, evaluar la inclusión de variables adicionales que capturen la dependencia temporal o espacial, o explorar el uso de modelos alternativos, como modelos de series de tiempo, modelos con errores correlacionados o la aplicación de errores estándar robustos.
# Cargar la librería necesaria
library(car)
# Prueba de Durbin-Watson para el Modelo 2
durbinWatsonTest(modelo)
## lag Autocorrelation D-W Statistic p-value
## 1 0.1596712 1.680149 0
## Alternative hypothesis: rho != 0
Los valores de VIF presentados para las variables en el modelo de regresión lineal múltiple son relativamente bajos, todos ellos por debajo de 3. Esto sugiere que no hay evidencia significativa de multicolinealidad entre las variables predictoras incluidas en el modelo.
# Cargar librerías necesarias
library(car)
library(dplyr)
library(knitr)
library(kableExtra)
# Calcular el VIF para el Modelo 2
vif_values <- vif(modelo)
tabla_vif <- data.frame(Variable = names(vif_values), VIF = round(vif_values, 2))
# Mostrar la tabla con kableExtra
tabla_vif %>%
kable("html", caption = "Factor de Inflación de la Varianza (VIF) - Modelo 2") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Variable | VIF | |
|---|---|---|
| areaconst | areaconst | 1.62 |
| estrato4 | estrato4 | 1.54 |
| estrato5 | estrato5 | 1.99 |
| estrato6 | estrato6 | 1.52 |
| habitaciones | habitaciones | 1.79 |
| parqueaderos | parqueaderos | 1.32 |
| banios | banios | 2.14 |
Este gráfico muestra la Distancia de Cook para cada observación en un modelo de regresión. La mayoría de las observaciones tienen una Distancia de Cook cercana a cero, lo que indica que no tienen una influencia significativa en el modelo. Sin embargo, se observan varios puntos con una Distancia de Cook mucho mayor, con lo cual es pertinente realizar análisis detallado de estas observaciones dado su gran influencia.
# Cargar librerías necesarias
library(ggplot2)
# Calcular la distancia de Cook para el Modelo 2
cook <- cooks.distance(modelo)
# Convertir a un data frame para graficar
df_cook <- data.frame(Observacion = 1:length(cook), Cook = cook)
# Graficar la distancia de Cook
ggplot(df_cook, aes(x = Observacion, y = Cook)) +
geom_point(color = "blue") +
geom_hline(yintercept = 4 / length(cook), color = "red", linetype = "dashed") +
labs(title = "Distancia de Cook", x = "Observación", y = "Distancia de Cook") +
theme_minimal()+theme(plot.title = element_text(hjust = 0.5))
A continuación se listan los puntos identificados a partir de la distancia de Cook:
# Calcular la distancia de Cook
cook <- cooks.distance(modelo)
# Definir el umbral de influencia (4/n)
umbral <- 4 / length(cook)
# Crear un data frame con las observaciones y sus valores de Cook
df_cook <- data.frame(Observacion = 1:length(cook), Cook = cook)
# Filtrar las observaciones que superan el umbral
df_cook_influyente <- df_cook[df_cook$Cook > umbral, ]
# Ordenar de mayor a menor por la Distancia de Cook
df_cook_influyente <- df_cook_influyente[order(-df_cook_influyente$Cook), ]
# Extraer las observaciones influyentes en el mismo orden del dataset "vivienda"
vivienda_influyente <- base1[df_cook_influyente$Observacion, ]
# Agregar la Distancia de Cook como columna extra en el dataset filtrado
vivienda_influyente$Cook_Distance <- df_cook_influyente$Cook
# Mostrar el dataset con los puntos influyentes ordenados
print(vivienda_influyente)
## # A tibble: 51 × 14
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 534 Zona … 3 3 370 1440 1 4 10
## 2 4349 Zona … 2 5 650 1188 4 6 6
## 3 4793 Zona … 2 4 1800 607 1 4 8
## 4 4542 Zona … 2 5 1400 265 2 10 10
## 5 3858 Zona … 2 4 1650 734 1 5 10
## 6 4564 Zona … 2 5 1940 734 3 8 10
## 7 4056 Zona … 1 5 1600 942 4 4 10
## 8 6143 Zona … 2 3 1100 500 1 8 5
## 9 5710 Zona … 2 5 1530 776 6 6 10
## 10 6068 Zona … 2 6 1600 730 2 7 5
## # ℹ 41 more rows
## # ℹ 5 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>,
## # Cook_Distance <dbl>
Los Hat Values, miden la influencia potencial de una observación en los coeficientes de regresión. A diferencia de la Distancia de Cook, que mide el impacto real de una observación en el modelo, los valores de apalancamiento miden cuánto podría influir una observación debido a su posición en el espacio de las variables predictoras. Los valores de apalancamiento varían entre 0 y 1, donde valores más altos indican un mayor apalancamiento.
En la gráfica se observan algunos puntos con valores de apalancamiento altos, algunos superando el umbral de 0.025 marcado por la línea roja discontinua. Estos puntos representan observaciones que se alejan del centroide de las variables predictoras y, por lo tanto, podrían tener un impacto desproporcionado en los coeficientes del modelo.Es importante revisarlos para determinar si son valores atípicos y de esta manera poder determinar su tratamiento.
# Obtener valores de apalancamiento
hat_values <- hatvalues(modelo)
# Convertir a un data frame
df_hat <- data.frame(Observacion = 1:length(hat_values), Hat = hat_values)
# Graficar valores de apalancamiento
ggplot(df_hat, aes(x = Observacion, y = Hat)) +
geom_point(color = "blue") +
geom_hline(yintercept = 2 * mean(hat_values), color = "red", linetype = "dashed") +
labs(title = "Valores de Apalancamiento", x = "Observación", y = "Hat Values") +
theme_minimal()+theme(plot.title = element_text(hjust = 0.5))
El gráfico de “Residuals vs Fitted” permite evaluar la linealidad y la homocedasticidad del modelo. Una dispersión aleatoria de los residuos sin un patrón evidente es lo ideal, y en este caso, se observa dicha característica, aunque la presencia de algunos puntos atípicos podría indicar ligeras desviaciones respecto a la linealidad y la homocedasticidad.
El gráfico Q-Q se enfoca en la normalidad de los residuos, mostrando que, en general, estos siguen una distribución normal. Sin embargo, las desviaciones en las colas sugieren que la normalidad no se cumple de manera perfecta, probablemente debido a la influencia de algunos puntos problemáticos.
En cuanto al gráfico “Scale-Location”, su propósito es analizar la homocedasticidad. Una dispersión uniforme de la raíz cuadrada de los residuos estandarizados sugiere que la varianza es relativamente constante, aunque la presencia de ciertos puntos problemáticos podría afectar esta condición.
Por último, el gráfico “Residuals vs Leverage” es importante para identificar puntos con alta influencia (leverage) y posibles outliers. Este gráfico permite evaluar si dichos puntos tienen un impacto significativo en la robustez del modelo y si contribuyen al incumplimiento de los supuestos.
#Graficos de diagnostico de los supuestos del modelo.
# Configuración para los gráficos
par(mfrow = c(2, 2), mar = c(4, 4, 2, 1)) # Dividir la ventana gráfica en 2x2 y ajustar márgenes
# Graficar los diagnósticos del modelo
plot(modelo, which = 1, col = "blue") # Residuals vs Fitted
plot(modelo, which = 2, col = "darkgreen") # Normal Q-Q
plot(modelo, which = 3, col = "purple") # Scale-Location
plot(modelo, which = 5, col = "orange") # Residuals vs Leverage
En este caso se evaluará el modelo teniendo en cuenta la presencia de puntos influyentes y atípicos que puedan afectar la calidad de las predicciones y la validez de los supuestos del modelo de regresión lineal múltiple. Para ello, se utilizarán la Distancia de Cook y los residuos estandarizados para identificar estas observaciones problemáticas. Una vez identificadas, se eliminarán del conjunto de datos original y se ajustará un nuevo modelo con las mismas variables predictoras.
# Cargar librerías necesarias
library(dplyr)
library(broom)
library(caret)
# Definir número de folds para validación cruzada
set.seed(123) # Para reproducibilidad
control <- trainControl(method = "cv", number = 10)
# Ajustar el modelo con validación cruzada en el dataset original
modelo_cv <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1,
method = "lm",
trControl = control
)
# Obtener el modelo final ajustado
modelo <- modelo_cv$finalModel
# Calcular la distancia de Cook
cook_distances <- cooks.distance(modelo)
# Calcular los residuos estandarizados
resid_estandarizados <- rstudent(modelo)
# Definir umbrales
umbral_cook <- 4 / length(cook_distances) # Umbral estándar para la distancia de Cook
umbral_residuo <- 2 # Residuos estandarizados mayores a 2 en valor absoluto
# Identificar los puntos influyentes y atípicos
puntos_influyentes <- which(cook_distances > umbral_cook)
puntos_atipicos <- which(abs(resid_estandarizados) > umbral_residuo)
# Encontrar los puntos que son ambos (influyentes y atípicos)
puntos_a_eliminar <- intersect(puntos_influyentes, puntos_atipicos)
# Eliminar los puntos del dataset original
base1_clean <- base1[-puntos_a_eliminar, ]
# Ajustar un nuevo modelo sin los puntos influyentes y atípicos con validación cruzada
modelo_cv_clean <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1_clean,
method = "lm",
trControl = control
)
# Obtener el modelo limpio ajustado
modelo2 <- modelo_cv_clean$finalModel
# Mostrar resúmenes de los modelos
#cat("Resumen del modelo original:\n")
#print(summary(modelo))
#cat("\nResumen del modelo sin puntos influyentes y atípicos:\n")
#print(summary(modelo2))
# Comparación gráfica de residuos
#par(mfrow = c(1, 2))
# Modelo original
#plot(cook_distances, resid_estandarizados, main = "Modelo Original",
#xlab = "Distancia de Cook", ylab = "Residuos Estandarizados", col = "blue", pch = 19)
#abline(h = c(-umbral_residuo, umbral_residuo), col = "red", lty = 2)
#abline(v = umbral_cook, col = "red", lty = 2)
# Modelo sin puntos influyentes y atípicos
#cook_clean <- cooks.distance(modelo2)
#resid_clean <- rstudent(modelo2)
#plot(cook_clean, resid_clean, main = "Modelo Sin Puntos Influyentes",
# xlab = "Distancia de Cook", ylab = "Residuos Estandarizados", col = "blue", pch = 19)
#abline(h = c(-umbral_residuo, umbral_residuo), col = "red", lty = 2)
#abline(v = umbral_cook, col = "red", lty = 2)
library(dplyr)
library(caret)
library(knitr)
library(kableExtra)
# Obtener el resumen del modelo final
summary_modelo2 <- summary(modelo2)
# Convertir los coeficientes en un data frame para usar kableExtra
coeficientes <- as.data.frame(summary_modelo2$coefficients)
colnames(coeficientes) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
# Mostrar los coeficientes en una tabla con kableExtra
coeficientes %>%
kable("html", caption = "Resumen de Coeficientes del Modelo Lineal") %>%
kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
position = "center"
)
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 37.6284250 | 12.4765556 | 3.0159305 | 0.0026573 |
| areaconst | 0.7350479 | 0.0328252 | 22.3928069 | 0.0000000 |
| estrato4 | 67.9101194 | 11.2234613 | 6.0507287 | 0.0000000 |
| estrato5 | 110.2507845 | 11.0064194 | 10.0169529 | 0.0000000 |
| estrato6 | 319.3372920 | 18.6003209 | 17.1683754 | 0.0000000 |
| habitaciones | 1.2762724 | 3.1004793 | 0.4116371 | 0.6807349 |
| parqueaderos | 14.6519032 | 3.6333720 | 4.0325910 | 0.0000614 |
| banios | 23.6244526 | 3.9781584 | 5.9385399 | 0.0000000 |
Interpretación de los Coeficientes del Modelo Lineal
| Variable | Interpretación |
|---|---|
| (Intercept) | Coeficiente de 37.63 (p = 0.0027). El intercepto indica el precio base por metro cuadrado cuando todas las demás variables son cero, aunque no tiene una interpretación práctica directa en este contexto. |
| areaconst | Coeficiente de 0.74 (p < 0.001). Cada metro cuadrado adicional de área construida se asocia con un aumento de 7,350 COP por metro cuadrado en el precio, dado que “preciom” está en múltiplos de 10,000. |
| estrato4 | Coeficiente de 67.91 (p < 0.001). Las viviendas en estrato 4 tienen un precio por metro cuadrado 679,100 COP mayor que las del estrato 3 (categoría de referencia). |
| estrato5 | Coeficiente de 110.25 (p < 0.001). Las viviendas en estrato 5 tienen un precio por metro cuadrado 1,102,500 COP mayor que las del estrato 3. |
| estrato6 | Coeficiente de 319.34 (p < 0.001). Las viviendas en estrato 6 tienen un precio por metro cuadrado 3,193,370 COP mayor que las del estrato 3. |
| habitaciones | Coeficiente de 1.28 (p = 0.6807). El efecto del número de habitaciones en el precio no es estadísticamente significativo, lo que sugiere que no influye de manera importante en el precio por metro cuadrado. |
| parqueaderos | Coeficiente de 14.65 (p < 0.001). Cada parqueadero adicional incrementa el precio por metro cuadrado en 146,500 COP, resaltando su impacto positivo en el valor de las viviendas. |
| banios | Coeficiente de 23.62 (p < 0.001). Cada baño adicional aumenta el precio por metro cuadrado en 236,200 COP, lo que refleja un efecto positivo en el valor de la propiedad. |
library(dplyr)
library(knitr)
library(kableExtra)
# Extraer las métricas del modelo
r2 <- summary(modelo2)$r.squared
r2_ajustado <- summary(modelo2)$adj.r.squared
f_statistic <- summary(modelo2)$fstatistic[1]
df1 <- summary(modelo2)$fstatistic[2]
df2 <- summary(modelo2)$fstatistic[3]
p_value <- pf(f_statistic, df1, df2, lower.tail = FALSE)
# Crear un data frame con las métricas
metricas <- data.frame(
Métrica = c("R²", "R² Ajustado", "Estadístico F", "Grados de Libertad", "Valor p"),
Valor = c(round(r2, 4), round(r2_ajustado, 4), round(f_statistic, 1),
paste(df1, "y", df2), format.pval(p_value, digits = 3))
)
# Crear la tabla con kableExtra
tabla_metricas <- metricas %>%
kable("html", caption = "Métricas del Modelo Lineal") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_metricas
| Métrica | Valor |
|---|---|
| R² | 0.7867 |
| R² Ajustado | 0.7845 |
| Estadístico F | 358.7 |
| Grados de Libertad | 7 y 681 |
| Valor p | <2e-16 |
R² ( 0.7867): El valor de R² es 0.7867, lo que significa que aproximadamente el 78.67% de la variabilidad en el precio por metro cuadrado puede explicarse por las variables independientes del modelo. Este es un valor alto, lo que sugiere un buen ajuste del modelo a los datos.
R² Ajustado (0.7845): El valor de R² ajustado es 0.7845, que tiene en cuenta el número de variables en el modelo. Dado que el número de variables no es excesivo, la diferencia entre R² y R² ajustado es pequeña, lo que indica que el modelo está bien ajustado sin ser sobreajustado.
Estadístico F (358.7): El estadístico F es muy alto (2798.4), lo que indica que el modelo en su conjunto es altamente significativo. Esto significa que al menos una de las variables independientes tiene un impacto significativo sobre el precio por metro cuadrado.
Grados de Libertad (7 y 681): Los grados de libertad corresponden al número de predictores (7) y al número de observaciones (681). Esto sugiere que el modelo tiene suficientes datos para hacer estimaciones confiables.
Valor p (<2e-16): El valor p es extremadamente pequeño (<2e-16), lo que indica que el modelo es estadísticamente significativo en su totalidad. Es decir, existe una relación significativa entre las variables independientes y el precio por metro cuadrado.
En el gráfico, no se observan patrones claros en la distribución de los puntos, lo que sugiere que la varianza de los residuos podría ser constante (homocedasticidad). Sin embargo, para confirmar formalmente la presencia de homocedasticidad, se validará este supuesto mediante la prueba de hipótesis de Breusch-Pagan, la cual determinará si existe evidencia estadística suficiente para rechazar la hipótesis nula de homocedasticidad.
# Cargar librerías necesarias
library(ggplot2)
# Gráfico de residuos vs valores ajustados
ggplot(data = data.frame(fitted = fitted(modelo2), residuals = residuals(modelo2)),
aes(x = fitted, y = residuals)) +
geom_point(color = "blue", alpha = 0.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Residuos vs Valores Ajustados", x = "Valores Ajustados", y = "Residuos") +
theme_minimal()+theme(plot.title = element_text(hjust = 0.5))
El test de Breusch-Pagan arrojó un valor p muy bajo (< 2.2e-16), lo que indica que no se cumple el supuesto de homocedasticidad en el modelo.
# Cargar la librería necesaria
library(lmtest)
# Realizar la prueba de Breusch-Pagan
bptest(modelo2)
##
## studentized Breusch-Pagan test
##
## data: modelo2
## BP = 109.24, df = 7, p-value < 2.2e-16
El test de Shapiro-Wilk arrojó un valor p muy bajo (< 3.19e-10), lo que indica que los residuos del modelo no siguen una distribución normal, aunque el valor de W sugiere que la distribción de los residuos se aleja ligeramente de una normal, mejorando un poco este supuesto respecto al caso anterior aunque aun no hay evidencia que sea normal.
# Histograma de los residuos
ggplot(data = data.frame(residuals = residuals(modelo2)),
aes(x = residuals)) +
geom_histogram(aes(y = ..density..), bins = 30, fill = "blue", alpha = 0.5) +
geom_density(color = "red") +
labs(title = "Distribución de los Residuos", x = "Residuos", y = "Densidad") +
theme_minimal()
# Prueba de normalidad de Shapiro-Wilk
shapiro.test(residuals(modelo2))
##
## Shapiro-Wilk normality test
##
## data: residuals(modelo2)
## W = 0.97193, p-value = 3.198e-10
La prueba de Durbin-Watson arrojó un estadístico de 1.88, con un valor p mayor a 0.05 . Estos resultados indican que no presencia de autocorrelación en los residuos, a diferencia del primer caso.
# Cargar librería necesaria
library(car)
# Prueba de Durbin-Watson
durbinWatsonTest(modelo2)
## lag Autocorrelation D-W Statistic p-value
## 1 0.05605569 1.887031 0.108
## Alternative hypothesis: rho != 0
Dado que los supuestos no se cumplen en ambos casos, se llevarán a cabo diversas transformaciones en las variables de regresión con el objetivo de corregir los tres supuestos fundamentales del modelo. Para ello, se explorarán diferentes enfoques, como las transformaciones Log-Lin, Lin-Log y Log-Log.
Estas transformaciones permitirán ajustar mejor las relaciones no lineales, estabilizar la varianza de los errores (homocedasticidad) y aproximar la normalidad de los residuos, esperando mejorar la validez de las inferencias estadísticas y la robustez del modelo resultante.
# Cargar librerías necesarias
library(dplyr)
library(broom)
library(caret)
# Transformaciones logarítmicas necesarias
base1_clean <- base1_clean %>%
mutate(
log_preciom = log(preciom),
log_areaconst = log(areaconst)
)
# Definir número de folds para validación cruzada
set.seed(123) # Para reproducibilidad
control <- trainControl(method = "cv", number = 10)
# Modelo Lin-Lin
modelo_lin_lin <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1_clean,
method = "lm",
trControl = control
)
# Modelo Log-Lin
modelo_log_lin <- train(
log_preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1_clean,
method = "lm",
trControl = control
)
# Modelo Lin-Log
modelo_lin_log <- train(
preciom ~ log_areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1_clean,
method = "lm",
trControl = control
)
# Modelo Log-Log
modelo_log_log <- train(
log_preciom ~ log_areaconst + estrato + habitaciones + parqueaderos + banios,
data = base1_clean,
method = "lm",
trControl = control
)
# Obtener los modelos finales ajustados
modelo_lin_lin_final <- modelo_lin_lin$finalModel
modelo_log_lin_final <- modelo_log_lin$finalModel
modelo_lin_log_final <- modelo_lin_log$finalModel
modelo_log_log_final <- modelo_log_log$finalModel
# Mostrar resúmenes de los modelos
#cat("Modelo Lin-Lin:\n")
#print(summary(modelo_lin_lin_final))
#cat("\nModelo Log-Lin:\n")
#print(summary(modelo_log_lin_final))
#cat("\nModelo Lin-Log:\n")
#print(summary(modelo_lin_log_final))
#cat("\nModelo Log-Log:\n")
#print(summary(modelo_log_log_final))
Los resultados de los coeficientes de los modelos indican que todos los modelos presentan relaciones estadísticamente significativas entre las variables y la variable dependiente, ya que los valores p son extremadamente bajos (< 0.001).
library(dplyr)
library(caret)
library(knitr)
library(kableExtra)
# Obtener el resumen de cada modelo
summary_modelo2 <- summary(modelo2)
summary_modelo_log_lin <- summary(modelo_log_lin_final)
summary_modelo_lin_log <- summary(modelo_lin_log_final)
summary_modelo_log_log <- summary(modelo_log_log_final)
# Convertir los coeficientes en un data frame para usar kableExtra
coef_modelo2 <- as.data.frame(summary_modelo2$coefficients)
coef_log_lin <- as.data.frame(summary_modelo_log_lin$coefficients)
coef_lin_log <- as.data.frame(summary_modelo_lin_log$coefficients)
coef_log_log <- as.data.frame(summary_modelo_log_log$coefficients)
# Cambiar los nombres de las columnas para mayor claridad
colnames(coef_modelo2) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
colnames(coef_log_lin) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
colnames(coef_lin_log) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
colnames(coef_log_log) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
# Mostrar los coeficientes de los modelos en tablas con kableExtra
coef_modelo2 %>%
kable("html", caption = "<center>Resumen de Coeficientes del Modelo 2</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"), position = "center")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 37.6284250 | 12.4765556 | 3.0159305 | 0.0026573 |
| areaconst | 0.7350479 | 0.0328252 | 22.3928069 | 0.0000000 |
| estrato4 | 67.9101194 | 11.2234613 | 6.0507287 | 0.0000000 |
| estrato5 | 110.2507845 | 11.0064194 | 10.0169529 | 0.0000000 |
| estrato6 | 319.3372920 | 18.6003209 | 17.1683754 | 0.0000000 |
| habitaciones | 1.2762724 | 3.1004793 | 0.4116371 | 0.6807349 |
| parqueaderos | 14.6519032 | 3.6333720 | 4.0325910 | 0.0000614 |
| banios | 23.6244526 | 3.9781584 | 5.9385399 | 0.0000000 |
coef_log_lin %>%
kable("html", caption = "<center>Resumen de Coeficientes del Modelo Log-Lin</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"), position = "center")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 4.8511436 | 0.0310473 | 156.249886 | 0.0000000 |
| areaconst | 0.0014700 | 0.0000817 | 17.995978 | 0.0000000 |
| estrato4 | 0.3300136 | 0.0279291 | 11.816133 | 0.0000000 |
| estrato5 | 0.4473146 | 0.0273890 | 16.331921 | 0.0000000 |
| estrato6 | 0.7473231 | 0.0462861 | 16.145751 | 0.0000000 |
| habitaciones | 0.0265784 | 0.0077154 | 3.444856 | 0.0006064 |
| parqueaderos | 0.0249241 | 0.0090415 | 2.756640 | 0.0059962 |
| banios | 0.0622947 | 0.0098995 | 6.292732 | 0.0000000 |
coef_lin_log %>%
kable("html", caption = "<center>Resumen de Coeficientes del Modelo Lin-Log</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"), position = "center")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | -689.6049788 | 42.354017 | -16.2819263 | 0.0000000 |
| log_areaconst | 171.4698835 | 9.458947 | 18.1277984 | 0.0000000 |
| estrato4 | 47.2856223 | 12.449710 | 3.7981305 | 0.0001588 |
| estrato5 | 94.4067224 | 12.424461 | 7.5984559 | 0.0000000 |
| estrato6 | 321.2144492 | 20.365127 | 15.7727690 | 0.0000000 |
| habitaciones | 0.0198641 | 3.394723 | 0.0058515 | 0.9953329 |
| parqueaderos | 16.4466561 | 3.928020 | 4.1870096 | 0.0000320 |
| banios | 25.3951194 | 4.307880 | 5.8950389 | 0.0000000 |
coef_log_log %>%
kable("html", caption = "<center>Resumen de Coeficientes del Modelo Log-Log</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"), position = "center")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 3.0451680 | 0.0930504 | 32.726006 | 0.0000000 |
| log_areaconst | 0.4257638 | 0.0207810 | 20.488132 | 0.0000000 |
| estrato4 | 0.2594230 | 0.0273516 | 9.484742 | 0.0000000 |
| estrato5 | 0.3705187 | 0.0272961 | 13.574034 | 0.0000000 |
| estrato6 | 0.6918540 | 0.0447415 | 15.463354 | 0.0000000 |
| habitaciones | 0.0164311 | 0.0074581 | 2.203117 | 0.0279206 |
| parqueaderos | 0.0252175 | 0.0086297 | 2.922171 | 0.0035912 |
| banios | 0.0587654 | 0.0094643 | 6.209186 | 0.0000000 |
A continuación se muestra las métricas de todos las transformaciones realizadas, teniendo en cuenta que “modelo2” es el original.
El Modelo Log-Log es el más robusto, con el valor más alto de R² (0.8044) y R² ajustado (0.8024), lo que indica que explica mejor la variabilidad de la variable dependiente. Además, presenta el mayor estadístico F (400), lo que sugiere una relación fuerte entre las variables. Los otros modelos (Lin-Lin, Log-Lin y Lin-Log) tienen métricas más bajas, especialmente en R², aunque todos muestran valores p extremadamente bajos (<2e-16), lo que significa que las relaciones en todos los modelos son estadísticamente significativas.
library(dplyr)
library(knitr)
library(kableExtra)
# Extraer las métricas de cada modelo
# Modelo 2 (antes Modelo Lin-Lin)
r2_modelo2 <- summary(modelo2)$r.squared
r2_ajustado_modelo2 <- summary(modelo2)$adj.r.squared
f_statistic_modelo2 <- summary(modelo2)$fstatistic[1]
df1_modelo2 <- summary(modelo2)$fstatistic[2]
df2_modelo2 <- summary(modelo2)$fstatistic[3]
p_value_modelo2 <- pf(f_statistic_modelo2, df1_modelo2, df2_modelo2, lower.tail = FALSE)
# Modelo Log-Lin
r2_log_lin <- summary(modelo_log_lin_final)$r.squared
r2_ajustado_log_lin <- summary(modelo_log_lin_final)$adj.r.squared
f_statistic_log_lin <- summary(modelo_log_lin_final)$fstatistic[1]
df1_log_lin <- summary(modelo_log_lin_final)$fstatistic[2]
df2_log_lin <- summary(modelo_log_lin_final)$fstatistic[3]
p_value_log_lin <- pf(f_statistic_log_lin, df1_log_lin, df2_log_lin, lower.tail = FALSE)
# Modelo Lin-Log
r2_lin_log <- summary(modelo_lin_log_final)$r.squared
r2_ajustado_lin_log <- summary(modelo_lin_log_final)$adj.r.squared
f_statistic_lin_log <- summary(modelo_lin_log_final)$fstatistic[1]
df1_lin_log <- summary(modelo_lin_log_final)$fstatistic[2]
df2_lin_log <- summary(modelo_lin_log_final)$fstatistic[3]
p_value_lin_log <- pf(f_statistic_lin_log, df1_lin_log, df2_lin_log, lower.tail = FALSE)
# Modelo Log-Log
r2_log_log <- summary(modelo_log_log_final)$r.squared
r2_ajustado_log_log <- summary(modelo_log_log_final)$adj.r.squared
f_statistic_log_log <- summary(modelo_log_log_final)$fstatistic[1]
df1_log_log <- summary(modelo_log_log_final)$fstatistic[2]
df2_log_log <- summary(modelo_log_log_final)$fstatistic[3]
p_value_log_log <- pf(f_statistic_log_log, df1_log_log, df2_log_log, lower.tail = FALSE)
# Crear un data frame con las métricas de todos los modelos
metricas_modelo <- data.frame(
Métrica = c("R²", "R² Ajustado", "Estadístico F", "Grados de Libertad", "Valor p"),
`Modelo 2` = c(round(r2_modelo2, 4), round(r2_ajustado_modelo2, 4), round(f_statistic_modelo2, 1),
paste(df1_modelo2, "y", df2_modelo2), format.pval(p_value_modelo2, digits = 3)),
`Modelo Log-Lin` = c(round(r2_log_lin, 4), round(r2_ajustado_log_lin, 4), round(f_statistic_log_lin, 1),
paste(df1_log_lin, "y", df2_log_lin), format.pval(p_value_log_lin, digits = 3)),
`Modelo Lin-Log` = c(round(r2_lin_log, 4), round(r2_ajustado_lin_log, 4), round(f_statistic_lin_log, 1),
paste(df1_lin_log, "y", df2_lin_log), format.pval(p_value_lin_log, digits = 3)),
`Modelo Log-Log` = c(round(r2_log_log, 4), round(r2_ajustado_log_log, 4), round(f_statistic_log_log, 1),
paste(df1_log_log, "y", df2_log_log), format.pval(p_value_log_log, digits = 3))
)
# Crear la tabla con kableExtra
tabla_metricas_modelos <- metricas_modelo %>%
kable("html", caption = "<center>Métricas de Todos los Modelos</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_metricas_modelos
| Métrica | Modelo.2 | Modelo.Log.Lin | Modelo.Lin.Log | Modelo.Log.Log |
|---|---|---|---|---|
| R² | 0.7867 | 0.7857 | 0.7502 | 0.8044 |
| R² Ajustado | 0.7845 | 0.7835 | 0.7476 | 0.8024 |
| Estadístico F | 358.7 | 356.7 | 292.1 | 400 |
| Grados de Libertad | 7 y 681 | 7 y 681 | 7 y 681 | 7 y 681 |
| Valor p | <2e-16 | <2e-16 | <2e-16 | <2e-16 |
A continuación se evalúan los supuestos de normalidad (Shapiro-Wilk), homocedasticidad (bptest) y autocorrelación (dwtest) para las transformaciones realizadas con el fin de verificar si contribuyen a mejorar los supuestos clave de la Regresión Lineal Múltiple.
library(lmtest)
library(dplyr)
# Función para realizar las pruebas y obtener los valores p
evaluar_supuestos <- function(modelo) {
residuos <- residuals(modelo)
# Normalidad de los residuos
normalidad <- shapiro.test(residuos)$p.value
# Homocedasticidad con el modelo completo
homocedasticidad <- bptest(modelo)$p.value
# Autocorrelación de los residuos con el modelo completo
autocorrelacion <- dwtest(modelo)$p.value
return(c(normalidad, homocedasticidad, autocorrelacion))
}
# Aplicar la función a cada modelo
resultados_modelo2 <- evaluar_supuestos(modelo2)
resultados_log_lin <- evaluar_supuestos(modelo_log_lin_final)
resultados_lin_log <- evaluar_supuestos(modelo_lin_log_final)
resultados_log_log <- evaluar_supuestos(modelo_log_log_final)
# Crear la tabla de resultados
tabla_resultados <- data.frame(
Modelo = c("Modelo 2", "Log-Lineal", "Lineal-Log", "Log-Log"),
Normalidad = c(resultados_modelo2[1], resultados_log_lin[1], resultados_lin_log[1], resultados_log_log[1]),
Homocedasticidad = c(resultados_modelo2[2], resultados_log_lin[2], resultados_lin_log[2], resultados_log_log[2]),
Autocorrelación = c(resultados_modelo2[3], resultados_log_lin[3], resultados_lin_log[3], resultados_log_log[3])
)
# Función para formatear los valores p a 5 decimales
formatear_p_valores <- function(x) {
return(sprintf("%.5f", x))
}
# Aplicar el formateo a las columnas de valores p
tabla_resultados$Normalidad <- formatear_p_valores(tabla_resultados$Normalidad)
tabla_resultados$Homocedasticidad <- formatear_p_valores(tabla_resultados$Homocedasticidad)
tabla_resultados$Autocorrelación <- formatear_p_valores(tabla_resultados$Autocorrelación)
# Crear la tabla con kable
tabla_resultados %>%
kable("html", caption = "<center>Pruebas de Supuestos Estadísticos por Modelo</center>",
col.names = c("Modelo", "Normalidad", "Homocedasticidad", "Autocorrelación")) %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"), position = "center")
| Modelo | Normalidad | Homocedasticidad | Autocorrelación |
|---|---|---|---|
| Modelo 2 | 0.00000 | 0.00000 | 0.06065 |
| Log-Lineal | 0.06620 | 0.06734 | 0.00000 |
| Lineal-Log | 0.00000 | 0.00000 | 0.16644 |
| Log-Log | 0.00324 | 0.12783 | 0.00004 |
A partir de la tabla anterior se resalta lo siguiente:
Modelo 2 (Lin-Lin)
Normalidad: El valor p es 0.00000, lo que indica que los residuos no siguen una distribución normal.
Homocedasticidad: El valor p es 0.00000, lo que indica heterocedasticidad (varianza no constante).
Autocorrelación: El valor p es 0.06065, lo que sugiere que no hay autocorrelación significativa en los residuos.
Modelo Log-Lineal
Normalidad: El valor p es 0.06620, lo que sugiere que los residuos se acercan a una distribución normal, aunque no se puede afirmar con total certeza.
Homocedasticidad: El valor p es 0.06734, lo que sugiere homocedasticidad (varianza constante).
Autocorrelación: El valor p es 0.00000, lo que indica la presencia de autocorrelación en los residuos.
Modelo Lineal-Log
Normalidad: El valor p es 0.00000, lo que indica que los residuos no siguen una distribución normal.
Homocedasticidad: El valor p es 0.00000, lo que indica heterocedasticidad.
Autocorrelación: El valor p es 0.16644, lo que sugiere que no hay autocorrelación significativa.
Modelo Log-Log
Normalidad: El valor p es 0.00324, lo que indica que los residuos no siguen una distribución normal.
Homocedasticidad: El valor p es 0.12783, lo que sugiere homocedasticidad.
Autocorrelación: El valor p es 0.00004, lo que indica la presencia de autocorrelación.
Ante esto dado que ninguna transformación cumple con los supuestos fundamentales es pertinente probar otras opciones como por ejemplo modelos no lineales.
Para la predicción del precio de las viviendas se usará el modelo Log-Lin pues parece ser el mejor de los cuatro casos. Esto se debe a que muestra el valor p más alto para la prueba de normalidad (0.06620), lo que sugiere que sus residuos se aproximan más a una distribución normal en comparación con los otros modelos. Además, tiene un valor p relativamente alto para la homocedasticidad (0.06734), lo que indica que la varianza de los residuos es más constante en este modelo. Aunque muestra autocorrelación (valor p muy bajo), el cumplimiento relativo de los supuestos de normalidad y homocedasticidad lo hace preferible en este conjunto de datos.
library(dplyr)
library(knitr)
library(kableExtra)
library(scales) # Para formato de pesos
# Crear las solicitudes con el factor bien definido
solicitud1 <- data.frame(
areaconst = 200,
estrato3 = 0,
estrato4 = 1,
estrato5 = 0,
estrato6 = 0,
parqueaderos = 1,
banios = 2,
habitaciones = 4
)
solicitud2 <- data.frame(
areaconst = 200,
estrato3 = 0,
estrato4 = 0,
estrato5 = 1,
estrato6 = 0,
parqueaderos = 1,
banios = 2,
habitaciones = 4
)
# Realizar las predicciones
prediccion1 <- predict(modelo_log_lin_final, newdata = solicitud1)
resultado1 <- exp(prediccion1) * 200 * 10000 # Aplicar exp()
prediccion2 <- predict(modelo_log_lin_final, newdata = solicitud2)
resultado2 <- exp(prediccion2) * 200 * 10000 # Aplicar exp()
# Crear la tabla de resultados
resultados <- data.frame(
Solicitud = c("Solicitud 1", "Solicitud 2"),
Predicción = c(resultado1, resultado2)
)
# Formatear las predicciones como pesos colombianos
resultados$Predicción <- dollar(resultados$Predicción, scale = 1, prefix = "$", big.mark = ".", decimal.mark = ",")
# Mostrar la tabla con kableExtra y formato de pesos
resultados %>%
kable("html") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Solicitud | Predicción |
|---|---|
| Solicitud 1 | $616.543.125 |
| Solicitud 2 | $693.276.695 |
A continuación se presenta el mapa de las viviendas recomendadas, las cuales fueron elegidas teniendo en cuenta la restricción presupuestal del cliente. Es importante resaltar que, según la predicción hecha, una vivienda con esas características superaría ampliamente el presupuesto asignado, por lo cual las viviendas listadas fueron seleccionadas aplicando un filtro de precio máximo de 350 millones de pesos, y luego se ordenaron por similitud con la solicitud del cliente, priorizando el área construida y otros criterios como el número de habitaciones, baños y parqueaderos. Finalmente, se eligieron las viviendas que mejor se ajustaron a estas condiciones, garantizando así una selección óptima dentro de las limitaciones presupuestarias.
base1_clean <- base1_clean %>%
mutate(
estrato3 = ifelse(estrato == 3, 1, 0),
estrato4 = ifelse(estrato == 4, 1, 0),
estrato5 = ifelse(estrato == 5, 1, 0),
estrato6 = ifelse(estrato == 6, 1, 0)
)
# Cargar las librerías necesarias
library(dplyr)
library(kableExtra)
# Crear la tabla con las viviendas seleccionadas
tabla_viviendas <- ofertas_filtradas %>%
mutate(PrecioTotal = preciom * areaconst * 10000) %>%
select(
PrecioTotal,
Área = areaconst,
Estrato = estrato,
Habitaciones = habitaciones,
Parqueaderos = parqueaderos,
Baños = banios,
Longitud = longitud,
Latitud = latitud
) %>%
mutate(PrecioTotal = format(PrecioTotal, big.mark = ".", scientific = FALSE))
# Mostrar la tabla con kableExtra
tabla_viviendas %>%
kable(
caption = "<center>Tabla de Viviendas Seleccionadas</center>",
col.names = c("Precio Total (COP)", "Área (m²)", "Estrato", "Habitaciones",
"Parqueaderos", "Baños", "Longitud", "Latitud"),
format = "html",
align = "c"
) %>%
kable_styling(
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE,
position = "center"
)
| Precio Total (COP) | Área (m²) | Estrato | Habitaciones | Parqueaderos | Baños | Longitud | Latitud |
|---|---|---|---|---|---|---|---|
| 330.000.000 | 150 | 3 | 4 | 1 | 2 | -76.53200 | 3.45200 |
| 296.000.000 | 160 | 3 | 5 | 1 | 2 | -76.48800 | 3.47200 |
| 255.000.000 | 150 | 3 | 4 | 1 | 2 | -76.48600 | 3.46800 |
| 270.000.000 | 150 | 3 | 4 | 1 | 2 | -76.49575 | 3.47561 |
| 256.000.000 | 160 | 3 | 3 | 1 | 2 | -76.54015 | 3.42681 |
| 315.000.000 | 180 | 3 | 3 | 1 | 2 | -76.49500 | 3.46589 |
| 320.000.000 | 160 | 3 | 4 | 1 | 3 | -76.49358 | 3.46639 |
| 184.000.000 | 160 | 3 | 3 | 1 | 1 | -76.49929 | 3.47094 |
Para este caso se realizó un filtro con los apartamentos de la zona sur con un total de 2.787 observaciones.
# Cargar librerías necesarias
library(dplyr)
library(kableExtra)
# Filtrar la base de datos para casas en la zona norte
base2 <- vivienda %>%
filter(tipo == "Apartamento", zona == "Zona Sur")
# Mostrar los primeros 3 registros
head(base2, 5) %>%
kable() %>%
kable_styling(full_width = FALSE)
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5098 | Zona Sur | 5 | 4 | 290 | 96 | 1 | 2 | 3 | Apartamento | acopi | -76.53464 | 3.44987 |
| 698 | Zona Sur | 2 | 3 | 78 | 40 | 1 | 1 | 2 | Apartamento | aguablanca | -76.50100 | 3.40000 |
| 8199 | Zona Sur | 3 | 6 | 875 | 194 | 2 | 5 | 3 | Apartamento | aguacatal | -76.55700 | 3.45900 |
| 1241 | Zona Sur | 5 | 3 | 135 | 117 | 1 | 2 | 3 | Apartamento | alameda | -76.51400 | 3.44100 |
| 5370 | Zona Sur | 5 | 3 | 135 | 78 | 1 | 1 | 3 | Apartamento | alameda | -76.53600 | 3.43600 |
# Contar la cantidad de registros por tipo de vivienda en base2
base2 %>%
count(tipo) %>%
kable(col.names = c("Tipo de Vivienda", "Cantidad")) %>%
kable_styling(full_width = FALSE)
| Tipo de Vivienda | Cantidad |
|---|---|
| Apartamento | 2787 |
Al igual que con las casas de la zona norte, se evidencia la presencia de apartamentos en el norte de la ciudad, lo cual pudo haber sido causado por un error en la codificación de la variable al momento de crearla, es importante resaltar que la ciudad de Cali no tiene distinciones sobre las distintas zonas de manera oficial.
# Cargar librerías necesarias
library(leaflet)
# Crear el mapa con solo las ubicaciones
leaflet(base2) %>%
addTiles() %>%
addCircleMarkers(
~longitud, ~latitud,
radius = 3,
color = "blue",
fill = TRUE,
fillOpacity = 0.7
)
El total de observaciones de apartamentos de la zona Sur es de 2.787
# Cargar librerías necesarias
library(dplyr)
library(ggplot2)
library(plotly)
library(reshape2)
# Matriz de correlación para variables numéricas
cor_data <- base2 %>%
select(preciom, areaconst, banios, habitaciones) %>%
cor(use = "complete.obs")
# Convertir la matriz de correlación en formato largo
cor_data_melted2 <- melt(cor_data)
La matriz de correlación muestra patrones significativos entre las
variables analizadas. Se destaca una fuerte correlación positiva entre
el precio (preciom) y el área construida
(areaconst), con un coeficiente de 0.76, lo que sugiere que
viviendas más grandes tienden a tener precios más elevados. Asimismo, se
observa una correlación positiva considerable entre el número de baños
(banios) y el área construida, con un coeficiente de 0.68,
indicando que viviendas con mayor área suelen disponer de más baños. La
relación entre el precio y el número de baños también muestra una
correlación positiva notable de 0.73, lo que implica que un mayor número
de baños está asociado con precios más altos. Por otro lado, la
correlación entre el número de habitaciones (habitaciones)
y el precio es la más débil, con un coeficiente de 0.35, aunque aún
positiva, lo que indica que el impacto del numero de habitaciones en el
precio es menor en relación a las otras variables.
# Gráfico de matriz de correlación,
heatmap_plot <- ggplot(cor_data_melted2, aes(Var1, Var2, fill = value)) +
geom_tile() +
geom_text(aes(label = round(value, 2)), color = "black", size = 3) +
scale_fill_gradient2(low = "green", high = "red", midpoint = 0) +
theme_minimal() +
ggtitle("Matriz de Correlación") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
ggplotly(heatmap_plot)
Este gráfico muestra la relación entre el precio por metro cuadrado de los apartamentos y su área construida sugiriendo una fuerte correlación positiva entre el precio y el área construida: a medida que aumenta el área construida, el precio de la propiedad también tiende a aumentar.
scatter_plot <- ggplot(base2, aes(x = areaconst, y = preciom)) +
geom_point(aes(color = zona), alpha = 0.7) +
geom_smooth(method = "lm", col = "red") +
theme_minimal() +
labs(title = "Precio vs Área Construida",
x = "Área Construida (m²)", y = "Precio")
ggplotly(scatter_plot)
El gráfico muestra la distribución de precios de viviendas
(Predio) según el estrato socio económico. Se observa
claramente que a medida que aumenta el estrato, también aumenta
significativamente el precio de las viviendas. Los estratos más bajos (3
y 4) presentan una distribución de precios más concentrada y baja,
mientras que los estratos más altos (5 y 6) muestran una mayor
dispersión y valores considerablemente más altos, con el estrato 6
destacando por tener los precios más elevados y una mayor
variabilidad.
boxplot_estrato <- ggplot(base2, aes(x = as.factor(estrato), y = preciom, fill = as.factor(estrato))) +
geom_boxplot() +
theme_minimal() +
labs(title = "Distribución de Precios por Estrato",
x = "Estrato", y = "Precio")+
scale_fill_discrete(name = "Estrato")
ggplotly(boxplot_estrato)
El gráfico muestra la distribución del área construida
(Área m2) según el estrato socioeconómico. Se observa que a
medida que aumenta el estrato, también tiende a aumentar el área de las
viviendas, aunque con mayor variabilidad. Los estratos más bajos (3 y 4)
presentan áreas más pequeñas y concentradas, mientras que los estratos
más altos (5 y 6) muestran una mayor dispersión y áreas
significativamente mayores, especialmente en el estrato 6, que destaca
por tener las viviendas más grandes y una mayor variabilidad en
tamaño.
boxplot_estrato <- ggplot(base2, aes(x = as.factor(estrato), y = areaconst, fill = as.factor(estrato))) +
geom_boxplot() +
theme_minimal() +
labs(title = "Distribución de área por Estrato",
x = "Estrato", y = "Área m2")+
scale_fill_discrete(name = "Estrato")
ggplotly(boxplot_estrato)
El gráfico de violín muestra la distribución del precio por metro cuadrado y el área construida de los apartamentos en la Zona Sur. Se observa que tanto el precio por metro cuadrado como el área construida tienden a concentrarse en rangos más bajos, con una distribución asimétrica hacia valores más altos. En el caso del precio por metro cuadrado, la mayor concentración se encuentra entre 1.000.000 y 2.000.000 por metro cuadrado. En cuanto al área construida, la concentración principal está por debajo de los 100 m², con una distribución que se extiende hacia áreas mayores. Esto sugiere que, en la Zona Sur, predominan los apartamentos con precios por metro cuadrado y áreas construidas más modestas, aunque también existen algunos apartamentos de mayor valor y tamaño.
library(ggplot2)
library(plotly)
# Gráfico de violín para 'preciom' por 'zona'
violin_preciom <- ggplot(base2, aes(x = zona, y = preciom, fill = zona)) +
geom_violin(trim = FALSE, alpha = 0.7) +
theme_minimal() +
labs(title = "Distribución de Precios por Zona (Violín)",
x = "Zona", y = "Precio") +
theme(legend.position = "none")
# Gráfico de violín para 'areaconst' por 'zona'
violin_areaconst <- ggplot(base2, aes(x = zona, y = areaconst, fill = zona)) +
geom_violin(trim = FALSE, alpha = 0.7) +
theme_minimal() +
labs(title = "Distribución de Precio y Área Construida",
x = "Zona", y = "Área Construida (m²)") +
theme(legend.position = "none")
# Organizar los gráficos uno al lado del otro usando subplot
subplot(ggplotly(violin_preciom), ggplotly(violin_areaconst),
nrows = 1, shareX = FALSE, shareY = FALSE, titleX = TRUE, titleY = TRUE)
Similar al análisis realizado con las casas, se construirá un modelo
de regresión lineal múltiple para los apartamentos, utilizando las
siguientes variables predictoras: área construida, estrato, número de
habitaciones, número de parqueaderos y número de baños. El objetivo de
este modelo es determinar la influencia de estas variables en el precio
por metro cuadrado (preciom) de los apartamentos, y
cuantificar la relación entre cada variable y el precio. Para garantizar
la robustez del modelo y obtener predicciones precisas del precio de los
apartamentos, se empleará la validación cruzada de 10 folds.
# Cargar librerías necesarias
library(dplyr)
library(caret)
# Definir número de folds para validación cruzada
set.seed(1234) # Para reproducibilidad
control <- trainControl(method = "cv", number = 10) # 10-fold cross-validation
# Ajustar el modelo con validación cruzada
modelo_cvcaso2 <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2,
method = "lm",
trControl = control
)
# Obtener el modelo final ajustado
modelocaso2 <- modelo_cvcaso2$finalModel
# Obtener el resumen del modelo final
#summary(modelocaso2)
#print(modelo_cvcaso2)
library(dplyr)
library(caret)
library(knitr)
library(kableExtra)
# Obtener el resumen del modelo final
summary_modelocaso2 <- summary(modelocaso2)
# Convertir los coeficientes en un data frame para usar kableExtra
coeficientescaso2 <- as.data.frame(summary_modelocaso2$coefficients)
colnames(coeficientescaso2) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
# Mostrar los coeficientes en una tabla con kableExtra
coeficientescaso2 %>%
kable("html", caption = "Resumen de Coeficientes del Modelo Lineal") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | -41.37646 | 10.3323228 | -4.004565 | 6.38e-05 |
| areaconst | 1.30635 | 0.0466262 | 28.017477 | 0.00e+00 |
| estrato4 | 28.28496 | 6.8695124 | 4.117462 | 3.94e-05 |
| estrato5 | 53.88307 | 7.0965210 | 7.592886 | 0.00e+00 |
| estrato6 | 205.78092 | 8.9417555 | 23.013481 | 0.00e+00 |
| habitaciones | -12.85797 | 3.2922286 | -3.905553 | 9.62e-05 |
| parqueaderos | 61.64835 | 3.5588300 | 17.322644 | 0.00e+00 |
| banios | 40.36916 | 2.9354369 | 13.752353 | 0.00e+00 |
Interpretación de los Coeficientes del Modelo Lineal
| Variable | Interpretación |
|---|---|
| (Intercept) | El intercepto es -41.38 (p < 0.001), lo que indica que cuando todas las variables predictoras son cero, el precio por metro cuadrado (en múltiplos de 10,000) sería negativo, lo cual no tiene un significado práctico real. |
| areaconst | Coeficiente de 1.31 (p < 0.001). Cada metro cuadrado adicional de área construida se asocia con un aumento de 13,060 COP por metro cuadrado en el precio, dado que “preciom” está en múltiplos de 10,000. |
| estrato4 | Coeficiente de 28.28 (p < 0.001). Las viviendas en estrato 4 tienen un precio por metro cuadrado 282,800 COP mayor que las del estrato 3 (categoría de referencia). |
| estrato5 | Coeficiente de 53.88 (p < 0.001). Las viviendas en estrato 5 tienen un precio por metro cuadrado 538,800 COP mayor que las del estrato 3. |
| estrato6 | Coeficiente de 205.78 (p < 0.001). Las viviendas en estrato 6 tienen un precio por metro cuadrado 2,057,800 COP mayor que las del estrato 3. |
| habitaciones | Coeficiente de -12.86 (p < 0.001). Un aumento en el número de habitaciones se asocia con una disminución de 128,600 COP en el precio por metro cuadrado, lo cual podría indicar que más habitaciones no necesariamente implican mayor valor por área. |
| parqueaderos | Coeficiente de 61.65 (p < 0.001). Cada parqueadero adicional se asocia con un aumento de 616,500 COP por metro cuadrado en el precio. |
| banios | Coeficiente de 40.37 (p < 0.001). Cada baño adicional incrementa el precio por metro cuadrado en 403,700 COP. |
library(dplyr)
library(knitr)
library(kableExtra)
# Extraer las métricas del modelo 'modelocaso2'
r2 <- summary(modelocaso2)$r.squared
r2_ajustado <- summary(modelocaso2)$adj.r.squared
f_statistic <- summary(modelocaso2)$fstatistic[1]
df1 <- summary(modelocaso2)$fstatistic[2]
df2 <- summary(modelocaso2)$fstatistic[3]
p_value <- pf(f_statistic, df1, df2, lower.tail = FALSE)
# Crear un data frame con las métricas
metricas <- data.frame(
Métrica = c("R²", "R² Ajustado", "Estadístico F", "Grados de Libertad", "Valor p"),
Valor = c(
round(r2, 4),
round(r2_ajustado, 4),
round(f_statistic, 1),
paste(df1, "y", df2),
format.pval(p_value, digits = 3)
)
)
# Crear la tabla con kableExtra
tabla_metricas <- metricas %>%
kable("html", caption = "Métricas del Modelo 2") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_metricas
| Métrica | Valor |
|---|---|
| R² | 0.7912 |
| R² Ajustado | 0.7907 |
| Estadístico F | 1504.4 |
| Grados de Libertad | 7 y 2779 |
| Valor p | <2e-16 |
R² (0.7912): El modelo explica el 79.12% de la variabilidad del precio por metro cuadrado, indicando un buen ajuste.
R² Ajustado (0.7907): Muy cercano al R², lo que muestra que las variables incluidas son relevantes y no añaden complejidad innecesaria.
Estadístico F (1504.4): Indica que el modelo es significativamente mejor que uno sin predictores.
Grados de Libertad (7 y 2779): Reflejan el número de predictores y el tamaño de la muestra.
Valor p (<2e-16): El modelo es estadísticamente significativo, con al menos una variable independiente influyendo en el precio.
# Cargar librerías necesarias
library(ggplot2)
# Gráfico de residuos vs valores ajustados
ggplot(data = data.frame(fitted = fitted(modelocaso2), residuals = residuals(modelocaso2)),
aes(x = fitted, y = residuals)) +
geom_point(color = "blue", alpha = 0.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Residuos vs Valores Ajustados", x = "Valores Ajustados", y = "Residuos") +
theme_minimal()
El test de Breusch-Pagan arrojó un valor p muy bajo (< 2.2e-16), lo que indica que no se cumple el supuesto de homocedasticidad en el modelo.
# Cargar la librería necesaria
library(lmtest)
# Realizar la prueba de Breusch-Pagan para el Modelo 2
bptest(modelocaso2)
##
## studentized Breusch-Pagan test
##
## data: modelocaso2
## BP = 813.23, df = 7, p-value < 2.2e-16
El test de Shapiro-Wilk evalúa la normalidad de los residuos del modelo. Un valor de W = 0.77514 cercano a 1 indicaría normalidad, pero aquí sugiere desviaciones importantes. El p-value < 2.2e-16 indica que se rechaza la hipótesis nula de normalidad, confirmando que los residuos no siguen una distribución normal.
# Histograma de los residuos
ggplot(data = data.frame(residuals = residuals(modelocaso2)),
aes(x = residuals)) +
geom_histogram(aes(y = ..density..), bins = 30, fill = "blue", alpha = 0.5) +
geom_density(color = "red") +
labs(title = "Distribución de los Residuos", x = "Residuos", y = "Densidad") +
theme_minimal()
# Prueba de normalidad de Shapiro-Wilk
shapiro.test(residuals(modelocaso2))
##
## Shapiro-Wilk normality test
##
## data: residuals(modelocaso2)
## W = 0.77514, p-value < 2.2e-16
El análisis de autocorrelación muestra un valor de rho = 0.1454, indicando una ligera autocorrelación en los residuos. El estadístico de Durbin-Watson (D-W = 1.7059) se acerca a 2, lo que sugiere una baja autocorrelación. Sin embargo, el p-value = 0 lleva a rechazar la hipótesis nula de ausencia de autocorrelación, indicando una posible dependencia en los residuos.
# Cargar librería necesaria
library(car)
# Prueba de Durbin-Watson
durbinWatsonTest(modelocaso2)
## lag Autocorrelation D-W Statistic p-value
## 1 0.1454317 1.705874 0
## Alternative hypothesis: rho != 0
Los valores de VIF presentados para las variables en el modelo de regresión lineal múltiple son relativamente bajos, todos ellos por debajo de 3. Esto sugiere que no hay evidencia significativa de multicolinealidad entre las variables predictoras incluidas en el modelo.
# Cargar librerías necesarias
library(car)
library(dplyr)
library(knitr)
library(kableExtra)
# Calcular el VIF
vif_values <- vif(modelocaso2)
tabla_vif <- data.frame(Variable = names(vif_values), VIF = round(vif_values, 2))
# Mostrar la tabla con kableExtra
tabla_vif %>%
kable("html", caption = "Factor de Inflación de la Varianza (VIF)") %>%
kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Variable | VIF | |
|---|---|---|
| areaconst | areaconst | 2.18 |
| estrato4 | estrato4 | 4.08 |
| estrato5 | estrato5 | 4.26 |
| estrato6 | estrato6 | 4.01 |
| habitaciones | habitaciones | 1.47 |
| parqueaderos | parqueaderos | 1.86 |
| banios | banios | 2.69 |
La distancia de Cook evalúa cuánto cambiarían los coeficientes del modelo si se eliminara una observación en particular, combinando la discrepancia entre los valores ajustados y observados con la influencia de cada punto en los parámetros del modelo. Valores altos de la distancia de Cook indican que una observación tiene un impacto significativo en la estimación del modelo y podría ser un posible outlier o un punto con gran influencia, por lo cual deben analizarse con mayor detalle.
# Cargar librerías necesarias
library(ggplot2)
# Calcular la distancia de Cook para el Modelo 2
cook2 <- cooks.distance(modelocaso2)
# Convertir a un data frame para graficar
df_cook2 <- data.frame(Observacion = 1:length(cook), Cook = cook)
# Graficar la distancia de Cook
ggplot(df_cook, aes(x = Observacion, y = Cook)) +
geom_point(color = "blue", alpha = 0.6) +
geom_hline(yintercept = 4 / length(cook), color = "red", linetype = "dashed") +
labs(title = "Distancia de Cook", x = "Observación", y = "Distancia de Cook") +
theme_minimal()+theme(plot.title = element_text(hjust = 0.5))
# Calcular la distancia de Cook para el Modelo 2
cook <- cooks.distance(modelocaso2)
# Definir el umbral de influencia (4/n)
umbral <- 4 / length(cook)
# Crear un data frame con las observaciones y sus valores de Cook
df_cook <- data.frame(Observacion = 1:length(cook), Cook = cook)
# Filtrar las observaciones que superan el umbral
df_cook_influyente <- df_cook[df_cook$Cook > umbral, ]
# Ordenar de mayor a menor por la Distancia de Cook
df_cook_influyente <- df_cook_influyente[order(-df_cook_influyente$Cook), ]
# Extraer las observaciones influyentes en el mismo orden del dataset "base2"
base2_influyente <- base2[df_cook_influyente$Observacion, ]
# Agregar la Distancia de Cook como columna extra en el dataset filtrado
base2_influyente$Cook_Distance <- df_cook_influyente$Cook
# Mostrar el dataset con los puntos influyentes ordenados
print(base2_influyente)
## # A tibble: 143 × 14
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6121 Zona … 7 5 299 932 1 3 3
## 2 6472 Zona … 3 5 170 605 1 2 2
## 3 324 Zona … 5 4 190 50 10 2 4
## 4 7182 Zona … 3 5 730 573 3 8 5
## 5 4952 Zona … 3 5 650 600 2 4 5
## 6 6475 Zona … 2 6 1561 399 3 4 3
## 7 6512 Zona … 3 6 1750 290 3 4 3
## 8 5952 Zona … 2 6 1750 342 3 5 4
## 9 6086 Zona … 10 6 1500 240 3 5 6
## 10 6197 Zona … 7 6 1700 290 3 4 3
## # ℹ 133 more rows
## # ℹ 5 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>,
## # Cook_Distance <dbl>
En la gráfica se observan algunos puntos con valores de apalancamiento altos, algunos superando el umbral de 0.025 marcado por la línea roja discontinua. Estos puntos representan observaciones que se alejan del centroide de las variables predictoras y podrían tener un impacto desproporcionado en los coeficientes del modelo.Es importante revisarlos para determinar si son valores atípicos y de esta manera poder determinar su tratamiento.
# Obtener los valores de apalancamiento para el Modelo 2
hat_values <- hatvalues(modelocaso2)
# Convertir a un data frame
df_hat <- data.frame(Observacion = 1:length(hat_values), Hat = hat_values)
# Graficar los valores de apalancamiento
ggplot(df_hat, aes(x = Observacion, y = Hat)) +
geom_point(color = "blue") +
geom_hline(yintercept = 2 * mean(hat_values), color = "red", linetype = "dashed") +
labs(title = "Valores de Apalancamiento del Modelo",
x = "Observación",
y = "Valores de Apalancamiento (Hat Values)") +
theme_minimal()+theme(plot.title = element_text(hjust = 0.5))
El gráfico de “Residuals vs Fitted” muestra una dispersión aleatoria de los residuos, lo cual es ideal para cumplir con la linealidad y la homocedasticidad, aunque algunos puntos atípicos se alejan del patrón general, lo que podría indicar desviaciones en estos supuestos.
El gráfico Q-Q sugiere que los residuos siguen aproximadamente una distribución normal, aunque ciertas desviaciones en las colas revelan que la normalidad no se cumple de manera perfecta debido a estos puntos problemáticos.
En el gráfico “Scale-Location”, que evalúa la homocedasticidad, se observa una dispersión relativamente uniforme, aunque la presencia de algunos puntos atípicos podría estar afectando la constancia de la varianza.
Finalmente, el gráfico “Residuals vs Leverage” identifica puntos con alta influencia (leverage) y posibles outliers que podrían comprometer la robustez del modelo y contribuir al incumplimiento de los supuestos estadísticos.
# Gráficos de diagnóstico de los supuestos del Modelo 2
# Configuración de la ventana gráfica en 2x2 y ajuste de márgenes
par(mfrow = c(2, 2), mar = c(4, 4, 2, 1))
# Graficar los diagnósticos del Modelo 2
plot(modelocaso2, which = 1, col = "blue") # Residuos vs Valores Ajustados
plot(modelocaso2, which = 2, col = "darkgreen") # Gráfico Q-Q Normal
plot(modelocaso2, which = 3, col = "purple") # Scale-Location
plot(modelocaso2, which = 5, col = "orange") # Residuos vs Apalancamiento
En este caso se evaluará el modelo teniendo en cuenta la presencia de puntos influyentes y atípicos que puedan afectar la calidad de las predicciones y la validez de los supuestos del modelo de regresión lineal múltiple.
# Cargar librerías necesarias
library(dplyr)
library(broom)
library(caret)
# Definir número de folds para validación cruzada
set.seed(123) # Para reproducibilidad
control <- trainControl(method = "cv", number = 10)
# Ajustar el Modelo 2 con validación cruzada en el dataset original
modelo_cv <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2,
method = "lm",
trControl = control
)
# Obtener el Modelo 2 ajustado originalmente
modelocaso2 <- modelo_cv$finalModel
# Calcular la distancia de Cook
cook_distances <- cooks.distance(modelocaso2)
# Calcular los residuos estandarizados
resid_estandarizados <- rstudent(modelocaso2)
# Definir umbrales
umbral_cook <- 4 / length(cook_distances) # Umbral estándar para la distancia de Cook
umbral_residuo <- 2 # Residuos estandarizados mayores a 2 en valor absoluto
# Identificar los puntos influyentes y atípicos
puntos_influyentes <- which(cook_distances > umbral_cook)
puntos_atipicos <- which(abs(resid_estandarizados) > umbral_residuo)
# Encontrar los puntos que son ambos (influyentes y atípicos)
puntos_a_eliminar <- intersect(puntos_influyentes, puntos_atipicos)
# Eliminar los puntos del dataset original
base2_clean <- base2[-puntos_a_eliminar, ]
# Ajustar un nuevo Modelo 2 sin los puntos influyentes y atípicos con validación cruzada
modelo_cv_clean <- train(
preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2_clean,
method = "lm",
trControl = control
)
# Obtener el Modelo 2 limpio ajustado
modelocaso2_clean <- modelo_cv_clean$finalModel
# Mostrar resúmenes de los modelos
#cat("Resumen del Modelo 2 original:\n")
#print(summary(modelocaso2))
#cat("\nResumen del Modelo 2 sin puntos influyentes y atípicos:\n")
#print(summary(modelocaso2_clean))
# Comparación gráfica de residuos
par(mfrow = c(1, 2))
# Modelo 2 original
plot(cook_distances, resid_estandarizados, main = "Modelo 2 Original",
xlab = "Distancia de Cook", ylab = "Residuos Estandarizados", col = "blue", pch = 19)
abline(h = c(-umbral_residuo, umbral_residuo), col = "red", lty = 2)
abline(v = umbral_cook, col = "red", lty = 2)
# Modelo 2 sin puntos influyentes y atípicos
cook_clean <- cooks.distance(modelocaso2_clean)
resid_clean <- rstudent(modelocaso2_clean)
plot(cook_clean, resid_clean, main = "Modelo 2 Sin Puntos Influyentes",
xlab = "Distancia de Cook", ylab = "Residuos Estandarizados", col = "blue", pch = 19)
abline(h = c(-umbral_residuo, umbral_residuo), col = "red", lty = 2)
abline(v = umbral_cook, col = "red", lty = 2)
library(dplyr)
library(caret)
library(knitr)
library(kableExtra)
# Obtener el resumen del modelo final
summary_modelocaso2clean <- summary(modelocaso2_clean)
# Convertir los coeficientes en un data frame para usar kableExtra
coeficientescaso2clean <- as.data.frame(summary_modelocaso2clean$coefficients)
colnames(coeficientescaso2) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
# Mostrar los coeficientes en una tabla con kableExtra
coeficientescaso2clean %>%
kable("html", caption = paste0("<center>", "Resumen de Coeficientes del Modelo Lineal", "</center>")) %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Estimate | Std. Error | t value | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | -31.496537 | 6.7471425 | -4.668130 | 3.2e-06 |
| areaconst | 1.618029 | 0.0443402 | 36.491261 | 0.0e+00 |
| estrato4 | 30.502597 | 4.3881589 | 6.951115 | 0.0e+00 |
| estrato5 | 59.219574 | 4.5574284 | 12.994077 | 0.0e+00 |
| estrato6 | 192.925831 | 5.9303736 | 32.531818 | 0.0e+00 |
| habitaciones | -12.636209 | 2.1783102 | -5.800923 | 0.0e+00 |
| parqueaderos | 50.777717 | 2.6249658 | 19.344144 | 0.0e+00 |
| banios | 28.964871 | 1.9892574 | 14.560645 | 0.0e+00 |
| Variable | Interpretación |
|---|---|
| (Intercept) | El intercepto es -31.50 (p < 0.001). Esto indica que el valor base de la variable dependiente (cuando todas las demás variables son cero) es -31.50. Se debe interpretar con precaución. |
| areaconst | Coeficiente de 1.62 (p < 0.001). Cada metro cuadrado adicional de área construida se asocia con un aumento de 16,180 COP en el precio por metro cuadrado. |
| estrato4 | Coeficiente de 30.50 (p < 0.001). Las viviendas en estrato 4 tienen un precio por metro cuadrado 305,000 COP mayor que las del estrato 3. |
| estrato5 | Coeficiente de 59.22 (p < 0.001). Las viviendas en estrato 5 tienen un precio por metro cuadrado 592,200 COP mayor que las del estrato 3. |
| estrato6 | Coeficiente de 192.93 (p < 0.001). Las viviendas en estrato 6 tienen un precio por metro cuadrado 1,929,300 COP mayor que las del estrato 3. |
| habitaciones | Coeficiente de -12.64 (p < 0.001). Cada habitación adicional se asocia con una disminución de 126,400 COP en el precio por metro cuadrado. |
| parqueaderos | Coeficiente de 50.78 (p < 0.001). Cada parqueadero adicional se asocia con un aumento de 507,800 COP por metro cuadrado en el precio. |
| banios | Coeficiente de 28.96 (p < 0.001). Cada baño adicional incrementa el precio por metro cuadrado en 289,600 COP. |
library(dplyr)
library(knitr)
library(kableExtra)
# Extraer las métricas del modelo 'modelocaso2_clean'
r2 <- summary(modelocaso2_clean)$r.squared
r2_ajustado <- summary(modelocaso2_clean)$adj.r.squared
f_statistic <- summary(modelocaso2_clean)$fstatistic[1]
df1 <- summary(modelocaso2_clean)$fstatistic[2]
df2 <- summary(modelocaso2_clean)$fstatistic[3]
p_value <- pf(f_statistic, df1, df2, lower.tail = FALSE)
# Crear un data frame con las métricas
metricas <- data.frame(
Métrica = c("R²", "R² Ajustado", "Estadístico F", "Grados de Libertad", "Valor p"),
Valor = c(
round(r2, 4),
round(r2_ajustado, 4),
round(f_statistic, 1),
paste(df1, "y", df2),
format.pval(p_value, digits = 3)
)
)
# Tabla con kableExtra
tabla_metricas <- metricas %>%
kable("html", caption = "<center>Métricas del Modelo </center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_metricas
| Métrica | Valor |
|---|---|
| R² | 0.8794 |
| R² Ajustado | 0.8791 |
| Estadístico F | 2798.4 |
| Grados de Libertad | 7 y 2686 |
| Valor p | <2e-16 |
A partir de las métricas del Modelo 2 se puede resaltar lo siguiente:
R² (0.8794): El coeficiente de determinación indica que aproximadamente el 87.94% de la variabilidad del precio por metro cuadrado (preciom) se explica mediante las variables predictoras del modelo (área construida, estrato, habitaciones, parqueaderos y baños). Este valor relativamente alto de R² sugiere que el modelo tiene un buen nivel de explicación respecto a la variable dependiente.
R² Ajustado (0.8791): Esta métrica ajusta el R² considerando el número de predictores del modelo. El hecho de que el R² ajustado sea muy cercano al R² original indica que las variables incluidas en el modelo son relevantes y que no se está sobreajustando el modelo.
Estadístico F (2798.4): El estadístico F evalúa la significancia general del modelo. Un valor tan alto indica que el modelo con las variables predictoras actuales es significativamente mejor para predecir el precio por metro cuadrado que un modelo sin predictores (solo con el intercepto).
Grados de Libertad (7 y 2686): El primer valor (7) representa el número de predictores del modelo en los cuales se incluye las categorías creadas para el estrato, mientras que el segundo valor (2686) corresponde al número de observaciones menos el número de parámetros estimados.
Valor p (<2e-16): Este valor extremadamente bajo indica que el modelo es altamente significativo. Existe una evidencia muy fuerte para rechazar la hipótesis nula de que las variables predictoras no tienen ningún efecto sobre el precio por metro cuadrado.
# Cargar librerías necesarias
library(ggplot2)
# Gráfico de residuos vs valores ajustados para el Modelo 2 limpio
ggplot(data = data.frame(fitted = fitted(modelocaso2_clean),
residuals = residuals(modelocaso2_clean)),
aes(x = fitted, y = residuals)) +
geom_point(color = "blue", alpha = 0.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
labs(title = "Residuos vs Valores Ajustados",
x = "Valores Ajustados", y = "Residuos") +
theme_minimal()+
theme(plot.title = element_text(hjust = 0.5))
El test de Breusch-Pagan arrojó un valor p muy bajo (< 2.2e-16), lo que indica que no se cumple el supuesto de homocedasticidad en el modelo
# Cargar la librería necesaria
library(lmtest)
# Realizar la prueba de Breusch-Pagan
bptest(modelocaso2_clean)
##
## studentized Breusch-Pagan test
##
## data: modelocaso2_clean
## BP = 658.72, df = 7, p-value < 2.2e-16
En este caso también se observa que con la revisión realizada no se cumplen el supuesto de normalidad de los residuos del modelo.
# Cargar librerías necesarias
library(ggplot2)
# Histograma de los residuos del Modelo 2 limpio
ggplot(data = data.frame(residuals = residuals(modelocaso2_clean)),
aes(x = residuals)) +
geom_histogram(aes(y = ..density..), bins = 30, fill = "blue", alpha = 0.5) +
geom_density(color = "red") +
labs(title = "Distribución de los Residuos (Modelo 2 Sin Puntos Influyentes)",
x = "Residuos", y = "Densidad") +
theme_minimal()
# Prueba de normalidad de Shapiro-Wilk para el Modelo 2 limpio
shapiro.test(residuals(modelocaso2_clean))
##
## Shapiro-Wilk normality test
##
## data: residuals(modelocaso2_clean)
## W = 0.98839, p-value = 5.148e-14
El resultado del test de Durbin-Watson muestra una autocorrelación positiva débil en los residuos (coeficiente de 0.15767) con un estadístico D-W de 1.684098. Un valor cercano a 2 indica baja autocorrelación, pero el valor p de 0 rechaza la hipótesis nula de no autocorrelación. Esto sugiere que los residuos del modelo no son completamente independientes.
# Cargar librería necesaria
library(car)
# Prueba de Durbin-Watson para el Modelo 2 sin puntos influyentes
durbinWatsonTest(modelocaso2_clean)
## lag Autocorrelation D-W Statistic p-value
## 1 0.15767 1.684098 0
## Alternative hypothesis: rho != 0
Dado que los supuestos no se cumplen, se realizarán distintas transformaciones en las variables de regresión con el objetivo de corregir los tres supuestos, esperando mejorar así la validez de las inferencias estadísticas del modelo.
# Cargar librerías necesarias
library(dplyr)
library(broom)
library(caret)
# Transformaciones logarítmicas necesarias
base2_clean <- base2_clean %>%
mutate(
log_preciom = log(preciom),
log_areaconst = log(areaconst)
)
# Definir número de folds para validación cruzada
set.seed(123) # Para reproducibilidad
control <- trainControl(method = "cv", number = 10)
# Modelo Log-Lin
modelo_log_lin <- train(
log_preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2_clean,
method = "lm",
trControl = control
)
# Modelo Lin-Log
modelo_lin_log <- train(
preciom ~ log_areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2_clean,
method = "lm",
trControl = control
)
# Modelo Log-Log
modelo_log_log <- train(
log_preciom ~ log_areaconst + estrato + habitaciones + parqueaderos + banios,
data = base2_clean,
method = "lm",
trControl = control
)
# Obtener los modelos finales ajustados
modelocaso2_clean <- modelo_cv_clean$finalModel
modelo_log_lin_final <- modelo_log_lin$finalModel
modelo_lin_log_final <- modelo_lin_log$finalModel
modelo_log_log_final <- modelo_log_log$finalModel
# Mostrar resúmenes de los modelos
#cat("\nModelo Log-Lin:\n")
#print(summary(modelocaso2_clean))
#cat("\nModelo Log-Lin:\n")
#print(summary(modelo_log_lin_final))
#cat("\nModelo Lin-Log:\n")
#print(summary(modelo_lin_log_final))
#cat("\nModelo Log-Log:\n")
#print(summary(modelo_log_log_final))
Los resultados de los coeficientes de los modelos indican que todos los modelos presentan relaciones estadísticamente significativas entre las variables y la variable dependiente, ya que los valores p son extremadamente bajos (< 0.001). Esto sugiere que las variables independientes incluidas en cada modelo tienen un impacto relevante en la predicción del precio por metro cuadrado.
# Cargar librerías necesarias
library(dplyr)
library(caret)
library(knitr)
library(kableExtra)
# Obtener el resumen de cada modelo
summary_modelo2 <- summary(modelocaso2_clean) # Ahora modelo_cv_clean es el Modelo 2
summary_modelo_log_lin <- summary(modelo_log_lin_final)
summary_modelo_lin_log <- summary(modelo_lin_log_final)
summary_modelo_log_log <- summary(modelo_log_log_final)
# Convertir los coeficientes en un data frame para usar kableExtra
coef_modelo2 <- as.data.frame(summary_modelo2$coefficients)
coef_log_lin <- as.data.frame(summary_modelo_log_lin$coefficients)
coef_lin_log <- as.data.frame(summary_modelo_lin_log$coefficients)
coef_log_log <- as.data.frame(summary_modelo_log_log$coefficients)
# Cambiar los nombres de las columnas para mayor claridad
colnames(coef_modelo2) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
colnames(coef_log_lin) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
colnames(coef_lin_log) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
colnames(coef_log_log) <- c("Estimación", "Error Estándar", "Valor t", "Pr(>|t|)")
# Función para mostrar las tablas con kableExtra
mostrar_tabla <- function(data, titulo) {
data %>%
kable("html", caption = paste0("<center>", titulo, "</center>"), digits = 4) %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
}
# Tablas de coeficientes para cada modelo
mostrar_tabla(coef_modelo2, "Resumen de Coeficientes del Modelo 2")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | -31.4965 | 6.7471 | -4.6681 | 0 |
| areaconst | 1.6180 | 0.0443 | 36.4913 | 0 |
| estrato4 | 30.5026 | 4.3882 | 6.9511 | 0 |
| estrato5 | 59.2196 | 4.5574 | 12.9941 | 0 |
| estrato6 | 192.9258 | 5.9304 | 32.5318 | 0 |
| habitaciones | -12.6362 | 2.1783 | -5.8009 | 0 |
| parqueaderos | 50.7777 | 2.6250 | 19.3441 | 0 |
| banios | 28.9649 | 1.9893 | 14.5606 | 0 |
mostrar_tabla(coef_log_lin, "Resumen de Coeficientes del Modelo Log-Lin")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 4.3311 | 0.0243 | 178.0046 | 0.0000 |
| areaconst | 0.0042 | 0.0002 | 25.9995 | 0.0000 |
| estrato4 | 0.2998 | 0.0158 | 18.9449 | 0.0000 |
| estrato5 | 0.5029 | 0.0164 | 30.6012 | 0.0000 |
| estrato6 | 0.7961 | 0.0214 | 37.2235 | 0.0000 |
| habitaciones | 0.0073 | 0.0079 | 0.9325 | 0.3512 |
| parqueaderos | 0.1135 | 0.0095 | 11.9930 | 0.0000 |
| banios | 0.0830 | 0.0072 | 11.5694 | 0.0000 |
mostrar_tabla(coef_lin_log, "Resumen de Coeficientes del Modelo Lin-Log")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | -716.4554 | 20.5301 | -34.8978 | 0 |
| log_areaconst | 193.1674 | 5.7485 | 33.6034 | 0 |
| estrato4 | 22.1239 | 4.5063 | 4.9095 | 0 |
| estrato5 | 38.7081 | 4.7492 | 8.1504 | 0 |
| estrato6 | 172.9784 | 6.1846 | 27.9694 | 0 |
| habitaciones | -17.7190 | 2.2899 | -7.7379 | 0 |
| parqueaderos | 55.4698 | 2.6639 | 20.8226 | 0 |
| banios | 27.5567 | 2.0769 | 13.2680 | 0 |
mostrar_tabla(coef_log_log, "Resumen de Coeficientes del Modelo Log-Log")
| Estimación | Error Estándar | Valor t | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 1.8814 | 0.0647 | 29.1002 | 0 |
| log_areaconst | 0.7012 | 0.0181 | 38.7345 | 0 |
| estrato4 | 0.2717 | 0.0142 | 19.1488 | 0 |
| estrato5 | 0.4196 | 0.0150 | 28.0552 | 0 |
| estrato6 | 0.6993 | 0.0195 | 35.9051 | 0 |
| habitaciones | -0.0328 | 0.0072 | -4.5537 | 0 |
| parqueaderos | 0.0901 | 0.0084 | 10.7459 | 0 |
| banios | 0.0481 | 0.0065 | 7.3532 | 0 |
Los resultados de las métricas de los modelos muestran que el Modelo 2 presenta el mayor coeficiente de determinación (R² = 0.8794) y un R² ajustado de 0.8791, indicando que este modelo explica mejor la variabilidad del precio por metro cuadrado en comparación con los demás modelos. Aunque los otros modelos también tienen un buen ajuste (R² entre 0.8293 y 0.873).
library(dplyr)
library(knitr)
library(kableExtra)
# Extraer las métricas de cada modelo
# Modelo 2 (modelo_cv_clean)
r2_modelo2 <- summary(modelo_cv_clean)$r.squared
r2_ajustado_modelo2 <- summary(modelo_cv_clean)$adj.r.squared
f_statistic_modelo2 <- summary(modelo_cv_clean)$fstatistic[1]
df1_modelo2 <- summary(modelo_cv_clean)$fstatistic[2]
df2_modelo2 <- summary(modelo_cv_clean)$fstatistic[3]
p_value_modelo2 <- pf(f_statistic_modelo2, df1_modelo2, df2_modelo2, lower.tail = FALSE)
# Modelo Log-Lin
r2_log_lin <- summary(modelo_log_lin_final)$r.squared
r2_ajustado_log_lin <- summary(modelo_log_lin_final)$adj.r.squared
f_statistic_log_lin <- summary(modelo_log_lin_final)$fstatistic[1]
df1_log_lin <- summary(modelo_log_lin_final)$fstatistic[2]
df2_log_lin <- summary(modelo_log_lin_final)$fstatistic[3]
p_value_log_lin <- pf(f_statistic_log_lin, df1_log_lin, df2_log_lin, lower.tail = FALSE)
# Modelo Lin-Log
r2_lin_log <- summary(modelo_lin_log_final)$r.squared
r2_ajustado_lin_log <- summary(modelo_lin_log_final)$adj.r.squared
f_statistic_lin_log <- summary(modelo_lin_log_final)$fstatistic[1]
df1_lin_log <- summary(modelo_lin_log_final)$fstatistic[2]
df2_lin_log <- summary(modelo_lin_log_final)$fstatistic[3]
p_value_lin_log <- pf(f_statistic_lin_log, df1_lin_log, df2_lin_log, lower.tail = FALSE)
# Modelo Log-Log
r2_log_log <- summary(modelo_log_log_final)$r.squared
r2_ajustado_log_log <- summary(modelo_log_log_final)$adj.r.squared
f_statistic_log_log <- summary(modelo_log_log_final)$fstatistic[1]
df1_log_log <- summary(modelo_log_log_final)$fstatistic[2]
df2_log_log <- summary(modelo_log_log_final)$fstatistic[3]
p_value_log_log <- pf(f_statistic_log_log, df1_log_log, df2_log_log, lower.tail = FALSE)
# Crear un data frame con las métricas de todos los modelos
metricas_modelo <- data.frame(
Métrica = c("R²", "R² Ajustado", "Estadístico F", "Grados de Libertad", "Valor p"),
`Modelo 2` = c(round(r2_modelo2, 4), round(r2_ajustado_modelo2, 4), round(f_statistic_modelo2, 1),
paste(df1_modelo2, "y", df2_modelo2), format.pval(p_value_modelo2, digits = 3)),
`Modelo Log-Lin` = c(round(r2_log_lin, 4), round(r2_ajustado_log_lin, 4), round(f_statistic_log_lin, 1),
paste(df1_log_lin, "y", df2_log_lin), format.pval(p_value_log_lin, digits = 3)),
`Modelo Lin-Log` = c(round(r2_lin_log, 4), round(r2_ajustado_lin_log, 4), round(f_statistic_lin_log, 1),
paste(df1_lin_log, "y", df2_lin_log), format.pval(p_value_lin_log, digits = 3)),
`Modelo Log-Log` = c(round(r2_log_log, 4), round(r2_ajustado_log_log, 4), round(f_statistic_log_log, 1),
paste(df1_log_log, "y", df2_log_log), format.pval(p_value_log_log, digits = 3))
)
# Crear la tabla con kableExtra
tabla_metricas_modelos <- metricas_modelo %>%
kable("html", caption = "<center>Métricas de Todos los Modelos</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_metricas_modelos
| Métrica | Modelo.2 | Modelo.Log.Lin | Modelo.Lin.Log | Modelo.Log.Log |
|---|---|---|---|---|
| R² | 0.8794 | 0.8293 | 0.873 | 0.8629 |
| R² Ajustado | 0.8791 | 0.8288 | 0.8727 | 0.8625 |
| Estadístico F | 2798.4 | 1863.8 | 2638.1 | 2414.9 |
| Grados de Libertad | 7 y 2686 | 7 y 2686 | 7 y 2686 | 7 y 2686 |
| Valor p | <2e-16 | <2e-16 | <2e-16 | <2e-16 |
En este caso se presenta que las transformaciones realizadas no garantizan el cumplimiento de ninguno de los supuestos del modelo, con lo cual es pertinente contemplar métodos alternativos como modelos no lineales, modelos mas robustos o técnicas de machine learning.
library(lmtest)
library(dplyr)
# Función para realizar las pruebas y obtener los valores p
evaluar_supuestos <- function(modelo) {
residuos <- residuals(modelo)
# Verificar residuos válidos
if (any(is.na(residuos)) || any(is.infinite(residuos))) {
stop("Los residuos contienen NA o valores infinitos.")
}
# Normalidad de los residuos
normalidad <- shapiro.test(residuos)$p.value
# Homocedasticidad con el modelo completo
homocedasticidad <- bptest(modelo)$p.value
# Autocorrelación de los residuos con el modelo completo
autocorrelacion <- dwtest(modelo)$p.value
return(c(normalidad, homocedasticidad, autocorrelacion))
}
# Aplicar la función a cada modelo
resultados_modelo2 <- evaluar_supuestos(modelocaso2_clean) # Ajuste en el nombre del modelo
resultados_log_lin <- evaluar_supuestos(modelo_log_lin_final)
resultados_lin_log <- evaluar_supuestos(modelo_lin_log_final)
resultados_log_log <- evaluar_supuestos(modelo_log_log_final)
# Crear la tabla de resultados
tabla_resultados <- data.frame(
Modelo = c("Modelo 2", "Log-Lineal", "Lineal-Log", "Log-Log"),
Normalidad = c(resultados_modelo2[1], resultados_log_lin[1], resultados_lin_log[1], resultados_log_log[1]),
Homocedasticidad = c(resultados_modelo2[2], resultados_log_lin[2], resultados_lin_log[2], resultados_log_log[2]),
Autocorrelación = c(resultados_modelo2[3], resultados_log_lin[3], resultados_lin_log[3], resultados_log_log[3])
)
# Función para formatear los valores p a 5 decimales
formatear_p_valores <- function(x) {
return(sprintf("%.9f", as.numeric(x)))}
# Aplicar el formateo a las columnas de valores p
tabla_resultados$Normalidad <- formatear_p_valores(tabla_resultados$Normalidad)
tabla_resultados$Homocedasticidad <- formatear_p_valores(tabla_resultados$Homocedasticidad)
tabla_resultados$Autocorrelación <- formatear_p_valores(tabla_resultados$Autocorrelación)
# Crear la tabla con kableExtra
tabla_resultados <- tabla_resultados %>%
kable("html", caption = "<center>Pruebas de Supuestos Estadísticos por Modelo</center>") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
# Mostrar la tabla
tabla_resultados
| Modelo | Normalidad | Homocedasticidad | Autocorrelación |
|---|---|---|---|
| Modelo 2 | 0.000000000 | 0.000000000 | 0.000000000 |
| Log-Lineal | 0.000005545 | 0.000000000 | 0.000000000 |
| Lineal-Log | 0.000000000 | 0.000000000 | 0.000000000 |
| Log-Log | 0.000000175 | 0.000000000 | 0.000000000 |
A continuación se realizan las predicciones de la solicitud. Es importante tener en cuenta que, a pesar de que el modelo no cumplió con los supuestos estadísticos (como la normalidad de los residuos y homocedasticidad),por lo cual los resultados deben interpretarse con cuidado, ya que posibles sesgos o errores en el modelo podrían afectar la precisión de las predicciones.
library(dplyr)
library(knitr)
library(kableExtra)
library(scales) # Para formato de pesos
# Crear las solicitudes con las variables adecuadas
solicitud1 <- data.frame(
areaconst = 300,
estrato3 = 0,
estrato4 = 0,
estrato5 = 1, # estrato 5
estrato6 = 0,
parqueaderos = 3,
banios = 3,
habitaciones = 5
)
solicitud2 <- data.frame(
areaconst = 300,
estrato3 = 0,
estrato4 = 0,
estrato5 = 0,
estrato6 = 1,# estrato 6
parqueaderos = 3,
banios = 3,
habitaciones = 5
)
# Realizar las predicciones con el modelo 'modelocaso2_clean'
prediccion1 <- predict(modelocaso2_clean, newdata = solicitud1)
resultado1 <- prediccion1 * 200 * 10000 # Sin logaritmos
prediccion2 <- predict(modelocaso2_clean, newdata = solicitud2)
resultado2 <- prediccion2 * 200 * 10000 # Sin logaritmos
# Crear la tabla de resultados
resultados <- data.frame(
Solicitud = c("Solicitud 1", "Solicitud 2"),
Predicción = c(resultado1, resultado2)
)
# Formatear las predicciones como pesos colombianos
resultados$Predicción <- dollar(resultados$Predicción, scale = 1, prefix = "$", big.mark = ".", decimal.mark = ",")
# Mostrar la tabla con kableExtra
resultados %>%
kable("html") %>%
kable_styling(full_width = F, bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| Solicitud | Predicción |
|---|---|
| Solicitud 1 | $1.378.357.094 |
| Solicitud 2 | $1.645.769.607 |
A continuación se muestran las viviendas seleccionadas teniendo en cuenta los criterios iniciales con la restricción presupuestal, se priorizaron viviendas con características similares a las solicitadas por el cliente.
# Cargar las librerías necesarias
library(dplyr)
library(kableExtra)
# Crear la tabla con las viviendas seleccionadas
tabla_viviendas <- ofertas_filtradas %>%
mutate(PrecioTotal = preciom * areaconst * 10000) %>%
select(
PrecioTotal,
Área = areaconst,
Estrato = estrato,
Habitaciones = habitaciones,
Parqueaderos = parqueaderos,
Baños = banios,
Longitud = longitud,
Latitud = latitud
) %>%
mutate(PrecioTotal = format(PrecioTotal, big.mark = ".", scientific = FALSE))
# Mostrar la tabla con kableExtra
tabla_viviendas %>%
kable(
caption = "<center>Tabla de Viviendas Seleccionadas</center>",
col.names = c("Precio Total (COP)", "Área (m²)", "Estrato", "Habitaciones",
"Parqueaderos", "Baños", "Longitud", "Latitud"),
format = "html",
align = "c"
) %>%
kable_styling(
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE,
position = "center"
)
| Precio Total (COP) | Área (m²) | Estrato | Habitaciones | Parqueaderos | Baños | Longitud | Latitud |
|---|---|---|---|---|---|---|---|
| 540.000.000 | 180.00 | 5 | 4 | 1 | 3 | -76.54300 | 3.38600 |
| 846.300.000 | 217.00 | 4 | 5 | 2 | 5 | -76.53100 | 3.43100 |
| 840.000.000 | 200.00 | 4 | 5 | 1 | 5 | -76.53339 | 3.43224 |
| 696.698.400 | 217.04 | 5 | 5 | 1 | 4 | -76.55066 | 3.40862 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 2 | -76.54803 | 3.40829 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 3 | -76.54807 | 3.40829 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 2 | -76.54807 | 3.40831 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 2 | -76.54807 | 3.40831 |
| 772.200.000 | 198.00 | 5 | 3 | 1 | 4 | -76.52030 | 3.38197 |
| 579.200.000 | 181.00 | 5 | 4 | 2 | 3 | -76.52984 | 3.38359 |
| 732.600.000 | 198.00 | 4 | 4 | 1 | 4 | -76.53076 | 3.38071 |
| 810.600.000 | 193.00 | 4 | 3 | 2 | 4 | -76.53403 | 3.38139 |
| 752.400.000 | 198.00 | 5 | 4 | 2 | 4 | -76.53503 | 3.38250 |
| 840.000.000 | 200.00 | 5 | 3 | 2 | 4 | -76.53611 | 3.45774 |
| 627.000.000 | 209.00 | 5 | 5 | 1 | 4 | -76.54347 | 3.39744 |
| 540.000.000 | 180.00 | 5 | 3 | 1 | 2 | -76.54158 | 3.38230 |
| 695.400.000 | 183.00 | 4 | 3 | 1 | 3 | -76.52862 | 3.39056 |
| 774.000.000 | 180.00 | 5 | 3 | 2 | 3 | -76.54072 | 3.41327 |
Se llevo a cabo un análisis de la ubicación de las viviendas registradas por la empresa C&A (Casas y Apartamentos) en el cual se identificó que existen numerosas viviendas que tienen erroneamente asignada la asignación de su zona, lo cual puede llevar a confusiones y malas asesorias por parte de los agentes comerciales de la empresa, por lo cual es pertinente diseñar estrategias para corregir y depurar la base de datos, asegurando que cada vivienda esté correctamente asignada a su zona o localidad.
Para ayudar a la asesoría de las dos solicitudes realizadas, se realizó un modelo de regresión lineal múltiple tanto para las casas de la Zona Norte como para los Apartamentos de la Zona Sur de la ciudad para predecir el precio de ambas viviendas con las características deseadas mediante el uso de la cantidad de habitaciones, parqueaderos, área construida y el estrato de la vivienda.Inicialmente ambos modelos obtuvieron un buen ajuste de bondad R², aunque al comprobar los supuestos del modelo se encontró que no se cumplían en su totalidad por lo cual los resultados del modelo podrían no ser completamente confiables al momento de realizar predicciones.
A pesar de los ajustes realizados, los nuevos modelos para las casas en la Zona Norte y los apartamentos en la Zona Sur continuaron sin cumplir completamente con los supuestos estadísticos necesarios para garantizar la validez de los resultados. Persistieron problemas relacionados con la normalidad de los residuos, la homocedasticidad y la autocorrelación de los residuos. Esto puede derivar en diversas problemáticas, como una incapacidad del modelo para capturar adecuadamente los patrones de los datos y una precisión variable en las estimaciones según el rango de los datos analizados.
Por esta razón, las estimaciones del precio realizadas tienen poco valor para la toma de decisiones, ya que podrían no reflejar adecuadamente las condiciones reales del mercado inmobiliario.
Ante esta situación, se recomienda considerar enfoques alternativos, como modelos no lineales o técnicas de machine learning, que podrían adaptarse mejor a la complejidad de los datos y ofrecer predicciones más confiables. Además, es fundamental establecer un proceso continuo de revisión y validación de la base de datos para evitar futuros problemas con la asignación de zonas y asegurar la calidad de la información utilizada por la empresa C&A en sus procesos comerciales.
A continuación se presentan las distintas recomendaciones de vivienda de las dos solicitudes teniendo en cuenta que no deben exceder el presupuesto asignado y deben presentar características similares en cuanto al estrato, habitaciones, parqueaderos, área y baños.
Para la primera solicitud, se seleccionaron un total de 8 viviendas que cumplen con la restricción presupuestal del cliente de 350 millones de pesos. Es importante destacar que, para ampliar las opciones disponibles, se aplicó una tolerancia en todas las características de la vivienda, incluidas el área construida, el estrato, el número de parqueaderos, baños y habitaciones. Esto permitió identificar ofertas que, aunque no coincidían exactamente con los parámetros iniciales, se encontraban dentro de un rango aceptable para el cliente.
Además, se verificó que todas las viviendas seleccionadas contaran con coordenadas válidas para su correcta visualización en el mapa interactivo, ofreciendo así una perspectiva clara de su ubicación geográfica en la ciudad.
# Cargar librerías necesarias
library(dplyr)
library(leaflet)
# Solicitud de vivienda con las características deseadas
solicitud1 <- data.frame(
areaconst = 200,
estrato = 4,
parqueaderos = 1,
banios = 2,
habitaciones = 4
)
# Definir rangos de tolerancia para cada característica
tolerancia <- data.frame(
areaconst = 50, # ±50 m²
estrato = 1, # ±1 estrato
parqueaderos = 1, # ±1 parqueadero
banios = 1, # ±1 baño
habitaciones = 1 # ±1 habitación
)
# Asegurarse de que 'estrato' sea numérico en el conjunto de datos base1_clean
base1_clean <- base1_clean %>%
mutate(estrato = as.numeric(as.character(estrato)))
# Filtrar viviendas que cumplen con las características cercanas y la restricción de precio
ofertas_filtradas <- base1_clean %>%
filter(
between(areaconst, solicitud1$areaconst - tolerancia$areaconst, solicitud1$areaconst + tolerancia$areaconst),
between(estrato, solicitud1$estrato - tolerancia$estrato, solicitud1$estrato + tolerancia$estrato),
between(parqueaderos, solicitud1$parqueaderos - tolerancia$parqueaderos, solicitud1$parqueaderos + tolerancia$parqueaderos),
between(banios, solicitud1$banios - tolerancia$banios, solicitud1$banios + tolerancia$banios),
between(habitaciones, solicitud1$habitaciones - tolerancia$habitaciones, solicitud1$habitaciones + tolerancia$habitaciones),
preciom * areaconst * 10000 <= 350000000
) %>%
filter(!is.na(longitud) & !is.na(latitud)) %>%
mutate(
longitud = as.numeric(longitud),
latitud = as.numeric(latitud)
)
# Crear el mapa interactivo con leaflet solo si hay ofertas disponibles
if (nrow(ofertas_filtradas) > 0) {
leaflet(ofertas_filtradas) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
popup = ~paste0(
"<strong>Precio Total:</strong> ", format(round(preciom * areaconst * 10000, 0), big.mark = ",", scientific = FALSE), " COP<br>",
"<strong>Área:</strong> ", areaconst, " m²<br>",
"<strong>Estrato:</strong> ", estrato, "<br>",
"<strong>Habitaciones:</strong> ", habitaciones, "<br>",
"<strong>Parqueaderos:</strong> ", parqueaderos, "<br>",
"<strong>Baños:</strong> ", banios
),
color = "blue",
radius = 6,
fillOpacity = 0.7
)
} else {
message("No hay viviendas seleccionadas para mostrar en el mapa.")
}
# Mostrar estadísticas de las viviendas seleccionadas
#cat("Viviendas filtradas:", nrow(ofertas_filtradas), "\n")
#cat("Viviendas con coordenadas válidas:", nrow(ofertas_filtradas), "\n")
# Cargar las librerías necesarias
library(dplyr)
library(kableExtra)
# Crear la tabla con las viviendas seleccionadas
tabla_viviendas <- ofertas_filtradas %>%
mutate(PrecioTotal = preciom * areaconst * 10000) %>%
select(
PrecioTotal,
Área = areaconst,
Estrato = estrato,
Habitaciones = habitaciones,
Parqueaderos = parqueaderos,
Baños = banios,
Longitud = longitud,
Latitud = latitud
) %>%
mutate(PrecioTotal = format(PrecioTotal, big.mark = ".", scientific = FALSE))
# Mostrar la tabla con kableExtra
tabla_viviendas %>%
kable(
caption = "<center>Tabla de Viviendas Seleccionadas</center>",
col.names = c("Precio Total (COP)", "Área (m²)", "Estrato", "Habitaciones",
"Parqueaderos", "Baños", "Longitud", "Latitud"),
format = "html",
align = "c"
) %>%
kable_styling(
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE,
position = "center"
)
| Precio Total (COP) | Área (m²) | Estrato | Habitaciones | Parqueaderos | Baños | Longitud | Latitud |
|---|---|---|---|---|---|---|---|
| 330.000.000 | 150 | 3 | 4 | 1 | 2 | -76.53200 | 3.45200 |
| 296.000.000 | 160 | 3 | 5 | 1 | 2 | -76.48800 | 3.47200 |
| 255.000.000 | 150 | 3 | 4 | 1 | 2 | -76.48600 | 3.46800 |
| 270.000.000 | 150 | 3 | 4 | 1 | 2 | -76.49575 | 3.47561 |
| 256.000.000 | 160 | 3 | 3 | 1 | 2 | -76.54015 | 3.42681 |
| 315.000.000 | 180 | 3 | 3 | 1 | 2 | -76.49500 | 3.46589 |
| 320.000.000 | 160 | 3 | 4 | 1 | 3 | -76.49358 | 3.46639 |
| 184.000.000 | 160 | 3 | 3 | 1 | 1 | -76.49929 | 3.47094 |
Respecto a la segunda solicitud se identificaron un total de 18 viviendas que podrían ser relevantes para el cliente, teniendo en cuenta un margen de tolerancia con respecto a las características originales.
# Cargar librerías necesarias
library(dplyr)
library(leaflet)
# Solicitud de vivienda con las características deseadas
solicitud1 <- data.frame(
areaconst = 300,
estrato = 5,
parqueaderos = 3,
banios = 3,
habitaciones = 5
)
# Definir rangos de tolerancia para cada característica
tolerancia <- data.frame(
areaconst = 120, # ± 120 m²
estrato = 2, # ± 2 estratos
parqueaderos = 2, # ± 2 parqueaderos
banios = 2, # ± 2 baños
habitaciones = 2 # ± 2 habitaciones
)
# Asegurarse de que 'estrato' sea numérico en el conjunto de datos base2_clean
base2_clean <- base2_clean %>%
mutate(estrato = as.numeric(as.character(estrato)))
# Filtrar viviendas que cumplen con las características cercanas y la restricción de precio
ofertas_filtradas <- base2_clean %>%
filter(
between(areaconst, solicitud1$areaconst - tolerancia$areaconst, solicitud1$areaconst + tolerancia$areaconst),
between(estrato, solicitud1$estrato - tolerancia$estrato, solicitud1$estrato + tolerancia$estrato),
between(parqueaderos, solicitud1$parqueaderos - tolerancia$parqueaderos, solicitud1$parqueaderos + tolerancia$parqueaderos),
between(banios, solicitud1$banios - tolerancia$banios, solicitud1$banios + tolerancia$banios),
between(habitaciones, solicitud1$habitaciones - tolerancia$habitaciones, solicitud1$habitaciones + tolerancia$habitaciones),
preciom * areaconst * 10000 <= 850000000
) %>%
filter(!is.na(longitud) & !is.na(latitud)) %>%
mutate(
longitud = as.numeric(longitud),
latitud = as.numeric(latitud)
)
# Crear el mapa interactivo con leaflet solo si hay ofertas disponibles
if (nrow(ofertas_filtradas) > 0) {
leaflet(ofertas_filtradas) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
popup = ~paste0(
"<strong>Precio Total:</strong> ", format(round(preciom * areaconst * 10000, 0), big.mark = ",", scientific = FALSE), " COP<br>",
"<strong>Área:</strong> ", areaconst, " m²<br>",
"<strong>Estrato:</strong> ", estrato, "<br>",
"<strong>Habitaciones:</strong> ", habitaciones, "<br>",
"<strong>Parqueaderos:</strong> ", parqueaderos, "<br>",
"<strong>Baños:</strong> ", banios
),
color = "blue",
radius = 6,
fillOpacity = 0.7
)
} else {
message("No hay viviendas seleccionadas para mostrar en el mapa.")
}
# Cargar las librerías necesarias
library(dplyr)
library(kableExtra)
# Crear la tabla con las viviendas seleccionadas
tabla_viviendas <- ofertas_filtradas %>%
mutate(PrecioTotal = preciom * areaconst * 10000) %>%
select(
PrecioTotal,
Área = areaconst,
Estrato = estrato,
Habitaciones = habitaciones,
Parqueaderos = parqueaderos,
Baños = banios,
Longitud = longitud,
Latitud = latitud
) %>%
mutate(PrecioTotal = format(PrecioTotal, big.mark = ".", scientific = FALSE))
# Mostrar la tabla con kableExtra
tabla_viviendas %>%
kable(
caption = "<center>Tabla de Viviendas Seleccionadas</center>",
col.names = c("Precio Total (COP)", "Área (m²)", "Estrato", "Habitaciones",
"Parqueaderos", "Baños", "Longitud", "Latitud"),
format = "html",
align = "c"
) %>%
kable_styling(
bootstrap_options = c("striped", "hover", "condensed", "responsive"),
full_width = FALSE,
position = "center"
)
| Precio Total (COP) | Área (m²) | Estrato | Habitaciones | Parqueaderos | Baños | Longitud | Latitud |
|---|---|---|---|---|---|---|---|
| 540.000.000 | 180.00 | 5 | 4 | 1 | 3 | -76.54300 | 3.38600 |
| 846.300.000 | 217.00 | 4 | 5 | 2 | 5 | -76.53100 | 3.43100 |
| 840.000.000 | 200.00 | 4 | 5 | 1 | 5 | -76.53339 | 3.43224 |
| 696.698.400 | 217.04 | 5 | 5 | 1 | 4 | -76.55066 | 3.40862 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 2 | -76.54803 | 3.40829 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 3 | -76.54807 | 3.40829 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 2 | -76.54807 | 3.40831 |
| 592.000.000 | 185.00 | 5 | 4 | 2 | 2 | -76.54807 | 3.40831 |
| 772.200.000 | 198.00 | 5 | 3 | 1 | 4 | -76.52030 | 3.38197 |
| 579.200.000 | 181.00 | 5 | 4 | 2 | 3 | -76.52984 | 3.38359 |
| 732.600.000 | 198.00 | 4 | 4 | 1 | 4 | -76.53076 | 3.38071 |
| 810.600.000 | 193.00 | 4 | 3 | 2 | 4 | -76.53403 | 3.38139 |
| 752.400.000 | 198.00 | 5 | 4 | 2 | 4 | -76.53503 | 3.38250 |
| 840.000.000 | 200.00 | 5 | 3 | 2 | 4 | -76.53611 | 3.45774 |
| 627.000.000 | 209.00 | 5 | 5 | 1 | 4 | -76.54347 | 3.39744 |
| 540.000.000 | 180.00 | 5 | 3 | 1 | 2 | -76.54158 | 3.38230 |
| 695.400.000 | 183.00 | 4 | 3 | 1 | 3 | -76.52862 | 3.39056 |
| 774.000.000 | 180.00 | 5 | 3 | 2 | 3 | -76.54072 | 3.41327 |