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).
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 |
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>
# 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.
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
# 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.
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
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.
# 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
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).
# 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.
# 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
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).
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
Realice los pasos del 1 al 6. Para la segunda solicitud que tiene un crédito pre-aprobado por valor de $850 millones.
# 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
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)
# 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.
# 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
# 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
# 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.
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: