Problema

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:

# Create a data frame with the table data
table_data <- data.frame(
  Características = c("Tipo", "área construida", "parqueaderos", "baños", "habitaciones", "estrato", "zona", "crédito preaprobado"),
  Vivienda_1 = c("Casa", "200", "1", "2", "4", "4 o 5", "Norte", "350 millones"),
  Vivienda_2 = c("Apartamento", "300", "3", "3", "5", "5 o 6", "Sur", "850 millones")
)

kable(table_data, caption = "Comparación de Viviendas")
Comparación de Viviendas
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) .

Informe ejecutivo

Resumen

Se llevó a cabo un análisis estadístico riguroso para identificar las mejores opciones de selección de ofertas en el proceso de adquisición de la vivienda. Para ello, se realizó una limpieza de datos y se estructuró la información registrada en la fuente de datos.

Adicionalmente, se realizó una segmentación de datos para el entrenamiento de modelos de regresión lineal múltiple en las categorías: \(zona norte - casas\) y \(zona sur - apartamentos\), asegurando que las ofertas utilizadas estén correctamente ubicadas en el sector correspondiente.

Limpieza de datos y modelación

Ofertas zona norte casas

Del total de 700 ofertas disponibles en la zona norte con tipo de vivienda casas, se realizó una limpieza de datos para excluir aquellas ubicadas fuera de la zona, obteniendo un total de 449 ofertas válidas.

Ofertas zona sur apartamentos

Del total de 2777 ofertas disponibles en la zona sur con tipo de vivienda sur, se realizó una limpieza de datos para excluir aquellas ubicadas fuera de la zona, obteniendo un total de 2214 ofertas válidas.

Modelo de regresión lineal multiple zona norte casas

Se generó un modelo de regresión lineal multiple \(log - ling\) el cual obtuvo un \(Adjusted R2 = 0.758\)

\[\begin{align*} precio = 4.8123353 + 0.0013657 \cdot areaconstruida + 0.0816912 \cdot banios + 0.2766516 \cdot e4 + \\ 0.4192394 \cdot e5 + 0.6710490 \cdot e6 + 0.0377740 \cdot parqueaderos + 0.0256739 \cdot habitaciones \end{align*}\]

Modelo de regresión lineal multiple zona norte casas

Se generó un modelo de regresión lineal multiple \(log - ling\) el cual obtuvo un \(Adjusted R2 = 0.821\)

\[ \begin{align*} precio = 4.387416 + 0.002226 \cdot areaconstruida + 0.111078 \cdot banios + 0.282537 \cdot e4 + \\ 0.462540 \cdot e5 + 0.784609 \cdot e6 + 0.158962 \cdot parqueaderos + 0.019957 \cdot habitaciones \end{align*} \]

Recomendación

  • Para la vivienda tipo casa en la zona norte, se recomienda al cliente adquirir una propiedad en el barrio La Merced, ya que se identificaron tres ofertas que cumplen con todas las condiciones requeridas, incluyendo un precio máximo acorde con el valor del crédito preaprobado. Además, los predios seleccionados son comparables con base en un análisis de mercado. Ofertas 937, 1108 y 1163.

  • Para la vivienda tipo apartamento en la zona sur, se recomienda al cliente adquirir el predio ubicado en el barrio Pance, con un valor de 850 millones, ya que cumple con todas las condiciones requeridas, especialmente la área construida. Este inmueble es el único que satisface la exigencia de 300 metros cuadrados, sin haberse identificado otra oferta con dicha característica. Oferta con id 5574.

Anexo 1 - Analisis exploratorio y tratamiento de datos

Estructuración de datos

