Maria comenzó como agente de bienes raíces en Cali hace 10 años. Después de laborar dos años para una empresa nacional, se traslado a Bogotá y trabajó para otra agencia de bienes raíces. Sus amigos y familiares la convencieron de que con su experiencia y conocimientos del negocio debía abrir su propia agencia. Terminó por adquirir la licencia de intermediario y al poco tiempo fundó su propia compañía, C&A (Casas y Apartamentos) en Cali. Santiago y Lina, dos vendedores de la empresa anterior aceptaron trabajar en la nueva compaña. En la actualidad ocho agentes de bienes raíces colaboran con ella en C&A.
Actualmente las ventas de bienes raíces en Cali se han visto disminuidas de manera significativa en lo corrido del año. Durante este periodo muchas instituciones bancarias de ahorro y vivienda están prestando grandes sumas de dinero para la industria y la construcción comercial y residencial. Cuando el efecto producto de las tensiones políticas y sociales disminuya, se espera que la actividad económica de este sector se reactive.
Hace dos días, María recibió una carta solicitando asesoría para la compra de dos viviendas por parte de una compañía internacional que desea ubicar a dos de sus empleados con sus familias en la ciudad. Las solicitudes incluyen las siguientes condiciones:
| Características | Vivienda 1 | Vivienda 2 |
|---|---|---|
| Tipo | Casa | Apartamento |
| área construida | 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 |
Ayude a María a responder la solicitud, mediante técnicas modelación que usted conoce. Ella requiere le envíe un informe ejecutivo donde analice los dos casos y sus recomendaciones (Informe). Como soporte del informe debe anexar las estimaciones, validaciones y comparación de modelos requeridos (Anexos) .
Los datos de los tres últimos meses se adjuntan en la base que puede obtener con el siguiente código en R
| 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 : 20 de Julio, alamos,.. |
| longitud | coordenada geográfica |
| latitud | coordenada geográfica |
En la Actividad 1 del curso de Modelos estadísticos para la Toma de Decisiones se realizó un análisis exploratorio para identificar y tratar las inconsistencias del dataset. Para esta segunda actividad se utiliza la base de datos limpia, se carga para iniciar con el primer paso de este ejercicio:
# Cargar librería
library(readxl)
# Leer archivo Excel
datos <- read_excel("C:/Users/LORE SALAS/Desktop/Actividad_2/vivienda_5.xlsx")
# Ver las primeras filas
head(datos)
# A tibble: 6 × 13
id zona estrato tipo barrio piso preciom areaconst parqueaderos banios
<dbl> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1147 Zona O… 3 Casa 20 de… 3 250 70 1 3
2 1169 Zona O… 3 Casa 20 de… 3 320 120 1 2
3 1350 Zona O… 3 Casa 20 de… 3 350 220 2 2
4 5992 Zona S… 4 Casa 3 de … 2 400 280 3 5
5 1212 Zona N… 5 Apar… acopi 1 260 90 1 2
6 1724 Zona N… 5 Apar… acopi 1 240 87 1 3
# ℹ 3 more variables: habitaciones <dbl>, longitud <dbl>, latitud <dbl>
Realice un filtro a la base de datos e incluya solo las ofertas de : base1: casas, de la zona norte de la ciudad. Presente los primeros 3 registros de las bases y algunas tablas que comprueben la consulta. (Adicional un mapa con los puntos de las bases. Discutir si todos los puntos se ubican en la zona correspondiente o se presentan valores en otras zonas, por que?).
Solución
library(tidyverse)
# Filtrar casas de la zona norte
base1 <- datos %>%
filter(tipo == "Casa", zona == "Zona Norte")
#Mostrar los primeros 3 registros
head(base1, 3)
# A tibble: 3 × 13
id zona estrato tipo barrio piso preciom areaconst parqueaderos banios
<dbl> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 1209 Zona N… 5 Casa acopi 2 320 150 2 4
2 1592 Zona N… 5 Casa acopi 2 780 350 2 3
3 4057 Zona N… 6 Casa acopi 2 750 350 2 5
# ℹ 3 more variables: habitaciones <dbl>, longitud <dbl>, latitud <dbl>
Se realiza un filtro de la base de datos como se menciona en la descripción del ejercicio. En los resultados se puede evidenciar que la base de datos quedó filtrada por tipo “Casa” y “Zona Norte” de la ciudad, mostrando las 3 primeras observaciones.
Se realiza una tabla con el fin de identificar que se realizó el filtro correctamente. Luego, se realiza una visualización del mapa con los puntos bases para determinar si en realidad pertenecen a la zona norte.
library(dplyr)
library(DT)
tabla_sin_frecuencia <- base1 %>%
select(zona, estrato, tipo, barrio, piso, preciom, areaconst, parqueaderos, banios, habitaciones, longitud, latitud) %>%
distinct()
datatable(
tabla_sin_frecuencia,
options = list(
pageLength = 5,
autoWidth = TRUE,
scrollX = TRUE,
lengthMenu = c(5, 10, 20, 50),
dom = 'Bfrtip',
columnDefs = list(list(className = 'dt-center', targets = "_all")) # 🔹 Centrar todo
),
class = "cell-border stripe hover",
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: center;
color: #B0006D; font-size: 18px; font-weight: bold;',
"Tabla de Variables Inmobiliarias"
)
) %>%
formatStyle(
names(tabla_sin_frecuencia),
color = 'black'
) %>%
formatStyle(
0,
backgroundColor = '#B0006D',
color = 'white',
fontWeight = 'bold'
)
Esta tabla comprueba la consulta, ya que se logra evidenciar el
filtro con respecto a los atributos zona y tipo (casa).
library(dplyr)
library(leaflet)
# Filtrar registros con coordenadas
base_map <- base1 %>%
filter(!is.na(longitud), !is.na(latitud))
# Paleta de colores por zona
zonas <- unique(base_map$zona)
pal <- colorFactor(palette = "Set1", domain = zonas)
# Mapa interactivo
leaflet(base_map) %>%
addTiles() %>%
addCircleMarkers(
lng = ~longitud,
lat = ~latitud,
radius = 5,
color = ~pal(zona),
stroke = FALSE,
fillOpacity = 0.8,
label = ~paste0(
"Zona: ", zona, "<br>",
"Barrio: ", barrio, "<br>",
"Tipo: ", tipo, "<br>",
"Latitud: ", latitud, "<br>",
"Longitud: ", longitud
)
) %>%
addLegend(
"bottomright",
pal = pal,
values = ~zona,
title = "Zona",
opacity = 1
)
Análizando el mapa se puede vizualizar que aunque la mayoría de puntos se encuentran en la zona norte de Cali, también hay varios puntos ubicados en el centro y sur de la ciudad. Esto indica que no todos los puntos pretenecen geográficamente a la zona indicada. La razón puede ser debido a la asignación incorrecta de la zona sin validar la ubicación real de cada punto en el territorio. En consecuencia, se presentan valores fuera de la zona correspondiente, lo cual evidencia la importancia de emplear polígonos oficiales de división territorial o una verificación más rigurosa de las coordenadas para evitar inconsistencias en la categorización espacial.
En este caso, la corrección de los puntos fuera de la zona no se lleva a cabo porque aún no se ha abordado el procedimiento de georreferenciación y validación espacial en el desarrollo de los cursos, lo cual limita la aplicación de técnicas avanzadas para ajustar las ubicaciones. Una posible alternativa sería realizar la verificación mediante latitud y longitud; sin embargo, esta opción presenta la desventaja de que requiere un control detallado de las coordenadas y aumenta el riesgo de introducir nuevos errores si no se cuenta con bases oficiales de referencia o herramientas adecuadas, por lo que no se considera pertinente en esta etapa del análisis.
Realice un análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio de la casa) en función del área construida, estrato, numero de baños, numero de habitaciones y zona donde se ubica la vivienda. Use gráficos interactivos con el paquete plotly e interprete los resultados.
library(dplyr)
library(plotly)
# 1. Relación entre precio y área construida
fig1 <- plot_ly(datos, x = ~areaconst, y = ~preciom,
type = "scatter", mode = "markers",
color = ~estrato,
text = ~paste("Estrato:", estrato,
"<br>Baños:", banios,
"<br>Habitaciones:", habitaciones,
"<br>Zona:", zona)) %>%
layout(title = list(text = "<b>Precio vs Área construida</b>"),
xaxis = list(title = "Área construida (m²)"),
yaxis = list(title = "Precio (millones)"))
# Mostrar gráfico
fig1
El gráfico de dispersión muestra la relación entre el área construida y el precio de las viviendas, diferenciadas por el número de habitaciones. En general, se observa que a mayor área construida el precio tiende a incrementarse, lo que refleja una relación positiva entre estas variables. Asimismo, las viviendas con menos habitaciones (3) se concentran en áreas más pequeñas y precios bajos, mientras que las de mayor número de habitaciones (4, 5 y 6) tienden a ubicarse en construcciones más amplias y con precios más altos. Sin embargo, la dispersión de los puntos indica que el precio no depende únicamente del tamaño ni de las habitaciones, sino también de otros factores como la ubicación o la calidad de los inmuebles, además de la presencia de algunos valores atípicos.
fig2 <- plot_ly(datos, x = ~estrato, y = ~preciom,
type = "box",
color = ~estrato) %>%
layout(title = "Distribución del precio por estrato",
xaxis = list(title = "Estrato"),
yaxis = list(title = "Precio (millones)"))
# Mostrar gráfico
fig2
La gráfica muestra la distribución del precio de las viviendas según el estrato, evidenciando una clara relación positiva: a mayor estrato, mayor precio. En los estratos bajos (3 y 4), los precios tienden a concentrarse en valores menores, con medianas alrededor de 150 a 250 millones, mientras que en los estratos altos (5 y 6) los precios son considerablemente más elevados, con medianas que superan los 350 y 600 millones respectivamente. Además, se observan más valores atípicos en los estratos 3 y 4, lo que indica que, aunque la mayoría de viviendas en esos estratos son más económicas, también existen algunas ofertas con precios elevados que se salen de la tendencia general. En contraste, en el estrato 6 los precios presentan mayor dispersión, pero en un rango claramente superior, lo que refleja la exclusividad y variabilidad del mercado en este nivel socioeconómico.
# 3. Precio vs Número de baños (banios = discreta, precio continua) -> scatter plot
fig3 <- plot_ly(datos, x = ~banios, y = ~preciom,
type = "scatter", mode = "markers",
color = ~as.factor(banios),
marker = list(size = 8, opacity = 0.6)) %>%
layout(title = "Precio vs Número de baños",
xaxis = list(title = "Número de baños"),
yaxis = list(title = "Precio (millones)"))
# Mostrar gráfico
fig3
En esta gráfica de dispersión se analiza la relación entre el precio de la vivienda y el número de baños. Se observa que las casas con 2, 3, 4 y 5 baños abarcan un rango amplio de precios, desde aproximadamente 150 hasta 900 millones, lo que indica que el precio no depende únicamente de esta variable. Sin embargo, se percibe una ligera tendencia a que las viviendas con mayor número de baños se ubiquen con más frecuencia en los rangos altos de precio, aunque también existen muchos casos de precios intermedios. En conclusión, el número de baños sí tiene cierta asociación con el precio, pero no es un factor determinante por sí solo, ya que el valor final depende de otras características como el área construida, el estrato o la ubicación de la vivienda.
fig4 <- plot_ly(datos, x = ~habitaciones, y = ~preciom,
type = "scatter", mode = "markers",
color = ~as.factor(habitaciones),
marker = list(size = 8, opacity = 0.6)) %>%
layout(title = "Precio vs Número de habitaciones",
xaxis = list(title = "Número de habitaciones"),
yaxis = list(title = "Precio (millones)"))
# Mostrar todos
fig4
Esta gráfica de dispersión muestra la relación entre el precio de la vivienda y el número de habitaciones. Se observa que las casas con 2, 3, 4 y 5 habitaciones presentan un rango de precios muy amplio, que oscila entre aproximadamente 150 y 900 millones. No se evidencia una tendencia clara de incremento de precios a medida que aumenta el número de habitaciones, ya que en todos los grupos existen viviendas tanto en rangos bajos como en los más altos de precio. Esto indica que, al igual que con el número de baños, el precio de la vivienda no depende exclusivamente del número de habitaciones, sino que intervienen otras variables como el área construida, el estrato o la ubicación geográfica.
fig5 <- plot_ly(datos, x = ~zona, y = ~preciom,
type = "box", color = ~zona) %>%
layout(title = "Distribución del precio por zona",
xaxis = list(title = "Zona"),
yaxis = list(title = "Precio (millones)"))
# Mostrar gráfico
fig5
Este boxplot muestra la distribución del precio de las viviendas por zona de la ciudad. Se observa que la Zona Oeste presenta los precios medianos más altos, con gran dispersión y presencia de valores extremos elevados, lo que indica que allí se concentran varias de las casas o apartamentos más costosos. En contraste, la Zona Oriente refleja precios medianos considerablemente más bajos y menor dispersión, sugiriendo que las viviendas en esta zona son, en general, más económicas. La Zona Norte y la Zona Sur presentan una amplia variabilidad, con valores que van desde los más bajos hasta precios muy altos, lo cual evidencia heterogeneidad en la oferta. Finalmente, la Zona Centro muestra precios medianos intermedios con algunos outliers altos. En conjunto, el gráfico resalta que la ubicación geográfica es un factor clave que influye en el precio de las viviendas, siendo la Zona Oeste la más costosa y la Oriente la más económica.
Estime un modelo de regresión lineal múltiple con las variables del punto anterior (precio = f(área construida, estrato, número de cuartos, número de parqueaderos, número de baños ) ) e interprete los coeficientes si son estadísticamente significativos. Las interpretaciones deben estar contextualizadas y discutir si los resultados son lógicos. Adicionalmente interprete el coeficiente \(R^2\) y discuta el ajuste del modelo e implicaciones (que podrían hacer para mejorarlo).
# Modelo de regresión lineal múltiple
modelo <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = datos)
# Resumen del modelo
summary(modelo)
Call:
lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
banios, data = datos)
Residuals:
Min 1Q Median 3Q Max
-391.34 -53.92 -0.84 46.07 494.48
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -32.87103 5.61745 -5.852 5.05e-09 ***
areaconst 1.06558 0.01751 60.871 < 2e-16 ***
estrato4 58.90219 3.31841 17.750 < 2e-16 ***
estrato5 123.63821 3.21883 38.411 < 2e-16 ***
estrato6 281.80418 3.90084 72.242 < 2e-16 ***
habitaciones -18.57017 1.66559 -11.149 < 2e-16 ***
parqueaderos 42.32845 1.96079 21.587 < 2e-16 ***
banios 41.89141 1.59251 26.305 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 94.94 on 8311 degrees of freedom
Multiple R-squared: 0.8331, Adjusted R-squared: 0.833
F-statistic: 5928 on 7 and 8311 DF, p-value: < 2.2e-16
1. areaconst:
El coeficiente es positivo, lo que indica que manteniendo constantes la demás variables, por cada metro cuadrado adicional de área construida, el precio de la vivienda aumenta en promedio 1.06558 millones. Esto es lógico porque a mayor área, mayor valor de la propiedad.
2. Estrato:
La variable estrato aparece codificada como dummies (variables ficticias), lo que significa que hay una categoría de referencia (seguramente estrato 3, porque no aparece en la tabla). Estos resultados confirman la teoría del momento de experiencia y contexto del módulo.
En conclusión:
El estrato tiene un impacto positivo, creciente y altamente significativo en el precio de las viviendas. Los resultados son lógicos porque reflejan cómo el mercado valora la localización socioeconómica: a mayor estrato, mayor precio, incluso si dos viviendas tienen igual área, número de cuartos, baños y parqueaderos.
3. habitaciones:
El coeficiente negativo indica que, manteniendo constantes el área construida, el estrato, los baños y los parqueaderos, cada habitación adicional se asocia con una disminución promedio de 18.6 millones en el precio de la vivienda. Aunque a primera vista podría parecer ilógico (más habitaciones deberían aumentar el valor), el resultado se entiende porque el modelo ya controla por el área construida. Es decir, si el área es fija, más habitaciones implican espacios más pequeños y posiblemente de menor calidad, lo cual puede disminuir el valor de la vivienda.
4. Parqueaderos:
El coeficiente positivo indica que, manteniendo constantes las demás variables, cada parqueadero adicional aumenta el precio en promedio 42.3 millones. Este resultado es lógico y consistente con el mercado, pues contar con parqueaderos es un atributo muy valorado en contextos urbanos, donde la disponibilidad de estacionamiento incrementa considerablemente el atractivo y el precio de una vivienda.
5. Banios:
El coeficiente positivo muestra que, manteniendo constantes las demás variables, cada baño adicional incrementa el precio de la vivienda en promedio 41.9 millones. Este resultado también es lógico: un mayor número de baños generalmente se asocia a mayor comodidad y funcionalidad, características valoradas por los compradores y que elevan el precio del inmueble.
El \(R^2\) me indica que el 83.31% de la variabilidad de los precios de las viviendas es explicada por el área, estrato, habitaciones, parqueaderos y baños. Sin embargo, aún queda apróximadamente un 17% de la variación no explicada, que podría atribuirse a otros factores relevantes no considerados, como la ubicación exacta del inmueble, la antigüedad de la construcción, la cercanía a servicios o características específicas del barrio.
En conclusión, los resultados son en su mayoría lógicos y coherentes con la teoría del mercado inmobiliario, salvo el efecto negativo del número de habitaciones, que se entiende únicamente en el contexto de un área fija. Para mejorar el modelo, se recomienda incluir variables adicionales de localización y características estructurales, así como explorar posibles interacciones entre variables (por ejemplo, área y estrato).
Es de resaltar que aunque el modelo muestre un \(R^2\) = 0.833, esto no garantiza por sí solo que sea un buen modelo. Para ello, se debe avanzar con el siguiente paso, que consiste en la validación de los supuestos de la regresión lineal. En particular, es necesario verificar que los residuos sigan una distribución aproximadamente normal, que tengan media cero, varianza constante (homocedasticidad) y que sean independientes entre sí. El cumplimiento de estos supuestos permite confiar en la validez de las inferencias estadísticas (como los intervalos de confianza y pruebas de significancia), así como en la estabilidad de las predicciones. En caso de que alguno de los supuestos no se cumpla, podrían considerarse transformaciones de variables, ajustes al modelo o el uso de metodologías alternativas que se adapten mejor a las características de los datos.
Realice la validación de supuestos del modelo e interprete los resultados (no es necesario corregir en caso de presentar problemas, solo realizar sugerencias de que se podría hacer).
El supuesto de normalidad de los residuos se plantea como:
\[ H_0: \ \varepsilon_i \sim N(0, \sigma^2) \quad \text{(los residuos siguen una distribución normal)} \]
\[ H_1: \ \varepsilon_i \ \text{no siguen una distribución normal} \]
Bajo la hipótesis nula (\(H_0\)), los residuos del modelo provienen de una distribución normal, por lo que el supuesto se cumple. En cambio, si se rechaza \(H_0\) (por ejemplo, en una prueba de Shapiro-Wilk con \(p < 0.05\)), se concluye que los residuos no son normales y, por lo tanto, el supuesto de normalidad no se cumple.
Se realiza un gráfico para visualizar si los errores siguen una distribución normal y luego se comprueba con el test de normalidad
# Extraer los residuos
residuos <- residuals(modelo)
# Configurar la ventana gráfica: 1 fila, 2 columnas
par(mfrow = c(1, 2))
# 1. Histograma con curva normal
hist(residuos, breaks = 50, freq = FALSE, col = "blue",
main = "Histograma de los residuos", xlab = "Residuos")
curve(dnorm(x, mean = mean(residuos), sd = sd(residuos)),
col = "red", lwd = 2, add = TRUE)
# 2. Gráfico Q-Q
qqnorm(residuos, main = "Grafico Q-Q de los residuos")
qqline(residuos, col = "red", lwd = 2)
Los residuos son aproximadamente normales en la zona central, pero presentan problemas en los extremos (colas más pesadas), lo que indica que el supuesto de normalidad se cumple de manera parcial. Esto no invalida del todo el modelo, pero conviene tenerlo en cuenta al interpretar pruebas de significancia (p-values) que dependen fuertemente de la normalidad. Para comprobar si en realidad los errores siguen una distribución normal o no, aplicamos el test de Anderson Darling que es más potente que Shapiro y KS para detectar colas pesadas.
# Cargar la librería en cada sesión
library(nortest)
# Prueba de Anderson-Darling
ad.test(residuos)
Anderson-Darling normality test
data: residuos
A = 53.635, p-value < 2.2e-16
El test de Anderson-Darling me arroja un p-value<0.05, rechazando la hipótesis nula. Esto quiere decir, que los residuos no siguen una distribución normal.
Se utiliza una prueba estadística, por ejemplo un t-test de una muestra, para contrastar la hipótesis.
\(H_0: \mu = 0\)
(la media de los residuos es cero)
\(H_1: \mu \neq 0\)
(la media de los residuos difiere de cero)
Antes de realizar el test, se visualiza la distribución de los residuos para tener una idea preliminar sobre su comportamiento y verificar si parecen estar centrados en torno a cero.
# 1. Gráfico de residuos
plot(residuos, type = "p", col = "green",
main = "Residuos vs Observaciones",
ylab = "Residuos", xlab = "Índice")
abline(h = 0, col = "red", lwd = 2) # Línea en cero
El gráfico muestra que los residuos se distribuyen alrededor de cero sin una tendencia clara, lo que indica que el modelo no presenta sesgo sistemático; sin embargo, se observan algunos valores atípicos y una ligera dispersión heterogénea en diferentes tramos, lo que sugiere revisar la homocedasticidad. Antes de pasar a comprobar la homocedasticidad se verifica con la prueba t de Student para la media.
# 2. Prueba t para verificar si la media de los residuos es cero
t_test_residuos <- t.test(residuos, mu = 0)
print(t_test_residuos)
One Sample t-test
data: residuos
t = -1.5823e-14, df = 8318, p-value = 1
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
-2.039504 2.039504
sample estimates:
mean of x
-1.646317e-14
Este resultado indica que el promedio de los residuos no es significativamente diferente de cero. El valor de la prueba t es prácticamente nulo (t ≈ 0), el p-value = 1 muestra que no hay evidencia estadística para rechazar la hipótesis nula, y el intervalo de confianza al 95% (−2.04, 2.04) incluye el cero, reforzando la conclusión de que la media poblacional de los residuos puede asumirse como cero; por lo tanto, se cumple el supuesto de la media nula en los residuos del modelo.
Se plantea la hipótesis nula y alternativa para evaluar la homocedasticidad de los residuos:
\[ H_0: \; V[e] = \sigma^2 \quad \text{(la varianza de los residuos es constante)} \]
\[ H_1: \; V[e] \neq \sigma^2 \quad \text{(la varianza de los residuos no es constante)} \]
Realizamos un gráfico para observar el comportamiento de las varianzas de los residuos frente a los valores ajustados, con el fin de verificar visualmente si se cumple el supuesto de homocedasticidad (varianza constante) o si, por el contrario, se presenta heterocedasticidad.
# 1. Gráfico de residuos vs valores ajustados
ajustados <- fitted(modelo)
plot(ajustados, residuos,
col = "orange", # color de los puntos
pch = 19, # tipo de punto sólido
main = "Residuos vs Valores Ajustados",
xlab = "Valores Ajustados",
ylab = "Residuos")
# Línea de referencia en 0
abline(h = 0, col = "red", lwd = 2)
La gráfica muestra que los residuos no mantienen una dispersión constante alrededor de cero, sino que presentan un patrón en el que la amplitud de la variabilidad cambia con los valores ajustados; esto sugiere la presencia de heterocedasticidad, es decir, que la varianza de los errores no es constante en todo el rango del modelo.
Para contrastar lo que nos indica el gráfico de la varianza, vamos aplicar el test de Breusch-Pagan.
# 2. Test de Breusch-Pagan (necesita librería 'lmtest')
library(lmtest)
bptest(modelo)
studentized Breusch-Pagan test
data: modelo
BP = 730.55, df = 7, p-value < 2.2e-16
El resultado del test de Breusch-Pagan (BP = 730.55, df = 7, p-value < 2.2e-16) indica que se rechaza la hipótesis nula de homocedasticidad, concluyendo que en el modelo existe heterocedasticidad en los residuos, lo cual implica que la varianza no es constante y puede afectar la eficiencia de los estimadores y la confiabilidad de las pruebas de significancia.
El test de Breusch-Pagan confirma la observación que se realiza en la gráfica de los errores de varianza constante.
El supuesto de independencia de los errores significa que los residuos (errores del modelo) no deben estar correlacionados entre sí, es decir, el error cometido en una observación no debe depender del error en otra. Para el supuesto de independencia de los errores, cuando aplicamos la prueba de Durbin-Watson, las hipótesis se plantean así:
\[ H_0:\; \text{Cov}(e_i, e_j) = 0 \quad \forall \; i \neq j \quad \text{(los errores son independientes)} \]
\[ H_1:\; \text{Cov}(e_i, e_j) \neq 0 \quad \exists \; i \neq j \quad \text{(los errores no son independientes)} \]En regresión lineal, se asume que los errores no presentan correlación entre sí. Si los residuos muestran patrones sistemáticos a lo largo del tiempo o de las observaciones, esto indica dependencia, lo cual invalida el supuesto.
plot(resid(modelo), type = "l",
main = "Residuos vs. Observaciones",
xlab = "Observaciones",
ylab = "Residuos estandarizados")
abline(h = 0, col = "red")
La gráfica de los residuos frente al tiempo muestra un patrón que oscila en torno a cero, pero con una aparente correlación entre puntos cercanos, lo que indica que los errores no se comportan como totalmente aleatorios, sino que presentan cierta dependencia temporal; en un escenario de independencia perfecta, esperaríamos que los residuos se distribuyeran sin un patrón visible a lo largo del eje temporal.
Ahora, se realiza el test de Durbin-Watson para confirmar la posible autocorrelación de los residuos; un valor cercano a 2 indica independencia, mientras que valores significativamente menores a 2 sugieren autocorrelación positiva y valores mayores a 2 reflejan autocorrelación negativa, lo que permite contrastar formalmente la validez del supuesto de independencia en el modelo.
library(lmtest)
dwtest(modelo)
Durbin-Watson test
data: modelo
DW = 1.764, p-value < 2.2e-16
alternative hypothesis: true autocorrelation is greater than 0
El test de Durbin-Watson arrojó un valor de 1.764 con un p-valor extremadamente bajo (< 2.2e-16), lo que indica evidencia estadística de autocorrelación positiva en los residuos del modelo, aunque el valor de DW cercano a 2 sugiere que esta autocorrelación no es muy fuerte.
Podemos concluir que los resultados del modelo no son confiables ya que no se cumplen los supuestos a cabalidad, aunque el modelo tenga un \(R^2\) alto (83,31%), no es adecuado para tomar deciones, ni hacer inferencias y se debe considerar reformularlo o realizar una transformación.
# 1. Separar en entrenamiento (70%) y prueba (30%)
set.seed(123) # para reproducibilidad
n <- nrow(datos)
train_index <- sample(1:n, size = 0.7*n)
train <- datos[train_index, ]
test <- datos[-train_index, ]
# 2. Ajustar el modelo con el set de entrenamiento
modelo_1 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios,
data = train)
# 3. Realizar predicciones con el set de prueba
predicciones <- predict(modelo_1, newdata = test)
# 4. Comparar resultados
resultados <- data.frame(
Real = test$preciom,
Predicho = predicciones
)
head(resultados)
Real Predicho
1 350 296.8618
2 400 604.1496
3 220 200.6889
4 310 374.4332
5 780 618.3958
6 750 825.3722
# 5. Calcular una métrica de error (ejemplo RMSE)
rmse <- sqrt(mean((resultados$Real - resultados$Predicho)^2))
cat("Error cuadrático medio (RMSE):", rmse)
Error cuadrático medio (RMSE): 93.76258
En el conjunto de prueba se observa que los valores predichos por el modelo se aproximan razonablemente a los valores reales, aunque existen diferencias en algunos casos por ejemplo, la vivienda con valor real de 400 fue sobreestimada en más de 200 unidades. El Error cuadrático medio (RMSE) de 93.76 indica que, en promedio, las predicciones del modelo se desvían de los valores reales en aproximadamente 94 unidades de la variable de precio. Un RMSE de esta magnitud sugiere que, si bien el modelo captura gran parte de la variabilidad de los datos, aún presenta errores de predicción que deben tenerse en cuenta para la toma de decisiones.
Con el modelo identificado debe predecir el precio de la vivienda con las características de la primera solicitud.
# Extraer la primera fila (primera solicitud)
primera_solicitud <- datos[1, ]
# Predicción del precio usando el modelo
precio_predicho <- predict(modelo, newdata = primera_solicitud)
# Mostrar resultado
precio_predicho
1
116.8714
El resultado indica que, según el modelo de regresión múltiple, el precio predicho para la vivienda (Casa o Apartamento) con las características de la primera solicitud es aproximadamente 116.87 millones de pesos.
Con las predicciones del modelo, sugiera potenciales ofertas que responda a la solicitud de la vivienda (Casa o Apartamento) 1. Tenga encuenta que la empresa tiene crédito pre-aprobado de máximo 350 millones de pesos. Realice un análisis y presente en un mapa al menos 5 ofertas potenciales que debe discutir.
# 2. Extraer la primera solicitud
primera_solicitud <- datos[1, ]
# 3. Predecir precio para todas las viviendas
datos$precio_predicho <- predict(modelo, newdata = datos)
# 4. Filtrar viviendas dentro del crédito máximo (350 millones)
ofertas_posibles <- datos[datos$precio_predicho <= 350, ]
# 5. Calcular similitud con la solicitud 1
vars <- c("areaconst", "estrato", "habitaciones", "banios", "parqueaderos")
ofertas_posibles[, vars] <- lapply(ofertas_posibles[, vars], function(x) as.numeric(as.character(x)))
primera <- primera_solicitud[, vars]
primera <- lapply(primera, function(x) as.numeric(as.character(x)))
primera <- as.data.frame(primera)
distancia <- apply(ofertas_posibles[, vars], 1, function(x) sum((x - primera[1, ])^2))
ofertas_posibles$distancia <- distancia
# 6. Seleccionar las 5 ofertas más similares
top5_ofertas <- ofertas_posibles[order(ofertas_posibles$distancia), ][1:5, ]
# 7. Agrupar por ubicación para mostrar toda la info en el popup si hay duplicados
library(dplyr)
top5_ofertas_agrupadas <- top5_ofertas %>%
group_by(longitud, latitud) %>%
summarise(
popup = paste(
sapply(1:n(), function(i) paste(
"<b>Oferta</b>: ", i,
"<br><b>Precio:</b> ", round(precio_predicho[i], 2),
"<br><b>Área:</b> ", areaconst[i],
"<br><b>Estrato:</b> ", estrato[i],
"<br><b>Habitaciones:</b> ", habitaciones[i],
"<br><b>Baños:</b> ", banios[i],
"<br><b>Parqueaderos:</b> ", parqueaderos[i]
)),
collapse = "<hr>"
),
.groups = "drop"
)
# 8. Visualizar en el mapa
library(leaflet)
leaflet(top5_ofertas_agrupadas) %>%
addTiles() %>%
addMarkers(
~longitud, ~latitud,
popup = ~popup
)
# 9. Mostrar tabla de ofertas para discusión
print(top5_ofertas[, c(vars, "precio_predicho", "longitud", "latitud")])
# A tibble: 5 × 8
areaconst estrato habitaciones banios parqueaderos precio_predicho longitud
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 70 3 5 3 1 117. -76.5
2 70 3 5 3 1 117. -76.5
3 70 3 4 3 1 135. -76.5
4 70 3 3 2 1 112. -76.5
5 70 3 3 2 1 112. -76.5
# ℹ 1 more variable: latitud <dbl>
Las 5 ofertas seleccionadas son viviendas de 70 m² y estrato 3, muy similares a la primera solicitud, con ligeras variaciones en habitaciones (3–5) y baños (2–3), todas con 1 parqueadero, y precios predichos entre 112 y 135 millones, lo que las hace accesibles dentro del crédito pre-aprobado de 350 millones; estas opciones permiten al cliente comparar alternativas cercanas a sus necesidades y tomar decisiones basadas en distribución y costo, facilitando su presentación en un mapa interactivo.
Salen 4 marcadores en el mapa porque, aunque se seleccionan 5 ofertas potenciales en la tabla, todas ellas tienen exactamente la misma ubicación geográfica (los mismos valores de longitud y latitud). Como resultado, varias ofertas se superponen en el mismo punto del mapa y el sistema de visualización solo muestra un marcador por coordinación única. Por eso, aunque en la tabla se discuten 5 alternativas, el mapa solo despliega 4 puntos, ya que dos o más ofertas comparten una misma ubicación.
Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.
# 1. Extraer la segunda solicitud
segunda_solicitud <- datos[2, ]
# 2. Predecir precio para todas las viviendas (si no lo has hecho antes)
datos$precio_predicho <- predict(modelo, newdata = datos)
# 3. Filtrar viviendas dentro del crédito máximo (850 millones)
ofertas_posibles <- datos[datos$precio_predicho <= 850, ]
# 4. Convertir variables relevantes a numéricas para calcular similitud
vars <- c("areaconst", "estrato", "habitaciones", "banios", "parqueaderos")
ofertas_posibles[, vars] <- lapply(ofertas_posibles[, vars], function(x) as.numeric(as.character(x)))
segunda <- segunda_solicitud[, vars]
segunda <- lapply(segunda, function(x) as.numeric(as.character(x)))
segunda <- as.data.frame(segunda)
# 5. Calcular distancia euclidiana respecto a la segunda solicitud
distancia <- apply(ofertas_posibles[, vars], 1, function(x) sum((x - segunda[1, ])^2))
ofertas_posibles$distancia <- distancia
# 6. Seleccionar las 5 ofertas más similares
top5_ofertas <- ofertas_posibles[order(ofertas_posibles$distancia), ][1:5, ]
# 7. Agrupar por ubicación para mostrar toda la info en el popup si hay duplicados
library(dplyr)
top5_ofertas_agrupadas <- top5_ofertas %>%
group_by(longitud, latitud) %>%
summarise(
popup = paste(
sapply(1:n(), function(i) paste(
"<b>Oferta</b>: ", i,
"<br><b>Precio:</b> ", round(precio_predicho[i], 2),
"<br><b>Área:</b> ", areaconst[i],
"<br><b>Estrato:</b> ", estrato[i],
"<br><b>Habitaciones:</b> ", habitaciones[i],
"<br><b>Baños:</b> ", banios[i],
"<br><b>Parqueaderos:</b> ", parqueaderos[i]
)),
collapse = "<hr>"
),
.groups = "drop"
)
# 8. Visualizar en el mapa
leaflet(top5_ofertas_agrupadas) %>%
addTiles() %>%
addAwesomeMarkers(
~longitud, ~latitud,
popup = ~popup,
icon = awesomeIcons(
icon = 'home',
iconColor = 'white',
markerColor = 'red',
library = 'fa'
)
)
# 9. Mostrar tabla para discusión
print(top5_ofertas[, c(vars, "precio_predicho", "longitud", "latitud")])
# A tibble: 5 × 8
areaconst estrato habitaciones banios parqueaderos precio_predicho longitud
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 120 3 3 2 1 165. -76.5
2 120 3 3 2 1 165. -76.5
3 120 3 3 2 1 165. -76.5
4 120 3 3 2 1 165. -76.5
5 120 3 3 2 1 165. -76.5
# ℹ 1 more variable: latitud <dbl>
En los resultados se muestra que las cinco ofertas seleccionadas para la segunda solicitud tienen exactamente las mismas características físicas y de precio: área construida, estrato, número de habitaciones, baños, parqueaderos y un precio predicho de 165 millones, muy por debajo del crédito máximo aprobado de 850 millones. Sin embargo, todos comparten también la misma ubicación geográfica, lo que indica que corresponden a diferentes registros de viviendas (Casa o Apartamento) en el mismo punto o conjunto residencial. Por esta razón, aunque la tabla presenta cinco alternativas, el mapa solo muestra un único marcador para esa ubicación, pues los demás marcadores corresponden a otras ofertas de tu base de datos con diferentes coordenadas. En definitiva, la visualización refleja que, aunque existen varias opciones similares y costosas, la diversidad espacial de las ofertas sugeridas es limitada y se concentran en pocas zonas de la ciudad, lo que puede influir en la decisión del cliente según sus preferencias de localización.