library(ggplot2) # Para gráficos
library(dplyr) # Para manipulación de datos
## 
## Adjuntando el paquete: 'dplyr'
## The following object is masked from 'package:gridExtra':
## 
##     combine
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(cluster)   # Para clustering
library(factoextra) # Para visualización
## Warning: package 'factoextra' was built under R version 4.4.2
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(tidyr)
library(tinytex)
library(GGally)
library(summarytools)
library(leaflet)
## Warning: package 'leaflet' was built under R version 4.4.3

Informe ejecutivo

El presente informe ejecutivo tiene como finalidad brindar un análisis estratégico y recomendaciones fundamentadas en técnicas de modelación para atender la solicitud de asesoría en la adquisición de dos viviendas por parte de una compañía internacional, que busca reubicar a dos de sus empleados en Cali.

María, quien inició su carrera en bienes raíces en Cali hace 10 años y ha acumulado experiencia trabajando en distintas ciudades, fundó su propia agencia, C&A (Casas y Apartamentos), tras obtener la licencia de intermediaria. Con el respaldo de un equipo consolidado, conformado actualmente por ocho agentes, María ha logrado posicionarse en el mercado local, lo que le confiere un amplio conocimiento y una sólida reputación en el sector.

En respuesta a la reciente solicitud de María, este informe analiza dos casos de estudio, aplicando técnicas de modelación para estimar y validar el precio de las viviendas en función de variables clave como el área construida, estrato, número de baños, habitaciones y parqueaderos. Además, se presentan recomendaciones estratégicas para identificar ofertas potenciales que se ajusten a los creditos pre-aprobados de los clientes y a las necesidades específicas de la compañía.

El informe se estructura en dos secciones principales: el “Análisis exploratorio”, en el que se sintetizan los detalles del mercado inmobiliario en Cali y el de “Ofertas de viviendas” donde se muestran los hallazgos, se proponen las recomendaciones y que contienen el detalle de las estimaciones, validaciones y comparación de modelos utilizados en el análisis. Esta metodología integral busca proporcionar una base sólida para la toma de decisiones en un momento crucial para el sector inmobiliario en Cali.

Análisis exploratorio

Inicialmente se realiza un EDA sobre la base de datos que se tiene para conocer la información con lo que se está trabajando

str(vivienda)
## spc_tbl_ [8,322 × 13] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ id          : num [1:8322] 1147 1169 1350 5992 1212 ...
##  $ zona        : chr [1:8322] "Zona Oriente" "Zona Oriente" "Zona Oriente" "Zona Sur" ...
##  $ piso        : chr [1:8322] NA NA NA "02" ...
##  $ estrato     : num [1:8322] 3 3 3 4 5 5 4 5 5 5 ...
##  $ preciom     : num [1:8322] 250 320 350 400 260 240 220 310 320 780 ...
##  $ areaconst   : num [1:8322] 70 120 220 280 90 87 52 137 150 380 ...
##  $ parqueaderos: num [1:8322] 1 1 2 3 1 1 2 2 2 2 ...
##  $ banios      : num [1:8322] 3 2 2 5 2 3 2 3 4 3 ...
##  $ habitaciones: num [1:8322] 6 3 4 3 3 3 3 4 6 3 ...
##  $ tipo        : chr [1:8322] "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr [1:8322] "20 de julio" "20 de julio" "20 de julio" "3 de julio" ...
##  $ longitud    : num [1:8322] -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num [1:8322] 3.43 3.43 3.44 3.44 3.46 ...
##  - attr(*, "spec")=List of 3
##   ..$ cols   :List of 13
##   .. ..$ id          : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ zona        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ piso        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ estrato     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ preciom     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ areaconst   : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ parqueaderos: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ banios      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ habitaciones: list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ tipo        : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ barrio      : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_character" "collector"
##   .. ..$ longitud    : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   .. ..$ latitud     : list()
##   .. .. ..- attr(*, "class")= chr [1:2] "collector_double" "collector"
##   ..$ default: list()
##   .. ..- attr(*, "class")= chr [1:2] "collector_guess" "collector"
##   ..$ delim  : chr ";"
##   ..- attr(*, "class")= chr "col_spec"
##  - attr(*, "problems")=<externalptr>

Primeramente,se realizará un tratamiento de los NA’s por la naturaleza del análisis.

summary(vivienda)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:8322        Length:8322        Min.   :3.000  
##  1st Qu.:2080   Class :character   Class :character   1st Qu.:4.000  
##  Median :4160   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4160                                         Mean   :4.634  
##  3rd Qu.:6240                                         3rd Qu.:5.000  
##  Max.   :8319                                         Max.   :6.000  
##  NA's   :3                                            NA's   :3      
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 220.0   1st Qu.:  80.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 330.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 433.9   Mean   : 174.9   Mean   : 1.835   Mean   : 3.111  
##  3rd Qu.: 540.0   3rd Qu.: 229.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1745.0   Max.   :10.000   Max.   :10.000  
##  NA's   :2        NA's   :3        NA's   :1605     NA's   :3       
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:8322        Length:8322        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.605                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##  NA's   :3                                              NA's   :3       
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.381  
##  Median :3.416  
##  Mean   :3.418  
##  3rd Qu.:3.452  
##  Max.   :3.498  
##  NA's   :3

Se tiene NAs en las variables de Estrato, precio, area, parqueaderos, baños y habitaciones

# Eliminar todas las filas que tengan al menos un NA
vivienda_clean <- na.omit(vivienda)

# Verificar el resultado
summary(vivienda_clean)
##        id           zona               piso              estrato     
##  Min.   :   1   Length:4808        Length:4808        Min.   :3.000  
##  1st Qu.:2479   Class :character   Class :character   1st Qu.:4.000  
##  Median :4474   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :4427                                         Mean   :4.838  
##  3rd Qu.:6413                                         3rd Qu.:6.000  
##  Max.   :8316                                         Max.   :6.000  
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  58.0   Min.   :  40.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 244.5   1st Qu.:  85.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 350.0   Median : 123.0   Median : 2.000   Median : 3.000  
##  Mean   : 457.2   Mean   : 174.8   Mean   : 1.815   Mean   : 3.219  
##  3rd Qu.: 560.0   3rd Qu.: 225.0   3rd Qu.: 2.000   3rd Qu.: 4.000  
##  Max.   :1999.0   Max.   :1500.0   Max.   :10.000   Max.   :10.000  
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:4808        Length:4808        Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median : 3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   : 3.564                                         Mean   :-76.53  
##  3rd Qu.: 4.000                                         3rd Qu.:-76.52  
##  Max.   :10.000                                         Max.   :-76.46  
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.378  
##  Median :3.408  
##  Mean   :3.414  
##  3rd Qu.:3.451  
##  Max.   :3.498

Se tienen 4.808 observaciones y las siguientes variables:

Análisis de componentes principales

# Variables numéricas relevantes
vars_numericas <- c( "preciom", "areaconst", "parqueaderos",
                    "banios", "habitaciones")

# Nuevo data frame con las variables seleccionadas
df_numeric <- vivienda[, vars_numericas]

# Se eliminan filas con NA
df_numeric <- na.omit(df_numeric)

head(df_numeric)
## # A tibble: 6 × 5
##   preciom areaconst parqueaderos banios habitaciones
##     <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1     250        70            1      3            6
## 2     320       120            1      2            3
## 3     350       220            2      2            4
## 4     400       280            3      5            3
## 5     260        90            1      2            3
## 6     240        87            1      3            3
# Ejecutar el PCA con centrado y escalado
pca_result <- prcomp(df_numeric, center = TRUE, scale. = TRUE)

# Resumen del PCA para ver la varianza explicada
summary(pca_result)
## Importance of components:
##                           PC1    PC2     PC3     PC4     PC5
## Standard deviation     1.8035 0.9280 0.60371 0.57319 0.43938
## Proportion of Variance 0.6505 0.1722 0.07289 0.06571 0.03861
## Cumulative Proportion  0.6505 0.8228 0.89568 0.96139 1.00000
fviz_eig(pca_result, addlabels = TRUE, ylim = c(0, 80)) +
  ggtitle("Scree Plot: Varianza Explicada por Componentes")

fviz_pca_biplot(pca_result,
                geom.ind = "point",  # Representa las observaciones como puntos
                col.ind = "blue",    # Color para las observaciones
                repel = TRUE,        # Evita que las etiquetas se solapen
                title = "Biplot de PCA")

fviz_pca_var(pca_result,
             col.var = "contrib",            # Colorea según la contribución
             gradient.cols = c("blue", "yellow", "red"),
             repel = TRUE,                   # Evita solapamientos en las etiquetas
             title = "Contribución de Variables en el PCA")

El precio (preciom) está fuertemente influenciado por características estructurales como área construida, cantidad de baños, parqueaderos y estrato.

El PCA ha reducido la dimensionalidad a dos componentes que explican el 65.1% de la variabilidad, lo que sugiere que estas dimensiones capturan gran parte de la estructura del mercado inmobiliario.

Ofertas de viviendas

1. Filtro que incluya las ofertas que se adapten a lo que se busca en la compra de la vivienda 1

La primera solicitud tiene un credito preaprobado de COP 350 mm para una vivienda con las siguientes características:

  • Tipo: Casa
  • Área construida: 200 metros cuadrados
  • Parqueaderos: 1
  • Baños: 2
  • Habitaciones: 4
  • Estrato: 4 o 5
  • Zona: Zona Norte

De acuerdo a esto, se realizó un filtro de la base de datos para que las viviendas evaluadas tenga todas las características anteriores.

# Filtrar las viviendas que cumplen con las condiciones del cliente
viviendas_filtradas <- vivienda %>%
  filter(tipo == "Casa",
         areaconst >= 200,
         parqueaderos >= 1,
         banios >= 2,
         habitaciones >= 4,
         estrato %in% c(4, 5),
         zona == "Zona Norte",
         preciom <= 350)  # Filtrar por precio máximo de 350 millones

# Mostrar las primeras filas del resultado
head(viviendas_filtradas, 3)
## # A tibble: 3 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  4210 Zona N… 01          5     350       200            3      3            4
## 2  4267 Zona N… 01          5     335       202            1      4            5
## 3  4800 Zona N… 01          5     340       250            2      4            4
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
summary(viviendas_filtradas)
##        id           zona               piso              estrato     
##  Min.   : 766   Length:34          Length:34          Min.   :4.000  
##  1st Qu.:1154   Class :character   Class :character   1st Qu.:4.000  
##  Median :1928   Mode  :character   Mode  :character   Median :5.000  
##  Mean   :2743                                         Mean   :4.647  
##  3rd Qu.:4210                                         3rd Qu.:5.000  
##  Max.   :7470                                         Max.   :5.000  
##     preciom        areaconst      parqueaderos       banios     
##  Min.   :230.0   Min.   :200.0   Min.   :1.000   Min.   :2.000  
##  1st Qu.:320.2   1st Qu.:219.5   1st Qu.:2.000   1st Qu.:3.000  
##  Median :340.0   Median :250.0   Median :2.000   Median :4.000  
##  Mean   :328.5   Mean   :254.6   Mean   :1.882   Mean   :3.618  
##  3rd Qu.:350.0   3rd Qu.:275.0   3rd Qu.:2.000   3rd Qu.:4.000  
##  Max.   :350.0   Max.   :355.0   Max.   :3.000   Max.   :5.000  
##   habitaciones       tipo              barrio             longitud     
##  Min.   :4.000   Length:34          Length:34          Min.   :-76.55  
##  1st Qu.:4.000   Class :character   Class :character   1st Qu.:-76.53  
##  Median :4.500   Mode  :character   Mode  :character   Median :-76.52  
##  Mean   :4.824                                         Mean   :-76.52  
##  3rd Qu.:5.000                                         3rd Qu.:-76.51  
##  Max.   :8.000                                         Max.   :-76.50  
##     latitud     
##  Min.   :3.376  
##  1st Qu.:3.468  
##  Median :3.480  
##  Mean   :3.472  
##  3rd Qu.:3.483  
##  Max.   :3.489
# Tablas de frecuencia para comprobar la consulta
# Tabla de frecuencia para la variable 'zona'
tabla_zona <- table(viviendas_filtradas$zona)
print(tabla_zona)
## 
## Zona Norte 
##         34
# Tabla de frecuencia para 'tipo' (debe ser solo "Casa")
tabla_tipo <- table(viviendas_filtradas$tipo)
print(tabla_tipo)
## 
## Casa 
##   34

De acuerdo a la base de datos, hay 34 potenciales ofertas que se ajustan a lo que el cliente busca.

# Tabla para 'estrato'
tabla_estrato <- table(viviendas_filtradas$estrato)
print(tabla_estrato)
## 
##  4  5 
## 12 22

En la zona se tiene 12 viviendas de estrato 4 y 22 de estrato 5

# Mapa con los puntos de las viviendas filtradas
# Usamos ggplot para una visualización rápida
mapa_gg <- ggplot(viviendas_filtradas, aes(x = longitud, y = latitud)) +
  geom_point(color = "blue", size = 2, alpha = 0.7) +
  labs(title = "Ubicación de Viviendas Filtradas (Zona Norte)",
       x = "Longitud", y = "Latitud") +
  theme_minimal()
print(mapa_gg)

Se puede observar que en la base de datos hay 4 ofertas (casas) que se salen de la zona en la que el cliente está solicitando la busqueda, por lo que se procede a filtrarlo para que solo esten las viviendas que apliquen a lo que el cliente busca.

# Ordenar por latitud de menor a mayor
df_filtrado <- viviendas_filtradas[order(viviendas_filtradas$latitud), ]

# Eliminar las tres con menor latitud (son las casas que se salen de la zona pedida)
df_filtrado <- df_filtrado[-c(1:4), ]

# Tabla de frecuencia para 'tipo' (debe ser solo "Casa")
tabla_tipo <- table(df_filtrado$tipo)
print(tabla_tipo)
## 
## Casa 
##   30
# se grafica nuevamente
ggplot(df_filtrado, aes(x = longitud, y = latitud)) +
  geom_point(color = "blue") +
  ggtitle("Ubicación de Viviendas Filtradas (Zona Norte)") +
  xlab("Longitud") + ylab("Latitud") +
  theme_minimal()

# Alternativamente, un mapa interactivo con leaflet
mapa_leaflet <- leaflet(df_filtrado) %>%
  addTiles() %>%
  addCircleMarkers(~longitud, ~latitud,
                   color = "blue", fillOpacity = 0.7,
                   radius = 5, popup = ~paste("Tipo:", tipo, "<br>",
                                              "Precio:", preciom, "mm<br>",
                                              "Zona:", zona))
mapa_leaflet  # Esto abre el mapa interactivo en el visor de RStudio o en el navegador

2. Análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio) en función del área construida, estrato, baños, habitaciones y zona donde se ubica la vivienda.

# Calcular la correlación entre precio y las variables numéricas
correlaciones <- cor(df_filtrado[, c("preciom", "areaconst", "estrato", "banios", "habitaciones")], use = "complete.obs")
print(correlaciones)
##                  preciom   areaconst    estrato      banios habitaciones
## preciom       1.00000000  0.14757614  0.2221428 -0.19469118  -0.07287675
## areaconst     0.14757614  1.00000000 -0.1983761  0.01275372   0.28256251
## estrato       0.22214283 -0.19837613  1.0000000  0.18544106   0.30241165
## banios       -0.19469118  0.01275372  0.1854411  1.00000000   0.49800449
## habitaciones -0.07287675  0.28256251  0.3024116  0.49800449   1.00000000
ggpairs(correlaciones[,1:5], title=" ") 

library(ggplot2)

ggplot(df_filtrado, aes(x = areaconst, y = preciom)) +
  geom_point(color = "blue", alpha = 0.6) +
  labs(title = "Precio vs Área Construida",
       x = "Área Construida (m²)", 
       y = "Precio (COP)") +
  theme_minimal()

ggplot(df_filtrado, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "red", alpha = 0.6) +
  labs(title = "Distribución del Precio por Estrato",
       x = "Estrato",
       y = "Precio (COP)") +
  theme_minimal()

ggplot(df_filtrado, aes(x = factor(banios), y = preciom)) +
  geom_boxplot(fill = "green", alpha = 0.6) +
  labs(title = "Distribución del Precio por Número de Baños",
       x = "Número de Baños",
       y = "Precio (COP)") +
  theme_minimal()

ggplot(df_filtrado, aes(x = factor(habitaciones), y = preciom)) +
  geom_boxplot(fill = "orange", alpha = 0.6) +
  labs(title = "Distribución del Precio por Número de Habitaciones",
       x = "Número de Habitaciones",
       y = "Precio (COP)") +
  theme_minimal()

ggplot(df_filtrado, aes(x = factor(barrio), y = preciom)) +
  geom_boxplot(fill = "orange", alpha = 0.6) +
  labs(title = "Distribución del Precio por Barrio",
       x = "Barrio",
       y = "Precio (COP)") +
  theme_minimal()

ggplot(df_filtrado, aes(x = longitud, y = latitud, color = preciom)) +
  geom_point(size = 2, alpha = 0.7) +
  scale_color_gradient(low = "yellow", high = "red") +
  labs(title = "Ubicación y Precio de las Viviendas",
       x = "Longitud",
       y = "Latitud",
       color = "Precio (COP)") +
  theme_minimal()

Dentro de la zona no hay ningún barrio cuyas viviendas tengan un valor preferencial en el mercado.

3. Modelo de regresión lineal múltiple con precio= f(área construida, estrato, habitaciones, parqueaderos, baños)

# Ajuste del modelo
modelo <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = df_filtrado)

# Resumen del modelo para ver los coeficientes y sus pruebas de significancia
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = df_filtrado)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -86.264  -7.336   4.728  14.842  27.015 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)  
## (Intercept)  187.4631    72.4655   2.587   0.0162 *
## areaconst      0.2036     0.1370   1.487   0.1501  
## estrato       22.8987    12.2184   1.874   0.0731 .
## habitaciones  -5.0277     6.5585  -0.767   0.4508  
## parqueaderos  13.7770     9.8580   1.398   0.1750  
## banios        -5.3729     6.1962  -0.867   0.3945  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 26.79 on 24 degrees of freedom
## Multiple R-squared:  0.2297, Adjusted R-squared:  0.06923 
## F-statistic: 1.431 on 5 and 24 DF,  p-value: 0.249

Interpretación de coeficientes:

  • Intercepto (187.46): Representa el precio base (en millones de COP) cuando todas las variables predictoras son cero. Aunque en el contexto inmobiliario este valor no tiene una interpretación real, sirve como referencia en el modelo.

  • Área Construida (coef. 0.2036): Sugiere que, manteniendo constantes las demás variables, por cada metro cuadrado adicional se incrementa el precio en aproximadamente 0.20 millones de COP. Sin embargo, el valor p (0.1501) indica que este coeficiente no es estadísticamente significativo al 5% (aunque pudiera ser relevante en un análisis práctico).

  • Estrato (coef. 22.8987): Indica que por cada incremento en el estrato, se espera un aumento de alrededor de 22.90 millones de COP en el precio, con una significancia marginal (p = 0.0731). Esto es coherente con que viviendas en estratos más altos suelen tener precios mayores.

-Habitaciones (coef. -5.0277): Curiosamente, el coeficiente es negativo, lo que sugiere que, manteniendo constantes otras variables, un mayor número de habitaciones se asocia con un precio inferior. Sin embargo, este resultado no es estadísticamente significativo (p = 0.4508) y podría reflejar problemas de colinealidad o que esta variable esté capturando efectos complejos (por ejemplo, viviendas con muchas habitaciones pero de menor calidad).

-Parqueaderos (coef. 13.7770): Cada parqueadero adicional se asocia con un aumento de 13.78 millones de COP, pero con p = 0.1750, no es estadísticamente significativo.

-Baños (coef. -5.3729): También se observa un coeficiente negativo (–5.37), sin significancia estadística (p = 0.3945). Esto podría deberse a que la cantidad de baños en este conjunto de datos no varía de forma consistente con el precio, o puede estar relacionado con otras características.

  • R² = 0.2297 y R² Ajustado = 0.06923: Esto indica que el modelo explica aproximadamente el 23% de la variabilidad en el precio (o solo el 7% cuando se ajusta por el número de predictores). El ajuste es bajo, lo que sugiere que hay otros factores importantes no incluidos en el modelo o que la relación es no lineal.

  • F-Statistic p-valor = 0.249: El modelo global es estadísticamente significativo, lo que implica que, en conjunto, las variables incluidas explican la variación en el precio.

Discusión del ajuste:

Si bien es razonable esperar que las variables utilizadas como área construida y el estrato influyan en el precio, la falta de significancia de algunas variables y el bajo R² sugieren que el modelo podría mejorarse. Se podría incluir otras variables como la ubicación detallada (barrio), antigüedad (variable que no se tendría en esta base de datos) y el estado de la propiedad (tampoco se tiene este detalle).

Adicionalmente, se podría considerar transformaciones (como logaritmos) o interacciones entre variables.

4. Validación de supuestos del modelo

par(mfrow = c(2,2))
plot(modelo)

En el gráfico de Residuals vs Fitted, los residuos parecen estar relativamente alineados, pero se aprecia cierta variación (quizás algunos puntos se concentran en un rango y otros se salen). No se ve un patrón muy marcado, pero sí podría haber una ligera pendiente descendente. Podría no ser grave, pero conviene vigilar si hay un ligero sesgo de linealidad.

En el Q-Q Residuals, hay valores en las colas que muestran asimetrías en la distribución. Si bien la desviación no es drástica, se podría revisar si hay outliers que estén influyendo en la distribución.

El Scale-Location evalúa la homocedasticidad (constancia de la varianza). En este caso, la línea muestra una pequeña disminución, lo que podría indicar heterocedasticidad, es decir la varianza de los residuos no son completamente constante. Aunque la disminución no es muy grande por lo que no parece ser un problema grave.

Residuals vs leverage: no parece haber puntos extremadamente lejanos que crucen la línea de Cook’s distance, aunque sí hay algunos casos un poco apartados. Esto indica que no hay un gran problema de puntos con influencia desmedida, pero también se podría revisar manualmente aquellos con mayor leverage o residuo estandarizado.

5. Predicción de precio de la vivienda con las características de la solicitud de vivienda 1

nueva_vivienda <- data.frame(
  areaconst = 200,
  estrato = 4,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)


# Predecir el precio:
precio_predicho <- predict(modelo, newdata = nueva_vivienda)
precio_predicho
##        1 
## 302.7058
nueva_vivienda1.1 <- data.frame(
  areaconst = 200,
  estrato = 5,
  habitaciones = 4,
  parqueaderos = 1,
  banios = 2
)


# Predecir el precio:
precio_predicho1.1 <- predict(modelo, newdata = nueva_vivienda)
precio_predicho1.1
##        1 
## 302.7058

El valor de la predicción se encuentra por debajo del crédito preaprobado.

6. Potenciales ofertas para la solicitud de vivienda 1 en base a la predicción del modelo.

# Ya se había filtrado viviendas que son "Casa" en "Zona Norte"
ofertas <- df_filtrado %>% 
  filter(tipo == "Casa", zona == "Zona Norte")

# Predecir precios con el modelo
ofertas$precio_predicho <- predict(modelo, newdata = ofertas)
ofertas_potenciales <- ofertas %>% 
  filter(precio_predicho <= 350)
ofertas_seleccionadas <- ofertas_potenciales %>% 
  arrange(350 - precio_predicho) %>% 
  head(5)
ofertas_seleccionadas
## # A tibble: 5 × 14
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  4210 Zona N… 01          5     350       200            3      3            4
## 2  4209 Zona N… 02          5     350       300            3      5            6
## 3   819 Zona N… 02          5     350       264            2      3            4
## 4  3043 Zona N… <NA>        5     330       275            2      3            5
## 5  1163 Zona N… <NA>        5     350       216            2      2            4
## # ℹ 5 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   precio_predicho <dbl>

Teniendo en cuenta el credito preaprobado, se dan 5 ofertas que estan en con un precio de 350 mm. Entre las opciones hay casas entre 200 a 300 metros cuadrados. Si la elección se realiza solo por tamaño, entonces la mejor opción sería la número dos, que se encuentra en el barrio El bosque (-76.53010 3.48577 ) y tiene 5 baños y 6 habitaciones.

No obstante, es bueno que se tenga en cuenta otros temas como el barrio y su seguridad, los años de construcción y los alrededores de la vivienda (vías de acceso, parques cercanes, escuelas, centros comerciales, etc). Por eso, es importante que el cliente visite las 5 opciones antes de elegir la definitiva.

A continuación se ve la ubicación de las 5 opciones:

# Alternativamente, un mapa interactivo con leaflet
mapa_leaflet <- leaflet(ofertas_seleccionadas) %>%
  addTiles() %>%
  addCircleMarkers(~longitud, ~latitud,
                   color = "blue", fillOpacity = 0.7,
                   radius = 5, popup = ~paste("Tipo:", tipo, "<br>",
                                              "Precio:", preciom, "mm<br>",
                                              "Zona:", zona))
mapa_leaflet  # Esto abre el mapa interactivo en el visor de RStudio o en el navegador

7 Segunda solicitud de vivienda

La primera solicitud tiene un credito preaprobado de COP 850 mm para una vivienda con las siguientes características:

  • Tipo: Apartamento
  • Área construida: 300 metros cuadrados
  • Parqueaderos: 3
  • Baños: 3
  • Habitaciones: 5
  • Estrato: 5 o 6
  • Zona: Zona Sur

De acuerdo a esto, se realizó un filtro de la base de datos para que las viviendas evaluadas tenga todas las características anteriores.

Filtro que incluya las ofertas que se adapten a lo que se busca en