El conjunto de datos analiza la dinámica inmobiliaria en la ciudad de Cali y está compuesto por 8,322 observaciones y 13 variables. Estas incluyen características físicas de las propiedades, como el número de habitaciones, baños, área construida y parqueaderos, además de información geoespacial, como la zona, el barrio, la latitud y la longitud. Asimismo, se incluyen variables económicas como el estrato socioeconómico y el precio de venta, junto con el tipo de vivienda (casa o apartamento). Esta estructuración permite un análisis integral del mercado inmobiliario en la ciudad.

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")=
  .. cols(
  ..   id = col_double(),
  ..   zona = col_character(),
  ..   piso = col_character(),
  ..   estrato = col_double(),
  ..   preciom = col_double(),
  ..   areaconst = col_double(),
  ..   parqueaderos = col_double(),
  ..   banios = col_double(),
  ..   habitaciones = col_double(),
  ..   tipo = col_character(),
  ..   barrio = col_character(),
  ..   longitud = col_double(),
  ..   latitud = col_double()
  .. )
 - attr(*, "problems")=<externalptr> 
head(vivienda)
# A tibble: 6 × 13
     id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
  <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
1  1147 Zona O… <NA>        3     250        70            1      3            6
2  1169 Zona O… <NA>        3     320       120            1      2            3
3  1350 Zona O… <NA>        3     350       220            2      2            4
4  5992 Zona S… 02          4     400       280            3      5            3
5  1212 Zona N… 01          5     260        90            1      2            3
6  1724 Zona N… 01          5     240        87            1      3            3
# ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
tabla_variables <- data.frame(
  Variable = c("id", "zona", "piso", "estrato", "precio", "area construida", 
               "parqueaderos", "banios", "habitaciones", "tipo", "barrio", 
               "longitud", "latitud"),
  Tipo = c("Cuantitativa(Discreta)", "Cualitativa(Nominal)", 
           "Cuantitativa(Discreta)", "Cualitativa(Ordinal)", 
           "Cuantitativa(Discreta)", "Cuantitativa(Continua)", 
           "Cuantitativa(Discreta)", "Cuantitativa(Discreta)", 
           "Cuantitativa(Discreta)", "Cualitativa(Nominal)", 
           "Cualitativa(Nominal)", "Cuantitativa(Continua)", 
           "Cuantitativa(Continua)"))
tabla_variables
          Variable                   Tipo
1               id Cuantitativa(Discreta)
2             zona   Cualitativa(Nominal)
3             piso Cuantitativa(Discreta)
4          estrato   Cualitativa(Ordinal)
5           precio Cuantitativa(Discreta)
6  area construida Cuantitativa(Continua)
7     parqueaderos Cuantitativa(Discreta)
8           banios Cuantitativa(Discreta)
9     habitaciones Cuantitativa(Discreta)
10            tipo   Cualitativa(Nominal)
11          barrio   Cualitativa(Nominal)
12        longitud Cuantitativa(Continua)
13         latitud Cuantitativa(Continua)

El análisis de valores faltantes revela que las principales variables afectadas son piso y parqueaderos, con 2,638 y 1,605 datos ausentes, respectivamente, además de algunas ausencias menores en otras columnas. Para su tratamiento, se puede optar por la imputación con la mediana en variables numéricas y la moda en categóricas, o bien eliminar registros incompletos si representan una fracción pequeña del total, asegurando así la integridad del análisis sin distorsionar los resultados.

colSums(is.na(vivienda))
          id         zona         piso      estrato      preciom    areaconst 
           3            3         2638            3            2            3 
parqueaderos       banios habitaciones         tipo       barrio     longitud 
        1605            3            3            3            3            3 
     latitud 
           3 

Se realizó una limpieza y transformación de datos para garantizar la calidad del insumo. Primero, se eliminaron registros completamente vacíos y aquellos sin identificador único. Luego, se normalizaron las variables categóricas estandarizando nomenclaturas en minúsculas. Posteriormente, se imputaron valores faltantes en piso utilizando la mediana por tipo de vivienda, mientras que los valores nulos en parqueaderos se reemplazaron por cero. Finalmente, se filtraron registros inconsistentes eliminando aquellos sin habitaciones o baños y asegurando la presencia de al menos una variable relevante con valores mayores a cero, obteniendo así un dataset completamente limpio y sin valores faltantes, listo para el análisis.

# 1. Limpieza de valores NA y registros no válidos
vivienda <- vivienda[rowSums(is.na(vivienda)) != ncol(vivienda),] # Eliminar filas completamente vacías
vivienda <- vivienda[!is.na(vivienda$id),] # Eliminar registros sin identificador único

# 2. Normalización de datos categóricos
vivienda$zona <- tolower(vivienda$zona) # Convertir zonas a minúsculas
vivienda$tipo <- tolower(vivienda$tipo) # Convertir tipo de vivienda a minúsculas
vivienda$tipo[vivienda$tipo == "apto"] <- "apartamento" # Unificar nomenclatura
vivienda$barrio <- tolower(vivienda$barrio) # Convertir barrios a minúsculas

# 3. Transformaciones y limpieza de variables numéricas
vivienda$piso <- as.numeric(vivienda$piso) # Convertir a numérico
vivienda$parqueaderos[is.na(vivienda$parqueaderos)] <- 0 # Reemplazar NA en parqueaderos por 0

# 4. Filtrado de datos inconsistentes o irrelevantes
vivienda <- vivienda[vivienda$habitaciones != 0,] # Eliminar registros sin habitaciones
vivienda <- vivienda[vivienda$banios != 0,] # Eliminar registros sin baños
vivienda <- vivienda[rowSums(vivienda[,c("parqueaderos", "banios", "habitaciones")] == 0) < 3,] # Mantener registros con al menos una de estas variables > 0
vivienda <- vivienda[rowSums(vivienda[,c("banios", "habitaciones")] == 0) < 2,] # Mantener registros con al menos una de estas variables > 0

# Calcular la mediana del piso por tipo de vivienda
vivienda <- vivienda %>%
  group_by(tipo) %>%
  mutate(piso = ifelse(is.na(piso), median(piso, na.rm = TRUE), piso)) %>%
  ungroup()
colSums(is.na(vivienda))
          id         zona         piso      estrato      preciom    areaconst 
           0            0            0            0            0            0 
parqueaderos       banios habitaciones         tipo       barrio     longitud 
           0            0            0            0            0            0 
     latitud 
           0 

Se realizó un tratamiento a la variable barrio con el objetivo de corregir inconsistencias en la nomenclatura. Primero, se normalizaron los valores convirtiéndolos a minúsculas, eliminando tildes y espacios adicionales. Luego, se aplicó una lista de correcciones para unificar nombres que presentaban variaciones en su escritura. Como resultado, el número de barrios distintos se redujo de 437 a 386, mejorando la coherencia y calidad del dataset para un análisis más preciso.

# Cargar el paquete stringr
library(stringr)

normalize_barrio <- function(barrio) {
  barrio <- tolower(barrio) # Convertir a minúsculas
  barrio <- str_replace_all(barrio, "[áéíóú]", function(x) chartr("áéíóú", "aeiou", x)) # Reemplazar tildes
  barrio <- str_trim(barrio) # Eliminar espacios en blanco adicionales
  return(barrio)
}

# Aplicar la función a la columna 'barrio'
vivienda <- vivienda %>%
  mutate(barrio = normalize_barrio(barrio))

# Definir la lista de correcciones
correcciones <- list(
  "meléndez" = "melendez",
  "ciudad meléndez" = "ciudad melendez",
  "juanamb√∫" = "juanambu",
  "el trébol" = "el trebol",
  "las américas" = "las americas",
  "rep√∫blica de israel" = "republica de israel",
  "base aérea" = "base aerea",
  "alférez real" = "alferez real"
)

# Corregir los nombres en la columna 'barrio'
vivienda$barrio <- sapply(vivienda$barrio, function(barrio) {
  if (barrio %in% names(correcciones)) {
    return(correcciones[[barrio]])
  } else {
    return(barrio)
  }
})
vivienda$estrato <- as.factor(vivienda$estrato)

Anexo 2 - Casas de la zona norte

Paso 1. Selección de datos de la zona norte correspondientes a casas

Se realizó un filtrado de datos seleccionando las ofertas clasificadas en la zona norte y tipo de vivienda casa.

base1 <- vivienda[vivienda$zona == "zona norte" & vivienda$tipo == "casa",]
kable(head(base1, 3), caption = "Primeros 3 Registros de Casas en Zona Norte")
Primeros 3 Registros de Casas en Zona Norte
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 0 7 6 casa acopi -76.52950 3.38527
library(leaflet)
library(RColorBrewer)

