Actividad 2 / Caso C&A

Enunciado

María 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).

Datos

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

Pasos

Paso 1

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.

# Cargar archivo de datos
library(paqueteMODELOS)
data('vivienda')

# Ver encabezado / primeras filas
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>
# Ver últimas filas
tail(vivienda)
## # A tibble: 6 × 13
##      id zona    piso  estrato preciom areaconst parqueaderos banios habitaciones
##   <dbl> <chr>   <chr>   <dbl>   <dbl>     <dbl>        <dbl>  <dbl>        <dbl>
## 1  6417 Zona S… <NA>        6    1800       400            3      6            5
## 2  6998 Zona S… <NA>        6    1000       189            3      5            4
## 3  8139 Zona S… <NA>        5     530       142            2      4            4
## 4    NA <NA>    <NA>       NA      NA        NA           NA     NA           NA
## 5    NA <NA>    <NA>       NA      NA        NA           NA     NA           NA
## 6    NA <NA>    <NA>       NA     330        NA           NA     NA           NA
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Dataframe datos vivienda
vivienda_df = as.data.frame(vivienda)

# Descartar 3 últimas filas
vivienda_df = vivienda_df %>% 
  slice(1:(n() - 3)) %>%
  arrange(id)

# Estructura datos
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>

Base1

# Filtrar base de datos
base1 = vivienda_df %>%
  filter(tipo == 'Casa' & zona == 'Zona Norte')

# Estructura base1
str(base1)
## 'data.frame':    722 obs. of  13 variables:
##  $ id          : num  58 88 94 98 103 110 118 122 124 126 ...
##  $ zona        : chr  "Zona Norte" "Zona Norte" "Zona Norte" "Zona Norte" ...
##  $ piso        : chr  NA NA "02" NA ...
##  $ estrato     : num  5 3 4 3 3 3 3 3 3 3 ...
##  $ preciom     : num  400 175 265 120 380 125 140 150 380 140 ...
##  $ areaconst   : num  212 130 162 130 177 140 130 93 400 102 ...
##  $ parqueaderos: num  NA NA 1 1 NA 1 1 NA 1 NA ...
##  $ banios      : num  2 3 3 2 0 2 2 1 3 1 ...
##  $ habitaciones: num  4 4 4 4 0 4 3 4 7 3 ...
##  $ tipo        : chr  "Casa" "Casa" "Casa" "Casa" ...
##  $ barrio      : chr  "santa mónica residencial" "brisas de los" "zona norte" "floralia" ...
##  $ longitud    : num  -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num  3.42 3.48 3.47 3.47 3.47 ...
# Ver encabezado / primeras filas base1
kable(head(base1))
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
58 Zona Norte NA 5 400 212 NA 2 4 Casa santa mónica residencial -76.47300 3.41800
88 Zona Norte NA 3 175 130 NA 3 4 Casa brisas de los -76.48200 3.47800
94 Zona Norte 02 4 265 162 1 3 4 Casa zona norte -76.48238 3.46786
98 Zona Norte NA 3 120 130 1 2 4 Casa floralia -76.48271 3.47055
103 Zona Norte NA 3 380 177 NA 0 0 Casa gaitan -76.48302 3.47252
110 Zona Norte 03 3 125 140 1 2 4 Casa calimio norte -76.48386 3.48502
# Últimas fileas base 1
kable(tail(base1))
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
717 7824 Zona Norte 02 4 600 160 1 4 5 Casa acopi -76.55210 3.42125
718 7885 Zona Norte NA 3 330 280 2 3 5 Casa popular -76.55290 3.42135
719 7987 Zona Norte 02 5 420 200 4 4 5 Casa acopi -76.55363 3.40050
720 8088 Zona Norte NA 3 149 130 NA 2 3 Casa brisas de los -76.55500 3.41700
721 8318 Zona Norte NA 4 580 295 2 5 5 Casa la flora -76.58876 3.46348
722 8319 Zona Norte NA 5 1400 838 1 5 5 Casa la flora -76.58915 3.46467

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.

Ubicación espacial de las viviendas con leaflet:

# Mapa sin agrupar base 1
map1 = leaflet(data = base1) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud, radius = 0.3)

map1
# Mapa marcadores agrupados base 1
map2 = leaflet(data = base1) %>%
  addTiles() %>%
  addMarkers(lng = ~longitud, lat = ~latitud, clusterOptions = markerClusterOptions())

map2

Como se puede observar en el mapa anterior los puntos no pertenecen a una sola zona (en este caso Norte), las casas registradas (o las coordenadas) podrían estar mal etiquetadas / ubicadas.

Paso 2

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.

Análisis Exploratorio Datos / EDA:

# Resumen datos
summary(base1)
##        id             zona               piso              estrato     
##  Min.   :  58.0   Length:722         Length:722         Min.   :3.000  
##  1st Qu.: 766.2   Class :character   Class :character   1st Qu.:3.000  
##  Median :2257.0   Mode  :character   Mode  :character   Median :4.000  
##  Mean   :2574.6                                         Mean   :4.202  
##  3rd Qu.:4225.0                                         3rd Qu.:5.000  
##  Max.   :8319.0                                         Max.   :6.000  
##                                                                        
##     preciom         areaconst       parqueaderos        banios      
##  Min.   :  89.0   Min.   :  30.0   Min.   : 1.000   Min.   : 0.000  
##  1st Qu.: 261.2   1st Qu.: 140.0   1st Qu.: 1.000   1st Qu.: 2.000  
##  Median : 390.0   Median : 240.0   Median : 2.000   Median : 3.000  
##  Mean   : 445.9   Mean   : 264.9   Mean   : 2.182   Mean   : 3.555  
##  3rd Qu.: 550.0   3rd Qu.: 336.8   3rd Qu.: 3.000   3rd Qu.: 4.000  
##  Max.   :1940.0   Max.   :1440.0   Max.   :10.000   Max.   :10.000  
##                                    NA's   :287                      
##   habitaciones        tipo              barrio             longitud     
##  Min.   : 0.000   Length:722         Length:722         Min.   :-76.59  
##  1st Qu.: 3.000   Class :character   Class :character   1st Qu.:-76.53  
##  Median : 4.000   Mode  :character   Mode  :character   Median :-76.52  
##  Mean   : 4.507                                         Mean   :-76.52  
##  3rd Qu.: 5.000                                         3rd Qu.:-76.50  
##  Max.   :10.000                                         Max.   :-76.47  
##                                                                         
##     latitud     
##  Min.   :3.333  
##  1st Qu.:3.452  
##  Median :3.468  
##  Mean   :3.460  
##  3rd Qu.:3.482  
##  Max.   :3.496  
## 
# Selección variables categóricas (character / string)
base1_chr = base1 %>%
  select(where(is.character))

# Identificación valores únicos variables categóricas
base1_chr_unq = lapply(base1_chr, unique)

# Valores únicos por variable
for (col in names(base1_chr_unq)) {
  if (col != 'barrio') # Debido a la cantidad entradas, duplicados y 
    # nombres mal escritos se omite impresión de variable / columna barrio
    cat('Valores únicos', col, ':', paste(base1_chr_unq[[col]], collapse = ', '), '\n')
}
## Valores únicos zona : Zona Norte 
## Valores únicos piso : NA, 02, 03, 01, 04, 07 
## Valores únicos tipo : Casa
# Cantidad de NaN / nulls por variable
colSums(is.na(base1))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0          372            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##          287            0            0            0            0            0 
##      latitud 
##            0

Luego se procede la generación de gráficos descriptivos para las variables de base 1.

# Comprobación tipo vivienda casa base 1
prop.table(table(base1$tipo)) * 100
## 
## Casa 
##  100
# Comprobación zona Norte base 1
prop.table(table(base1$zona)) * 100
## 
## Zona Norte 
##        100
# Torta estratos
ggplot(data = as.data.frame(table(base1$estrato)), aes(x = '', y = Freq, fill = Var1)) +
  geom_bar(stat = 'identity', width = 1) +
  coord_polar('y', start = 0) +
  labs(title = 'Porcentaje de Estratos')

prop.table(table(base1$estrato)) * 100
## 
##         3         4         5         6 
## 32.548476 22.299169 37.534626  7.617729

Plotly

# Boxplot precios base 1
p0 = ggplot(base1, aes(x=zona, y=preciom, fill=zona)) +
  geom_boxplot(fill = 'steelblue') +
  labs(title = 'Precios base1',
       x = 'Base 1 (Casas)',
       y = 'Precio (millones de pesos)')

p0_plotly = ggplotly(p0)
p0_plotly
# Dispersión del precio versus área base1
p1 = ggplot(base1, aes(x = areaconst, y = preciom)) + 
  geom_point(color = 'steelblue1') +
  geom_smooth(method = 'lm', se = FALSE, color = 'slategray') +
  labs(title = 'Relación entre el precio y el área construida base1',
       x = 'Área construida (metros cuadrados)',
       y = 'Precio (millones de pesos)')

p1_plotly = ggplotly(p1)
p1_plotly
# Dispersión del precio por estrato base1
p2 = ggplot(base1, aes(x = estrato, y = preciom, alpha=0.3)) +
  geom_point(color = 'steelblue2') +
  labs(title = 'Relación entre el precio y el estrato base1',
       x = 'Estrato',
       y = 'Precio (millones de pesos)')

p2_plotly = ggplotly(p2)
p2_plotly
# Dispersión del precio por baños base1
p3 = ggplot(base1, aes(x = banios, y = preciom, alpha=0.3)) +
  geom_point(color = 'steelblue3') +
  labs(title = 'Relación entre el precio y número de baños base1',
       x = 'Baños',
       y = 'Precio (millones de pesos)')

p3_plotly = ggplotly(p3)
p3_plotly
# Dispersión del precio por habitaciones base1
p4 = ggplot(base1, aes(x = habitaciones, y = preciom, alpha=0.3)) +
  geom_point(color = 'steelblue4') +
  labs(title = 'Relación entre el precio y número de habitaciones base1',
       x = 'Habitaciones',
       y = 'Precio (millones de pesos)')

p4_plotly = ggplotly(p4)
p4_plotly
# Dispersión del precio por habitaciones base1
p5 = ggplot(base1, aes(x = habitaciones, y = preciom, alpha=0.3)) +
  geom_point(color = 'lightsteelblue3') +
  labs(title = 'Relación entre el precio y número de habitaciones base1',
       x = 'Habitaciones',
       y = 'Precio (millones de pesos)')

p5_plotly = ggplotly(p5)
p5_plotly
# Dispersión del precio por parqueaderos base1
p6 = ggplot(base1, aes(x = parqueaderos, y = preciom, alpha=0.3)) +
  geom_point(color = 'lightsteelblue4') +
  labs(title = 'Relación entre el precio y número de parqueaderos base1',
       x = 'Parqueaderos',
       y = 'Precio (millones de pesos)')

p6_plotly = ggplotly(p6)
p6_plotly
# Selección variables numéricas (numeric / double)
base1_num = base1 %>%
  select(where(is.numeric))

# Diagrama de cajas de todas las variables
boxplot(base1_num[, 2:7],
        main='Diagrama de caja base 1', ylab='Valor',
        xlab='Variables')

# Variables númericas para análisis
base1_num_cor = base1_num %>% 
  select(-all_of(c('id', 'latitud', 'longitud')))

base1_num_na = na.omit(base1_num_cor)

# Matriz dispersión / scatterplot entre variables
ggpairs(base1_num_na)

Con el análisis exploratorio del conjunto de datos claro, procedemos a realizar los diferentes análisis requeridos en la actividad.

Paso 3

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 deber están 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 base1
base1_model = lm(preciom ~ areaconst + estrato + habitaciones + banios + parqueaderos, data = base1_num_na)

summary(base1_model)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + banios + 
##     parqueaderos, data = base1_num_na)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -784.29  -77.56  -16.03   47.67  978.61 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -238.17090   44.40551  -5.364 1.34e-07 ***
## areaconst       0.67673    0.05281  12.814  < 2e-16 ***
## estrato        80.63495    9.82632   8.206 2.70e-15 ***
## habitaciones    7.64511    5.65873   1.351    0.177    
## banios         18.89938    7.48800   2.524    0.012 *  
## parqueaderos   24.00598    5.86889   4.090 5.14e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155.1 on 429 degrees of freedom
## Multiple R-squared:  0.6041, Adjusted R-squared:  0.5995 
## F-statistic: 130.9 on 5 and 429 DF,  p-value: < 2.2e-16

Interpretaciones base1_model

Coeficientes

  • Intercepto: cuando todas las variables independientes son iguales a cero, el precio de la vivienda tiene un valor estimado de -238.17 millones de pesos. Obviamente en este estudio / contexto, es imposible encontrar una vivienda con área / estrato / habitaciones / baños igual a cero.

  • Variables dependientes: El coeficiente de estrato es 80.63 (manteniendo las demás variables constantes, un “aumento” de un estrato implica una valorización según el modelo de 80.63 millones de pesos en el precio de la casas en la base1). El coeficiente de área construida es 0.67673 que es positivo e implican un aumento de 676 mil pesos por cada metro cuadrado adicional. El coeficiente de baños es 18.9, mientras que el de parqueaderos es 24.00598.El coeficiente de habitaciones es el único con p-value > 0.05, por lo que en teoría no sería estadísticamente significativo.

Residuales El error estándar residual es una medida de la variabilidad no explicada por el modelo. En este caso, es de aproximadamente 155.1 millones de pesos.

# R2
base1_model_r2 = summary(base1_model)$r.squared

base1_model_r2
## [1] 0.6040956

Como se puede observar \(R^2\) es 0.6040956, lo que significa que aproximadamente el 60.4% de la variabilidad en el precio de la vivienda se explica por las variables independientes incluidas en el modelo.

Para mejorar el modelo, se podría incluir el estado de la vivienda (convertido al final en una variable numérica), la antigüedad del inmueble, un índice de la seguridad de la zona, accesibilidad, etc.

Stepwise

# Modelo  ingenuo  y= b0 base1
base1_modelb0 = lm(preciom ~ 1, data = base1_num_na)

# Se aplica el proceso forward stepwise regression
base1_fwd = step(base1_modelb0, direction='forward', scope=formula(base1_model), trace=0)

# Visualización de los resultados 
base1_fwd$anova
##             Step Df   Deviance Resid. Df Resid. Dev      AIC
## 1                NA         NA       434   26071994 4787.446
## 2    + areaconst -1 12247618.7       433   13824376 4513.470
## 3      + estrato -1  2436760.4       432   11387615 4431.121
## 4 + parqueaderos -1   679330.9       431   10708285 4406.364
## 5       + banios -1   342349.7       430   10365935 4394.230
# resultado final del modelo
summary(base1_fwd)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + parqueaderos + banios, 
##     data = base1_num_na)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -772.33  -79.57  -15.94   47.33  989.62 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -211.67994   39.88010  -5.308 1.78e-07 ***
## areaconst       0.69563    0.05097  13.647  < 2e-16 ***
## estrato        77.20869    9.50253   8.125 4.79e-15 ***
## parqueaderos   24.08024    5.87426   4.099 4.96e-05 ***
## banios         24.14746    6.40776   3.768 0.000187 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 155.3 on 430 degrees of freedom
## Multiple R-squared:  0.6024, Adjusted R-squared:  0.5987 
## F-statistic: 162.9 on 4 and 430 DF,  p-value: < 2.2e-16

Paso 4

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).

Validación supuestos

# Gráfico de residuos base1_model
plot(base1_model)

# Prueba de normalidad Shapiro-Wilk base1_model
shapiro.test(base1_model$residuals)
## 
##  Shapiro-Wilk normality test
## 
## data:  base1_model$residuals
## W = 0.85246, p-value < 2.2e-16

Como se puede observar, al aplicar la prueba de Shapiro-Wilk para evaluar la normalidad de los residuos de base1_model arroja un p-value muy pequeño menor a 0.05, lo que indica que los residuos no siguen una distribución normal.

Validación simple

# Estructura base1_num_na
str(base1_num_na)
## 'data.frame':    435 obs. of  6 variables:
##  $ estrato     : num  4 3 3 3 3 3 3 3 3 3 ...
##  $ preciom     : num  265 120 125 140 380 180 275 335 180 180 ...
##  $ areaconst   : num  162 130 140 130 400 140 85 166 140 114 ...
##  $ parqueaderos: num  1 1 1 1 1 2 1 2 1 1 ...
##  $ banios      : num  3 2 2 2 3 2 3 3 3 2 ...
##  $ habitaciones: num  4 4 4 3 7 3 9 3 5 3 ...
##  - attr(*, "na.action")= 'omit' Named int [1:287] 1 2 5 8 10 12 14 17 18 21 ...
##   ..- attr(*, "names")= chr [1:287] "1" "2" "5" "8" ...
# Selección índices aleatorios para training set. 
set.seed(1111)
base1_train = sample(x = 1:435, 435*0.6)

base1_model_train = lm(preciom ~ areaconst + estrato + habitaciones + banios + parqueaderos, data = base1_num_na, subset = base1_train)
summary(base1_model_train)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + banios + 
##     parqueaderos, data = base1_num_na, subset = base1_train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -360.32  -65.02   -9.17   53.87  902.60 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -170.43667   47.69265  -3.574 0.000421 ***
## areaconst       0.98797    0.06183  15.979  < 2e-16 ***
## estrato        54.20308   10.76647   5.034 9.07e-07 ***
## habitaciones    2.31331    5.99077   0.386 0.699711    
## banios         16.66494    7.78790   2.140 0.033316 *  
## parqueaderos   21.19242    6.07881   3.486 0.000577 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 126.4 on 255 degrees of freedom
## Multiple R-squared:  0.715,  Adjusted R-squared:  0.7095 
## F-statistic:   128 on 5 and 255 DF,  p-value: < 2.2e-16
base1_pred = predict(object = base1_model_train, newdata = base1_num_na[-base1_train, ])
base1_mse = mean((base1_num_na$preciom[-base1_train] - base1_pred)^2)
base1_mse
## [1] 41088.6

Paso 5

Con el modelo identificado debe predecir el precio de la vivienda con las características de la primera solicitud.

# Datos solicitud 1
base1_solicitud1 = data.frame(
  estrato = c(4, 5),
  areaconst = 200,
  habitaciones = 4,
  banios = 2,
  parqueaderos = 1
  )

# Dataframe base1_solicitud1
kable(base1_solicitud1)
estrato areaconst habitaciones banios parqueaderos
4 200 4 2 1
5 200 4 2 1
# Predicción base1_model con datos solicitud1
predict(base1_model, base1_solicitud1)
##        1        2 
## 312.1010 392.7359

Como se puede observar, el modelo estima / predice un valor de 312 millones de pesos para una casa en estrato 4 y una de 392 millones en una caso estrato 5 (la diferencia de 80 millones es coherente con el coeficiente de estrato es 80.63 encontrado en el paso / punto 3).

Paso 6

Con las predicciones del modelo sugiera potenciales ofertas que responda a la solicitud de la vivienda 1. Tenga encuentra 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.

# Filtrado dyplr ofertas potenciales base1
base1_ofertas = filter(vivienda,
                       preciom <= 350,
                       zona == 'Zona Norte',
                       tipo == 'Casa',
                       estrato %in% c(4,5),
                       areaconst >= 200, 
                       habitaciones >= 4, 
                       banios >= 2, 
                       parqueaderos >= 1
                      )

# Ordenar ofertas base1 por estrato
base1_ofertas = base1_ofertas %>%
  arrange(estrato)
                      
kable(base1_ofertas[, 1:11])
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio
4458 Zona Norte 02 4 315 270.0 2 4 4 Casa el bosque
3352 Zona Norte NA 4 335 300.0 3 4 4 Casa el bosque
937 Zona Norte 02 4 350 280.0 2 3 4 Casa la merced
952 Zona Norte 02 4 330 275.0 2 3 5 Casa la merced
1020 Zona Norte 02 4 230 250.0 2 3 5 Casa la merced
1108 Zona Norte 02 4 330 260.0 1 3 4 Casa la merced
1144 Zona Norte NA 4 320 200.0 2 4 4 Casa la merced
7432 Zona Norte 01 4 260 280.0 2 4 6 Casa los andes
5031 Zona Norte 03 4 350 350.0 1 4 5 Casa salomia
2544 Zona Norte 01 4 340 264.5 2 4 4 Casa vipasa
7470 Zona Norte 02 4 340 264.0 2 5 7 Casa vipasa
1822 Zona Norte NA 4 340 295.0 2 2 4 Casa vipasa
4210 Zona Norte 01 5 350 200.0 3 3 4 Casa el bosque
4267 Zona Norte 01 5 335 202.0 1 4 5 Casa el bosque
4800 Zona Norte 01 5 340 250.0 2 4 4 Casa el bosque
4209 Zona Norte 02 5 350 300.0 3 5 6 Casa el bosque
4422 Zona Norte 02 5 350 240.0 2 3 6 Casa el bosque
4483 Zona Norte 02 5 342 250.0 1 4 6 Casa el bosque
1009 Zona Norte NA 5 250 243.0 1 4 5 Casa el bosque
1270 Zona Norte NA 5 350 203.0 2 2 5 Casa el bosque
3453 Zona Norte NA 5 340 240.0 2 5 6 Casa la campiña
819 Zona Norte 02 5 350 264.0 2 3 4 Casa la flora
1343 Zona Norte 02 5 320 200.0 2 4 4 Casa la flora
3053 Zona Norte 02 5 320 230.0 2 4 4 Casa la flora
766 Zona Norte NA 5 321 249.0 1 5 5 Casa la merced
1163 Zona Norte NA 5 350 216.0 2 2 4 Casa la merced
3043 Zona Norte NA 5 330 275.0 2 3 5 Casa la merced
1849 Zona Norte NA 5 330 246.0 2 4 4 Casa prados del norte
3101 Zona Norte 02 5 340 355.0 2 5 8 Casa san vicente
1151 Zona Norte NA 5 320 210.0 2 3 5 Casa urbanización la merced
1887 Zona Norte 01 5 340 203.0 2 3 4 Casa vipasa
1842 Zona Norte 02 5 350 240.0 2 3 4 Casa vipasa
1914 Zona Norte 02 5 300 205.0 2 5 6 Casa vipasa
1943 Zona Norte NA 5 350 346.0 1 2 4 Casa vipasa

Como se puede observar, se filtran para la oferta de base1 casas en la Zona Norte en estratos 4 o 5 con más de 200 metros cuadrados, con 4 o más habitaciones, 2 o más baños y con 1 o más parqueaderos con un precio máximo de 350 millones de pesos.

Mapa ofertas base1:

# Mapa ofertas base 1
map3 = leaflet(data = base1_ofertas) %>%
  addTiles() %>%
  addMarkers(lng = ~longitud, lat = ~latitud, 
             label = ~paste('Precio = ', base1_ofertas$preciom, 'MCOP'), 
             popup = ~paste('Tipo = ', base1_ofertas$tipo,
                            ' / Barrio = ', base1_ofertas$barrio,
                            ' / Precio = ', base1_ofertas$preciom)
  )

map3

Paso 7

Realice los pasos del 1 al 6. Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.

Base2

7.1
# Filtrar base de datos
base2 = vivienda_df %>%
  filter(tipo == 'Apartamento' & zona == 'Zona Sur')

# Estructura base2
str(base2)
## 'data.frame':    2787 obs. of  13 variables:
##  $ id          : num  3 4 8 9 10 12 13 14 15 16 ...
##  $ zona        : chr  "Zona Sur" "Zona Sur" "Zona Sur" "Zona Sur" ...
##  $ piso        : chr  "03" NA "05" "09" ...
##  $ estrato     : num  5 6 5 4 6 5 5 5 5 4 ...
##  $ preciom     : num  250 1280 310 240 690 230 160 200 270 170 ...
##  $ areaconst   : num  86 346 82.5 80 150 70 63 71 76 60 ...
##  $ parqueaderos: num  NA 4 1 1 2 1 NA 1 NA NA ...
##  $ banios      : num  2 6 2 2 5 2 2 2 2 2 ...
##  $ habitaciones: num  3 5 3 3 4 2 2 3 3 3 ...
##  $ tipo        : chr  "Apartamento" "Apartamento" "Apartamento" "Apartamento" ...
##  $ barrio      : chr  "multicentro" "ciudad jardín" "valle del lili" "valle del lili" ...
##  $ longitud    : num  -76.5 -76.5 -76.5 -76.5 -76.5 ...
##  $ latitud     : num  3.43 3.43 3.43 3.43 3.43 ...
# Ver primeras filas base 2
kable(head(base2))
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
3 Zona Sur 03 5 250 86.0 NA 2 3 Apartamento multicentro -76.46400 3.42900
4 Zona Sur NA 6 1280 346.0 4 6 5 Apartamento ciudad jardín -76.46400 3.43300
8 Zona Sur 05 5 310 82.5 1 2 3 Apartamento valle del lili -76.46438 3.43463
9 Zona Sur 09 4 240 80.0 1 2 3 Apartamento valle del lili -76.46438 3.43463
10 Zona Sur 06 6 690 150.0 2 5 4 Apartamento pance -76.46478 3.42783
12 Zona Sur NA 5 230 70.0 1 2 2 Apartamento valle del lili -76.46500 3.43600
# Mapa sin agrupar base 2
map4 = leaflet(data = base2) %>%
  addTiles() %>%
  addCircleMarkers(lng = ~longitud, lat = ~latitud, radius = 0.3)

map4
7.2

EDA Base 2:

# Resumen datos
summary(base2)
##        id           zona               piso              estrato    
##  Min.   :   3   Length:2787        Length:2787        Min.   :3.00  
##  1st Qu.:2292   Class :character   Class :character   1st Qu.:4.00  
##  Median :4004   Mode  :character   Mode  :character   Median :5.00  
##  Mean   :4131                                         Mean   :4.63  
##  3rd Qu.:5876                                         3rd Qu.:5.00  
##  Max.   :8302                                         Max.   :6.00  
##                                                                     
##     preciom         areaconst       parqueaderos        banios     
##  Min.   :  75.0   Min.   : 40.00   Min.   : 1.000   Min.   :0.000  
##  1st Qu.: 175.0   1st Qu.: 65.00   1st Qu.: 1.000   1st Qu.:2.000  
##  Median : 245.0   Median : 85.00   Median : 1.000   Median :2.000  
##  Mean   : 297.3   Mean   : 97.47   Mean   : 1.415   Mean   :2.488  
##  3rd Qu.: 335.0   3rd Qu.:110.00   3rd Qu.: 2.000   3rd Qu.:3.000  
##  Max.   :1750.0   Max.   :932.00   Max.   :10.000   Max.   :8.000  
##                                    NA's   :406                     
##   habitaciones       tipo              barrio             longitud     
##  Min.   :0.000   Length:2787        Length:2787        Min.   :-76.57  
##  1st Qu.:3.000   Class :character   Class :character   1st Qu.:-76.54  
##  Median :3.000   Mode  :character   Mode  :character   Median :-76.53  
##  Mean   :2.966                                         Mean   :-76.53  
##  3rd Qu.:3.000                                         3rd Qu.:-76.52  
##  Max.   :6.000                                         Max.   :-76.46  
##                                                                        
##     latitud     
##  Min.   :3.334  
##  1st Qu.:3.370  
##  Median :3.383  
##  Mean   :3.390  
##  3rd Qu.:3.406  
##  Max.   :3.497  
## 
# Cantidad de NaN / nulls por variable
colSums(is.na(base2))
##           id         zona         piso      estrato      preciom    areaconst 
##            0            0          622            0            0            0 
## parqueaderos       banios habitaciones         tipo       barrio     longitud 
##          406            0            0            0            0            0 
##      latitud 
##            0

Gráficos Plotly Base 2.

# Boxplot precios base2
p7 = ggplot(base2, aes(x=zona, y=preciom, fill=zona)) +
  geom_boxplot(fill = 'slateblue') +
  labs(title = 'Precios base2',
       x = 'Base 2 (Apartamentos)',
       y = 'Precio (millones de pesos)')

p7_plotly = ggplotly(p7)
p7_plotly
# Dispersión del precio versus área base2
p8 = ggplot(base2, aes(x = areaconst, y = preciom)) + 
  geom_point(color = 'slateblue1') +
  geom_smooth(method = 'lm', se = FALSE, color = 'slategray') +
  labs(title = 'Relación entre el precio y el área construida base2',
       x = 'Área construida (metros cuadrados)',
       y = 'Precio (millones de pesos)')

p8_plotly = ggplotly(p8)
p8_plotly
# Dispersión del precio por estrato base2
p9 = ggplot(base2, aes(x = estrato, y = preciom, alpha=0.3)) +
  geom_point(color = 'slateblue2') +
  labs(title = 'Relación entre el precio y el estrato base2',
       x = 'Estrato',
       y = 'Precio (millones de pesos)')

p9_plotly = ggplotly(p9)
p9_plotly
# Dispersión del precio por baños base2
p10 = ggplot(base2, aes(x = banios, y = preciom, alpha=0.3)) +
  geom_point(color = 'slateblue3') +
  labs(title = 'Relación entre el precio y número de baños base2',
       x = 'Baños',
       y = 'Precio (millones de pesos)')

p10_plotly = ggplotly(p10)
p10_plotly
# Dispersión del precio por habitaciones base2
p11 = ggplot(base2, aes(x = habitaciones, y = preciom, alpha=0.3)) +
  geom_point(color = 'slateblue4') +
  labs(title = 'Relación entre el precio y número de habitaciones base2',
       x = 'Habitaciones',
       y = 'Precio (millones de pesos)')

p11_plotly = ggplotly(p11)
p11_plotly
# Dispersión del precio por habitaciones base2
p12 = ggplot(base2, aes(x = habitaciones, y = preciom, alpha=0.3)) +
  geom_point(color = 'mediumpurple3') +
  labs(title = 'Relación entre el precio y número de habitaciones base2',
       x = 'Habitaciones',
       y = 'Precio (millones de pesos)')

p12_plotly = ggplotly(p11)
p12_plotly
# Dispersión del precio por parqueaderos base2
p13 = ggplot(base2, aes(x = parqueaderos, y = preciom, alpha=0.3)) +
  geom_point(color = 'mediumpurple4') +
  labs(title = 'Relación entre el precio y número de parqueaderos base2',
       x = 'Parqueaderos',
       y = 'Precio (millones de pesos)')

p13_plotly = ggplotly(p13)
p13_plotly
# Selección variables numéricas (numeric / double)
base2_num = base2 %>%
  select(where(is.numeric))

# Variables númericas para análisis
base2_num_cor = base2_num %>% 
  select(-all_of(c('id', 'latitud', 'longitud')))

base2_num_na = na.omit(base2_num_cor)

# Matriz dispersión / scatterplot entre variables
ggpairs(base2_num_na)

7.3
# Modelo base2
base2_model = lm(preciom ~ areaconst + estrato + habitaciones + banios + parqueaderos, data = base2_num_na)

summary(base2_model)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + banios + 
##     parqueaderos, data = base2_num_na)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1092.02   -42.28    -1.33    40.58   926.56 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -261.62501   15.63220 -16.736  < 2e-16 ***
## areaconst       1.28505    0.05403  23.785  < 2e-16 ***
## estrato        60.89709    3.08408  19.746  < 2e-16 ***
## habitaciones  -24.83693    3.89229  -6.381 2.11e-10 ***
## banios         50.69675    3.39637  14.927  < 2e-16 ***
## parqueaderos   72.91468    3.95797  18.422  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 98.02 on 2375 degrees of freedom
## Multiple R-squared:  0.7485, Adjusted R-squared:  0.748 
## F-statistic:  1414 on 5 and 2375 DF,  p-value: < 2.2e-16
# R2
base2_model_r2 = summary(base2_model)$r.squared

base2_model_r2
## [1] 0.7485178

Como se puede observar \(R^2\) es 0.7485178, lo que significa que el 74.85% de la variabilidad en el precio de la vivienda se explica por las variables independientes incluidas en el modelo.

7.4
# Gráfico de residuos base2_model
plot(base2_model)

# Prueba de normalidad Shapiro-Wilk base2_model
shapiro.test(base2_model$residuals)
## 
##  Shapiro-Wilk normality test
## 
## data:  base2_model$residuals
## W = 0.79118, p-value < 2.2e-16

Como se puede observar, los residuos no siguen una distribución normal.

# Estructura base2_num_na
str(base2_num_na)
## 'data.frame':    2381 obs. of  6 variables:
##  $ estrato     : num  6 5 4 6 5 5 5 6 5 6 ...
##  $ preciom     : num  1280 310 240 690 230 200 290 550 300 580 ...
##  $ areaconst   : num  346 82.5 80 150 70 71 91 136 90 145 ...
##  $ parqueaderos: num  4 1 1 2 1 1 1 2 2 2 ...
##  $ banios      : num  6 2 2 5 2 2 3 4 2 5 ...
##  $ habitaciones: num  5 3 3 4 2 3 3 4 3 3 ...
##  - attr(*, "na.action")= 'omit' Named int [1:406] 1 7 9 10 18 25 39 42 47 51 ...
##   ..- attr(*, "names")= chr [1:406] "1" "7" "9" "10" ...
# Selección índices aleatorios para training set. 
set.seed(1111)
base2_train = sample(x = 1:2381, 2381*0.6)

base2_model_train = lm(preciom ~ areaconst + estrato + habitaciones + banios + parqueaderos, data = base2_num_na, subset = base2_train)
summary(base2_model_train)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + banios + 
##     parqueaderos, data = base2_num_na, subset = base2_train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -943.10  -47.22   -2.87   42.81  951.40 
## 
## Coefficients:
##                Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  -283.63191   22.17548 -12.790  < 2e-16 ***
## areaconst       1.10080    0.06737  16.339  < 2e-16 ***
## estrato        66.77432    4.20810  15.868  < 2e-16 ***
## habitaciones  -23.03397    5.38495  -4.277 2.02e-05 ***
## banios         54.71449    4.51344  12.123  < 2e-16 ***
## parqueaderos   70.86712    5.02421  14.105  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 105.5 on 1422 degrees of freedom
## Multiple R-squared:  0.7215, Adjusted R-squared:  0.7205 
## F-statistic: 736.8 on 5 and 1422 DF,  p-value: < 2.2e-16
base2_pred = predict(object = base2_model_train, newdata = base2_num_na[-base2_train, ])
base2_mse = mean((base2_num_na$preciom[-base2_train] - base2_pred)^2)
base2_mse
## [1] 7491.906
7.5
# Datos solicitud 2
base2_solicitud2 = data.frame(
  estrato = c(5, 6),
  areaconst = 300,
  habitaciones = 5,
  banios = 3,
  parqueaderos = 3
)

# Dataframe base2_solicitud2
kable(base2_solicitud2)
estrato areaconst habitaciones banios parqueaderos
5 300 5 3 3
6 300 5 3 3
# Predicción base2_model con datos solicitud2
predict(base2_model, base2_solicitud2)
##        1        2 
## 675.0247 735.9218
7.6
# Filtrado dyplr ofertas potenciales base2
base2_ofertas = filter(vivienda,
                       preciom <= 850,
                       zona == 'Zona Sur',
                       tipo == 'Apartamento',
                       estrato %in% c(5,6),
                       areaconst >= 300, 
                       habitaciones >= 5, 
                       banios >= 3, 
                       parqueaderos >= 3
                      )

# Ordenar ofertas base2 por estrato
base2_ofertas = base2_ofertas %>%
  arrange(estrato)
                      
kable(base2_ofertas[, 1:11])
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio
7182 Zona Sur NA 5 730 573 3 8 5 Apartamento guadalupe
7512 Zona Sur NA 5 670 300 3 5 6 Apartamento seminario
# Mapa ofertas base 2
map5 = leaflet(data = base2_ofertas) %>%
  addTiles() %>%
  addMarkers(lng = ~longitud, lat = ~latitud, 
             label = ~paste('Precio = ', base2_ofertas$preciom, 'MCOP'), 
             popup = ~paste('Tipo = ', base2_ofertas$tipo,
                            ' / Barrio = ', base2_ofertas$barrio,
                            ' / Precio = ', base2_ofertas$preciom)
  )

map5

Como se puede observar, se encuentran 2 opciones al filtrar para la oferta de base2 apartamentos en la Zona Sur en estratos 5 o 6 con más de 300 metros cuadrados, con 5 o más habitaciones, 3 o más baños / parqueaderos con un precio máximo de 850 millones de pesos.

Conclusiones y Recomendaciones

Con la base de datos de viviendas con la que cuenta la empresa inmobiliaria se realizó un análisis de variables del conjunto de datos y se encuentra lo siguiente:

  • En la base de datos original se encuentran datos para 5 zonas, de los cuales el 56.8% de los datos / viviendas pertenecen a la Zona Sur (seguida por un 23.1% en la Zona Norte). Todos los datos suministrados pertenecen a viviendas de estrato 3 al 6 (siendo el 5 el mayoritario con un 33%).El rango de precios de las viviendas va desde los 58 millones hasta un máximo de 1999 millones de pesos. El área construida mínima encontrada en los inmuebles analizados es de 30 m2 y la máxima de 1745 m2. Aproximadamente el 61% de los datos son tipo apartamento y el restante 39% son casas. El 75% de los precios de las viviendas analizadas es menor a 540 millones y menor a 229 m2.
  • Como era de esperarse, en los modelos aplicados, el área construida, el estrato y el número de habitaciones / cuartos, baños y parqueaderos en un efecto positivo en el precio predecidlo de la vivienda, y como es lógico, las viviendas más grandes y en estratos más altos tienden a tener mayores precios.
  • Respecto al material de estudio, como en la anterior unidad se recomienda revisar ortografía, funcionalidad y la ampliación de los de los ejemplos.

Referencias

Holtz, Yan. n.d. “The R Graph Gallery – Help and Inspiration for R´ Charts.” The R Graph Gallery. Accessed September 3, 2023. https://r-graph-gallery.com/.
Morales, Javier, and María Asunción Martínez. 2021. Modelos Estadísticos. Bookdown.org. https://bookdown.org/j_morales/librostat/.
Plotly Technologies Inc. n.d. Plotly R Open Source Graphing Library. Accessed September 14, 2023. https://plotly.com/r/.
Posit Software, PBC. n.d. RPubs. Accessed September 1, 2023. https://rpubs.com/.
Soage, José Carlos. n.d. R-Charts.com. Accessed September 15, 2023. https://r-charts.com/.
Stack Exchange, Inc. n.d. Stack Overflow. Accessed September 2, 2023. https://stackoverflow.com/.
Xie, Yihui, J. J. Allaire, and Garrett Grolemund. 2023. R Markdown: The Definitive Guide. https://bookdown.org/yihui/rmarkdown/.