# Filtrar las viviendas que cumplen con las condiciones del cliente
viviendas_filtradas2 <- vivienda %>%
  filter(tipo == "Apartamento",
         areaconst >= 300,
         parqueaderos >= 3,
         banios >= 3,
         habitaciones >= 5,
         estrato %in% c(5, 6),
         zona == "Zona Sur",
         preciom <= 850)

# Mostrar las primeras filas del resultado
head(viviendas_filtradas2, 5)
## # A tibble: 2 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  7182 Zona S… <NA>        5     730       573            3      8            5
## 2  7512 Zona S… <NA>        5     670       300            3      5            6
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Filtrar las viviendas que cumplen con las condiciones del cliente
viviendas_filtradas2 <- vivienda %>%
  filter(tipo == "Apartamento",
         areaconst >= 300,
         parqueaderos >= 3,
         banios >= 3,
         habitaciones >= 5,
         estrato %in% c(5, 6),
         latitud < 3.418,
         preciom <= 850)

# Mostrar las primeras filas del resultado
head(viviendas_filtradas2, 5)
## # A tibble: 2 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  7182 Zona S… <NA>        5     730       573            3      8            5
## 2  7512 Zona S… <NA>        5     670       300            3      5            6
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>

Siendo que el filtro de la zona sur dio que solo había dos ofertas que cumplian con las caracteristicas buscadas, se valido realizando el mismo filtro pero en vez de usar la “Zona” se usó la latitud, donde se dejó que fuera menor que la media que da la base de datos de viviendas. Se comprobó de esta manera que solo hay dos viviendas que cumplen.

# Tablas de frecuencia para comprobar la consulta
# Tabla de frecuencia para la variable 'zona'
tabla_zona <- table(viviendas_filtradas2$zona)
print(tabla_zona)
## 
## Zona Sur 
##        2
# Tabla de frecuencia para 'tipo' (debe ser solo "Casa")
tabla_tipo <- table(viviendas_filtradas2$tipo)
print(tabla_tipo)
## 
## Apartamento 
##           2
# Tabla para 'estrato'
tabla_estrato <- table(viviendas_filtradas2$estrato)
print(tabla_estrato)
## 
## 5 
## 2

Las dos viviendas son de estrato 5

# Alternativamente, un mapa interactivo con leaflet
mapa_leaflet <- leaflet(viviendas_filtradas2) %>%
  addTiles() %>%
  addCircleMarkers(~longitud, ~latitud,
                   color = "blue", fillOpacity = 0.7,
                   radius = 5, popup = ~paste("Tipo:", tipo, "<br>",
                                              "Precio:", preciom, "mm<br>",
                                              "Zona:", zona))
mapa_leaflet  # Esto abre el mapa interactivo en el visor de RStudio o en el navegador

2. Análisis exploratorio de datos enfocado en la correlación entre la variable respuesta (precio) en función del área construida, estrato, baños, habitaciones y zona donde se ubica la vivienda.

Siendo dos opciones no tendría sentido hacer análisis de correlaciones.

# Calcular la correlación entre precio y las variables numéricas
correlaciones <- cor(viviendas_filtradas2[, c("preciom", "areaconst", "estrato", "banios", "habitaciones")], use = "complete.obs")
## Warning in cor(viviendas_filtradas2[, c("preciom", "areaconst", "estrato", : La
## desviación estándar es cero
print(correlaciones)
##              preciom areaconst estrato banios habitaciones
## preciom            1         1      NA      1           -1
## areaconst          1         1      NA      1           -1
## estrato           NA        NA       1     NA           NA
## banios             1         1      NA      1           -1
## habitaciones      -1        -1      NA     -1            1
library(ggplot2)

ggplot(viviendas_filtradas2, aes(x = areaconst, y = preciom)) +
  geom_point(color = "blue", alpha = 0.6) +
  labs(title = "Precio vs Área Construida",
       x = "Área Construida (m²)", 
       y = "Precio (COP)") +
  theme_minimal()

ggplot(viviendas_filtradas2, aes(x = factor(estrato), y = preciom)) +
  geom_boxplot(fill = "red", alpha = 0.6) +
  labs(title = "Distribución del Precio por Estrato",
       x = "Estrato",
       y = "Precio (COP)") +
  theme_minimal()

3. Modelo de regresión lineal múltiple con precio= f(área construida, estrato, habitaciones, parqueaderos, baños)

# Ajuste del modelo
modelo2 <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = viviendas_filtradas2)

# Resumen del modelo para ver los coeficientes y sus pruebas de significancia
summary(modelo2)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos + 
##     banios, data = viviendas_filtradas2)
## 
## Residuals:
## ALL 2 residuals are 0: no residual degrees of freedom!
## 
## Coefficients: (4 not defined because of singularities)
##              Estimate Std. Error t value Pr(>|t|)
## (Intercept)  604.0659        NaN     NaN      NaN
## areaconst      0.2198        NaN     NaN      NaN
## estrato            NA         NA      NA       NA
## habitaciones       NA         NA      NA       NA
## parqueaderos       NA         NA      NA       NA
## banios             NA         NA      NA       NA
## 
## Residual standard error: NaN on 0 degrees of freedom
## Multiple R-squared:      1,  Adjusted R-squared:    NaN 
## F-statistic:   NaN on 1 and 0 DF,  p-value: NA

Interpretación de coeficientes:

No hay suficientes datos para estimar los parámetros de la regresión. En otras palabras, R no puede calcular los coeficientes, se debería tener más observaciones (ofertas de apartamentos) para poder estimar un modelo con esta cantidad de variables. Como solo hay dos datos R da 1 porque no habría error residual.

4. Validación de supuestos del modelo

#par(mfrow = c(2,2))
#plot(modelo2)

No se puede validar los supuestos del modelo.

Predicción de precio de la vivienda con las características de la solicitud de vivienda 2

nueva_vivienda2 <- data.frame(
  areaconst = 300,
  estrato = 5,
  habitaciones = 5,
  parqueaderos = 3,
  banios = 3
)


# Predecir el precio:
precio_predicho2 <- predict(modelo2, newdata = nueva_vivienda2)
## Warning in predict.lm(modelo2, newdata = nueva_vivienda2): prediction from
## rank-deficient fit; attr(*, "non-estim") has doubtful cases
precio_predicho2
##   1 
## 670 
## attr(,"non-estim")
## 1 
## 1

El rango es deficiente para el modelo

Potenciales ofertas para la solicitud de vivienda 1 en base a la predicción del modelo.

# Ya se había filtrado viviendas que son "Casa" en "Zona Norte"
ofertas <- viviendas_filtradas2 %>% 
  filter(tipo == "Apartamento", zona == "Zona Sur")

# Predecir precios con el modelo
ofertas$precio_predicho <- predict(modelo2, newdata = ofertas)
ofertas_potenciales <- ofertas %>% 
  filter(precio_predicho <= 850)
ofertas_seleccionadas2 <- ofertas_potenciales %>% 
  arrange(350 - precio_predicho) %>% 
  head(5)
ofertas_seleccionadas2
## # A tibble: 2 × 14
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  7182 Zona S… <NA>        5     730       573            3      8            5
## 2  7512 Zona S… <NA>        5     670       300            3      5            6
## # ℹ 5 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>,
## #   precio_predicho <dbl>

Teniendo en cuenta el credito preaprobado, se dan 2 ofertas que estan en con un precio menor a 850 mm. Entre las opciones hay un apartamento de 573 y otro de 300 metros cuadrados. Si la elección se realiza solo por tamaño, entonces la mejor opción sería la primera, que se encuentra en el barrio Guadalupe (-76.548 3.408) y tiene 8 baños y 5 habitaciones. Pero teniendo en cuenta que es mucho más grande de lo que se buscaba y que la diferencia de precio entre los dos apartamentos es 8.9% la opción más inteligente sería la segunda.

No obstante, es bueno que se tenga en cuenta otros temas como el barrio (seguridad), los años de construcción y los alrededores de la vivienda (vías de acceso, parques cercanes, escuelas, centros comerciales, etc). Por eso, es importante que el cliente visite ambas opciones antes de elegir la definitiva.

A continuación se ve la ubicación de las 2 opciones:

# Alternativamente, un mapa interactivo con leaflet
mapa_leaflet <- leaflet(ofertas_seleccionadas2) %>%
  addTiles() %>%
  addCircleMarkers(~longitud, ~latitud,
                   color = "blue", fillOpacity = 0.7,
                   radius = 5, popup = ~paste("Tipo:", tipo, "<br>",
                                              "Precio:", preciom, "mm<br>",
                                              "Zona:", zona))
mapa_leaflet  # Esto abre el mapa interactivo en el visor de RStudio o en el navegador