pal <- colorNumeric(
  palette = rev(brewer.pal(9, "RdYlBu")),  # Puedes cambiar la paleta de colores
  domain = base1$preciom
)

leaflet(data = base1) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    radius = ~sqrt(preciom)/10, 
    color = ~pal(preciom),
    popup = ~paste("Precio:", preciom)) %>%
  addLegend(
    "bottomright", 
    pal = pal, 
    values = ~preciom,
    title = "Precio")

Se identificó que, de las 700 ofertas relacionadas en la zona norte con tipo de vivienda casa, algunas se encuentran ubicadas en otras zonas de la ciudad de Cali. Esta situación se debe principalmente a una incorrecta georreferenciación durante la clasificación de las ofertas en dicha zona.

Con el fin de limpiar las ofertas que se encuentran por fuera de la zona norte, se realizó un filtro a partir de las coordenadas limítrofes de la zona de norte de la ciudad de Cali, estas son coordenadas mínimas \(longitud = -76.52732195\) \(latitud = 3.4369546\) y coordenadas máximas \(longitud = -76.46485570\) \(latitud = 3.51233553\)

base1_limpia <- base1[base1$longitud >= -76.52732195 & base1$latitud >= 3.4369546 & base1$longitud <= -76.46485570 & base1$latitud <= 3.51233553,]
pal <- colorNumeric(
  palette = rev(brewer.pal(9, "RdYlBu")),  # Puedes cambiar la paleta de colores
  domain = base1$preciom
)

leaflet(data = base1_limpia) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    radius = ~sqrt(preciom)/20, 
    color = ~pal(preciom),
    #fillOpacity = 0.2,
    #clusterOptions = markerClusterOptions(),
    popup = ~paste("Precio:", preciom)) %>%
  addLegend(
    "bottomright", 
    pal = pal, 
    values = ~preciom,
    title = "Precio")

Paso 2. Análisis exploratorio

library(GGally)
quant_vars <- base1_limpia %>%
  select(preciom, estrato, areaconst,parqueaderos, banios, habitaciones)
ggpairs(quant_vars, title = " ")

- Se tiene una alta correlación (0.726) entre el area construida y el precio de la vivienda en el resto de variables no se tiene una alta correlación.

Se realizó una revisión de colinealidad con el análisis de factor de varianza (VIF).

vif(lm(preciom ~ areaconst + estrato + banios + parqueaderos, data =base1_limpia))
                 GVIF Df GVIF^(1/(2*Df))
areaconst    1.530232  1        1.237025
estrato      1.512030  3        1.071339
banios       1.480292  1        1.216673
parqueaderos 1.334996  1        1.155420

Dado que todos los valores están por debajo de 5 Se pueden mantener todas las variables en el modelo de regresión múltiple sin problemas.

Paso 3. modelo de regresión lineal múltiple

Se realizó la estimación de dos modelos \(1) lin - lin\), \(2)log - lin\) y se comparó su rendimiento para determinar cual es el que tiene mejor ajustes para la estimación.

model_1 <- lm(preciom ~ areaconst + banios + estrato + parqueaderos + habitaciones,data = base1_limpia)
model_2 <- lm(log(preciom) ~ areaconst + banios + estrato + parqueaderos + habitaciones,data = base1_limpia)
stargazer(model_1,model_2,type = "text")

===========================================================
                                   Dependent variable:     
                               ----------------------------
                                  preciom     log(preciom) 
                                    (1)            (2)     
-----------------------------------------------------------
areaconst                         0.591***      0.001***   
                                  (0.045)       (0.0001)   
                                                           
banios                           28.394***      0.082***   
                                  (5.534)        (0.013)   
                                                           
estrato4                         65.146***      0.277***   
                                  (14.968)       (0.036)   
                                                           
estrato5                         117.805***     0.419***   
                                  (14.777)       (0.035)   
                                                           
estrato6                         335.935***     0.671***   
                                  (50.713)       (0.121)   
                                                           
parqueaderos                     15.608***      0.038***   
                                  (3.910)        (0.009)   
                                                           
habitaciones                       0.618         0.026**   
                                  (4.248)        (0.010)   
                                                           
Constant                         56.110***      4.812***   
                                  (16.045)       (0.038)   
                                                           
-----------------------------------------------------------
Observations                        449            449     
R2                                 0.695          0.761    
Adjusted R2                        0.690          0.758    
Residual Std. Error (df = 441)    107.619         0.256    
F Statistic (df = 7; 441)        143.614***    200.930***  
===========================================================
Note:                           *p<0.1; **p<0.05; ***p<0.01

Como resultado, se obtuvo que el \(modelo2\) \(2)log - lin\) tiene un mejor ajuste en todas las variables se tiene un \(Adjusted R2 = 0.758\)

summary(model_2)

Call:
lm(formula = log(preciom) ~ areaconst + banios + estrato + parqueaderos + 
    habitaciones, data = base1_limpia)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.48673 -0.15191 -0.01026  0.14091  1.11204 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  4.8123353  0.0382240 125.898  < 2e-16 ***
areaconst    0.0013657  0.0001075  12.704  < 2e-16 ***
banios       0.0816912  0.0131841   6.196 1.33e-09 ***
estrato4     0.2766516  0.0356588   7.758 6.04e-14 ***
estrato5     0.4192394  0.0352021  11.909  < 2e-16 ***
estrato6     0.6710490  0.1208116   5.555 4.82e-08 ***
parqueaderos 0.0377740  0.0093151   4.055 5.92e-05 ***
habitaciones 0.0256739  0.0101200   2.537   0.0115 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.2564 on 441 degrees of freedom
Multiple R-squared:  0.7613,    Adjusted R-squared:  0.7575 
F-statistic: 200.9 on 7 and 441 DF,  p-value: < 2.2e-16
par(mfrow= c(2,2))
plot(model_2)

### Paso 4. Validación de supuestos

Test de Shapiro-Wilk (normalidad):

shapiro.test(residuals(model_2))

    Shapiro-Wilk normality test

data:  residuals(model_2)
W = 0.9687, p-value = 3.332e-08

Dado el p-valor obtiene un valor menor al nivel de significancia (0.05), los residuos del modelo no tienen una distribución normal.

Test de Durwin-Watson (independencia):

library(lmtest)
dwtest(model_2)

    Durbin-Watson test

data:  model_2
DW = 1.5587, p-value = 8.973e-07
alternative hypothesis: true autocorrelation is greater than 0

Dado el p-valor obtiene un valor menor al nivel de significancia (0.05), los errores del modelo no son independientes.

Test de Goldfeld-Quandt (varianza constante):

gqtest(model_2)

    Goldfeld-Quandt test

data:  model_2
GQ = 1.1095, df1 = 217, df2 = 216, p-value = 0.2226
alternative hypothesis: variance increases from segment 1 to 2

Dado el p-valor obtiene un valor menor al nivel de significancia (0.05), se puede afirmar que la varianza de los errores no es constante.

Paso 5. Estimación de valor

Al realizar la estimación del valor de la vivienda con el modelo seleccionado se cumple con todas las condiciones y características de la solicitud del usuario, una vivienda en estrato 4 tendría un valor estimado de \(288 millones\) y en estrato 5 de alrededor de \(333 millones\)

modeloprecio <- function(ac,b,e4,e5,e6,p,h){
  nprecio = 4.8123353 + 0.0013657*ac + 0.0816912*b + 0.2766516*e4 + 0.4192394*e5 + 0.6710490*e6 + 0.0377740*p + 0.0256739*h
  return(nprecio)}
exp(modeloprecio(200,2,1,0,0,1,4)) # estrato 4
[1] 288.8706
exp(modeloprecio(200,2,0,1,0,1,4)) # estrato 5
[1] 333.1413

Paso 6. Ofertas potenciales

Se realizó la selección de 8 posibles ofertas potenciales para el cliente de casas en la zona norte de Cali, las cuales tengan como minino los 200 metros de área construida, 4 habitaciones, máximo 3 baños y minio un parqueadero en los estratos 4 y 5. El resultado son estás ofertas:

ofertas_base1 <- base1_limpia %>%
  filter(as.numeric(as.character(estrato)) < 6 & as.numeric(as.character(estrato)) >= 4,
         areaconst >= 200,
         parqueaderos >= 1,
         banios <= 3,
         habitaciones == 4,
         preciom <= 350)

kable(head(ofertas_base1), caption = "Ofertas de casas zona norte")
Ofertas de casas zona norte
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
819 zona norte 2 5 350 264 2 3 4 casa la flora -76.50330 3.46412
937 zona norte 2 4 350 280 2 3 4 casa la merced -76.50603 3.46643
1108 zona norte 2 4 330 260 1 3 4 casa la merced -76.51060 3.48108
1163 zona norte 2 5 350 216 2 2 4 casa la merced -76.51218 3.48181
1887 zona norte 1 5 340 203 2 3 4 casa vipasa -76.51803 3.48257
1842 zona norte 2 5 350 240 2 3 4 casa vipasa -76.51800 3.48100
pal <- colorNumeric(
  palette = "blue",  # Puedes cambiar la paleta de colores
  domain = base1$preciom
)

leaflet(data = ofertas_base1) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    radius = ~sqrt(preciom)/5, 
    color = ~pal(preciom),
    #fillOpacity = 0.2,
    #clusterOptions = markerClusterOptions(),
    popup = ~paste("Precio:", preciom)) %>%
  addLegend(
    "bottomright", 
    pal = pal, 
    values = ~preciom,
    title = "Precio")

Anexo 3 - Apartamentos de la zona sur

Paso 1. Selección de datos de la zona norte correspondientes a casas

Se realizó un filtrado de datos seleccionando las ofertas clasificadas en la zona sur y tipo de vivienda apartamento.

base2 <- vivienda[vivienda$zona == "zona sur" & vivienda$tipo == "apartamento",]
kable(head(base2, 3), caption = "Primeros 3 Registros de Casas en Zona Norte")
Primeros 3 Registros de Casas en Zona Norte
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 4 6 875 194 2 5 3 apartamento aguacatal -76.55700 3.45900
library(leaflet)
library(RColorBrewer)

pal <- colorNumeric(
  palette = rev(brewer.pal(9, "RdYlBu")),  # Puedes cambiar la paleta de colores
  domain = base1$preciom
)

leaflet(data = base2) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    radius = ~sqrt(preciom)/10, 
    color = ~pal(preciom),
    popup = ~paste("Precio:", preciom)) %>%
  addLegend(
    "bottomright", 
    pal = pal, 
    values = ~preciom,
    title = "Precio")

Se identificó que, de las 2777 ofertas relacionadas en la zona sur con tipo de vivienda apartamento, algunas se encuentran ubicadas en otras zonas de la ciudad de Cali. Esta situación se debe principalmente a una incorrecta georreferenciación durante la clasificación de las ofertas en dicha zona.

Con el fin de limpiar las ofertas que se encuentran por fuera de la zona sur, se realizó un filtro a partir de las coordenadas limítrofes de la zona de sur de la ciudad de Cali, estas son coordenadas mínimas \(longitud = -76.60063914\) \(latitud = 3.30352817\) y coordenadas máximas \(longitud = -76.49858161\) \(latitud = 3.41262415\)

base2_limpia <- base2[base2$longitud >= -76.60063914 & base2$latitud >= 3.30352817 & base2$longitud <= -76.49858161 & base2$latitud <= 3.41262415,]
pal <- colorNumeric(
  palette = rev(brewer.pal(9, "RdYlBu")),  # Puedes cambiar la paleta de colores
  domain = base1$preciom
)

leaflet(data = base2_limpia) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    radius = ~sqrt(preciom)/20, 
    color = ~pal(preciom),
    #fillOpacity = 0.2,
    #clusterOptions = markerClusterOptions(),
    popup = ~paste("Precio:", preciom)) %>%
  addLegend(
    "bottomright", 
    pal = pal, 
    values = ~preciom,
    title = "Precio")

Paso 2. Análisis exploratorio

library(GGally)
quant_vars <- base2_limpia %>%
  select(preciom, estrato, areaconst,parqueaderos, banios, habitaciones)
ggpairs(quant_vars, title = " ")

- Se tiene una alta correlación (0.751) entre el área construida y el precio. - Se tiene una alta correlación (0.706) entre el número de parqueaderos y el precio. - Se tiene una alta correlación (0.706) entre el número de baños y el precio.

Se realizó una revisión de colinealidad con el análisis de factor de varianza (VIF).

vif(lm(preciom ~ areaconst + estrato + banios + parqueaderos, data =base2_limpia))
                 GVIF Df GVIF^(1/(2*Df))
areaconst    2.078719  1        1.441776
estrato      1.826155  3        1.105579
banios       2.386784  1        1.544922
parqueaderos 1.953618  1        1.397719

Dado que todos los valores están por debajo de 5 Se pueden mantener todas las variables en el modelo de regresión múltiple sin problemas.

Paso 3. modelo de regresión lineal múltiple

Se realizó la estimación de dos modelos \(1) lin - lin\), \(2)log - lin\) y se comparó su rendimiento para determinar cual es el que tiene mejor ajustes para la estimación.

b2_model_1 <- lm(preciom ~ areaconst + banios + estrato + parqueaderos + habitaciones,data = base2_limpia)
b2_model_2 <- lm(log(preciom) ~ areaconst + banios + estrato + parqueaderos + habitaciones,data = base2_limpia)
stargazer(b2_model_1,b2_model_2,type = "text")

============================================================
                                    Dependent variable:     
                                ----------------------------
                                   preciom     log(preciom) 
                                     (1)            (2)     
------------------------------------------------------------
areaconst                          1.269***      0.002***   
                                   (0.052)       (0.0001)   
                                                            
banios                            46.114***      0.111***   
                                   (3.461)        (0.008)   
                                                            
estrato4                           17.965**      0.283***   
                                   (8.705)        (0.021)   
                                                            
estrato5                          31.794***      0.463***   
                                   (8.966)        (0.021)   
                                                            
estrato6                          183.966***     0.785***   
                                   (11.015)       (0.026)   
                                                            
parqueaderos                      54.117***      0.159***   
                                   (3.564)        (0.008)   
                                                            
habitaciones                      -15.510***      0.020**   
                                   (3.876)        (0.009)   
                                                            
Constant                           -10.804       4.387***   
                                   (12.433)       (0.029)   
                                                            
------------------------------------------------------------
Observations                        2,214          2,214    
R2                                  0.783          0.822    
Adjusted R2                         0.783          0.821    
Residual Std. Error (df = 2206)     91.828         0.217    
F Statistic (df = 7; 2206)       1,139.039***  1,451.127*** 
============================================================
Note:                            *p<0.1; **p<0.05; ***p<0.01

Como resultado, se obtuvo que el \(modelo2\) \(2)log - lin\) tiene un mejor ajuste en todas las variables se tiene un \(Adjusted R2 = 0.821\)

summary(b2_model_2)

Call:
lm(formula = log(preciom) ~ areaconst + banios + estrato + parqueaderos + 
    habitaciones, data = base2_limpia)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.77620 -0.13805 -0.00499  0.14488  1.15616 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)  4.387416   0.029393 149.267   <2e-16 ***
areaconst    0.002226   0.000124  17.946   <2e-16 ***
banios       0.111078   0.008183  13.574   <2e-16 ***
estrato4     0.282537   0.020580  13.729   <2e-16 ***
estrato5     0.462540   0.021197  21.821   <2e-16 ***
estrato6     0.784609   0.026041  30.129   <2e-16 ***
parqueaderos 0.158962   0.008426  18.867   <2e-16 ***
habitaciones 0.019957   0.009164   2.178   0.0295 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.2171 on 2206 degrees of freedom
Multiple R-squared:  0.8216,    Adjusted R-squared:  0.821 
F-statistic:  1451 on 7 and 2206 DF,  p-value: < 2.2e-16
par(mfrow= c(2,2))
plot(b2_model_2)

### Paso 4. Validación de supuestos

Test de Shapiro-Wilk (normalidad):

shapiro.test(residuals(b2_model_2))

    Shapiro-Wilk normality test

data:  residuals(b2_model_2)
W = 0.97835, p-value < 2.2e-16

Dado el p-valor obtiene un valor menor al nivel de significancia (0.05), los residuos del modelo no tienen una distribución normal.

Test de Durwin-Watson (independencia):

library(lmtest)
dwtest(b2_model_2)

    Durbin-Watson test

data:  b2_model_2
DW = 1.6209, p-value < 2.2e-16
alternative hypothesis: true autocorrelation is greater than 0

Dado el p-valor obtiene un valor menor al nivel de significancia (0.05), los errores del modelo no son independientes.

Test de Goldfeld-Quandt (varianza constante):

gqtest(b2_model_2)

    Goldfeld-Quandt test

data:  b2_model_2
GQ = 0.7842, df1 = 1099, df2 = 1099, p-value = 1
alternative hypothesis: variance increases from segment 1 to 2

Dado el p-valor obtiene un valor mayor al nivel de significancia (0.05), se puede afirmar que la varianza de los errores es constante.

Paso 5. Estimación de valor

Al realizar la estimación del valor de la vivienda con el modelo seleccionado se cumple con todas las condiciones y características de la solicitud del usuario, superando en tal solo \(3 millones\) el valor de la vivienda con todas las caracteristicas para el estrato 6. Una vivienda en estrato 5 tendría un valor estimado de \(618 millones\) y en estrato 6 de alrededor de \(853 millones\). En este caso la diferencia de estrato si marca una clara diferencia en el valor del inmueble.

b2_modeloprecio <- function(ac,b,e4,e5,e6,p,h){
  nprecio = 4.387416 + 0.002226*ac + 0.111078*b + 0.282537*e4 + 0.462540*e5 + 0.784609*e6 + 0.158962*p + 0.019957*h
  return(nprecio)}
exp(b2_modeloprecio(300,3,0,1,0,3,5)) # estrato 5
[1] 618.7251
exp(b2_modeloprecio(300,3,0,0,1,3,5)) # estrato 6
[1] 853.8282

Paso 6. Ofertas potenciales

Se realizó la selección de 8 posibles ofertas potenciales para el cliente de casas en la zona norte de Cali, las cuales tengan como minino los 200 metros de área construida, 4 habitaciones, máximo 3 baños y minio un parqueadero en los estratos 4 y 5. El resultado son estás ofertas:

ofertas_base2 <- base2_limpia %>%
  filter(as.numeric(as.character(estrato)) <= 6 & as.numeric(as.character(estrato)) > 4,
         areaconst >= 200,
         banios <= 4,
         preciom <= 850 & preciom >= 700)


kable(head(ofertas_base2), caption = "Ofertas de casas zona norte")
Ofertas de casas zona norte
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
3848 zona sur 12 6 760 200 2 3 3 apartamento ciudad jardin -76.52897 3.36403
4133 zona sur 12 6 760 200 2 2 3 apartamento ciudad jardin -76.52999 3.36550
3603 zona sur 1 6 833 213 2 3 3 apartamento ciudad jardin pance -76.52726 3.34865
4266 zona sur 5 6 700 250 2 4 5 apartamento el ingenio -76.53043 3.37062
3827 zona sur 2 6 820 213 2 3 3 apartamento pance -76.52888 3.35064
5574 zona sur 4 6 850 352 4 3 3 apartamento pance -76.53729 3.34265
pal <- colorNumeric(
  palette = "blue",  # Puedes cambiar la paleta de colores
  domain = base1$preciom
)

leaflet(data = ofertas_base2) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    radius = ~sqrt(preciom)/5, 
    color = ~pal(preciom),
    #fillOpacity = 0.2,
    #clusterOptions = markerClusterOptions(),
    popup = ~paste("Precio:", preciom)) %>%
  addLegend(
    "bottomright", 
    pal = pal, 
    values = ~preciom,
    title = "Precio")