ASESORÍA INMOBILIARIA C&A

La Firma C&A ha sido contratada para brindar una consultoría y asesorar a una empresa para la adquisición de dos inmuebles en la ciudad de Cali, el cliente presentó unos requerimientos específicos sobre las características de las propiedades que proyecta adquirir. La Firma debe analizar la oferta inmobiliaria disponible e identificar los inmuebles que se ajusten a los requerimientos del cliente y recomendar cuales son las mejores opciones ajustadas a las peticiones del cliente.

Introducción

El informe presenta un análisis detallado del mercado inmobiliario en la Ciudad de Cali, específicamente en las unidades de vivienda tipo casas y apartamentos ubicados en las Zonas Norte y Sur de la Ciudad. Se realizó un análisis exhaustivos de los precios y de las características de las viviendas en términos de estratos socio económico, área construidas, número de baños, número de habitaciones, número de parqueaderos, entre otras con el objetivo de estimar los precios e identificar las mejores ofertas de acuerdo con los requerimientos del cliente.

Análisis del Mercado Inmobiliario

Para llevar a cabo este informe, se analizaron 521 casas ubicadas en la zona norte y 2.777 apartamentos ubicados en la zona sur. Identificamos que el precio promedio de las casas es de 433 Millones con un rango que oscila entre 58 Millones hasta los 2.000 Millones, y un área promedio 174 metros cuadrados con áreas desde los 30 hasta los 1745 metros cuadrados, en esta zona de la ciudad predomina el estrato 4 y 5.En lo que respecta a los apartamentos, identificamos que el precio promedio es de 695 Millones, el área promedio es de 123 metros cuadrados, que oscilan entre los 30 hasta los 229 metros cuadrados, en esta zona de la ciudad predomina el estrato 4.

En cuanto a la evaluación de precios, en general los precios de los apartamentos son más altos que el de las casas; sin embargo, los precios de las casas presentan mayor variabilidad mientras que los precios de los apartamentos son mas estables, se evidenció también, que en estas zonas hay inmuebles con precios significativamente altos en comparación con la media del sector.

Recomendaciones de compra

De acuerdo con la restricción presupuestas sujeta al crédito pre aprobado por el Entidad Bancaria y, teniendo presente los requerimientos iniciales del cliente, se definió presentar las mejores ofertas basados en los criterios de la relación precio metros cuadrados, así mismo, los inmuebles disponibles que más se ajusta y se propone para la adquisición de la casa es una unidad de vivienda ubicada en el barrio San Vicente, con un área de 355 metros cuadrados, dos parqueaderos, cinco baños y ocho habitaciones en el estrato 5, además su precio es de 340 Millones. Para la adquisición del apartamento la mejor oferta disponible, es un inmueble ubicado en el barrio El Ingenio, estrato cinco, área de 600 metros cuadrados, dos parqueaderos, cuatro baños, cinco habitaciones y precio de 650 millones.

Conclusiones

El análisis del mercado inmobiliario permite concluir que la Zona Norte es ideal para quienes buscan propiedades con mayor valor agregado, mientras que la Zona Sur ofrece oportunidades a precios más asequibles con buena proyección de valorización. Para el presupuesto del cliente, se identificaron las mejores opciones de casas y apartamentos en cada sector, optimizando la inversión en función de las necesidades y expectativas.

ANEXO 1 - Metodología

La metodología adoptada para el estudio inmobiliario de realizó desarrollando los siguientes pasos consecutivos y sistemáticos.

  1. Recolección de datos.
  2. Análisis exploratorio de la totalidad de la base de datos.
  3. Limpieza de datos.
  4. Análisis exploratorio discriminado por casas y apartamentos.
  5. Modelado aplicando Regresión Lineal Múltiple.
  6. Evaluación de los supuestos del Modelo.
  7. Predicción de precios viviendas tipo casa y apartamento.
  8. Selección de inmuebles óptimos
  9. Visualización y presentación de resultados.

ANEXO 2 - Implementación

Implementación de la metodología, resultados de la moderación , validación y comparación de los modelos

Recolección de Datos.

library(paqueteMODELOS)
library(dplyr)
library(DT)
library(knitr)
library(pander)
library(tidyr)

data("vivienda")

Numero de filas: 8322
Numero de columnas: 13

id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20 de julio -76.51168 3.43382
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20 de julio -76.51237 3.43369
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20 de julio -76.51537 3.43566
5992 Zona Sur 02 4 400 280 3 5 3 Casa 3 de julio -76.54000 3.43500
1212 Zona Norte 01 5 260 90 1 2 3 Apartamento acopi -76.51350 3.45891
1724 Zona Norte 01 5 240 87 1 3 3 Apartamento acopi -76.51700 3.36971
library(DT)


diccionario <- data.frame(
  Variable = c("id", "barrio", "longitud", "latitud", "zona", "piso",
               "estrato", "preciom", "areaconst", "parqueaderos", "banios",
               "habitaciones", "tipo"),
  
  Descripcion = c("Identificador único del inmueble",
                  "Nombre del barrio donde se encuentra la vivienda",
                  "Coordenada de longitud geográfica del inmueble",
                  "Coordenada de latitud geográfica del inmueble",
                  "Ubicación geográfica de la vivienda (ejemplo: Norte, Sur, Centro)",
                  "Número de piso en el  que se ubica la vivienda",
                  "Estrato socioeconómico asignado a la vivienda",
                  "Precio del inmuble en millones de pesos ",
                  "Área construida de la vivienda en metros cuadrados",
                  "Cantidad de parqueaderos disponibles",
                  "Número de baños en la vivienda",
                  "Número de habitaciones en la vivienda",
                  "Tipo de inmueble (ejemplo: Casa, Apartamento)"),
  stringsAsFactors = FALSE
)


datatable(diccionario, options = list(scrollX = TRUE))
estructura_vivienda <- tibble(
  Variable = names(vivienda),
  Tipo = sapply(vivienda, \(x) class(x)[1])  
) %>% arrange(Tipo)  

datatable(estructura_vivienda, options = list(
  dom = 't',          
  pageLength = nrow(estructura_vivienda),  
  scrollX = TRUE      
))

En una primera aproximación para conocer el contenido de la base de datos, observamos que es un base pequeña, que contiene 8.322 registros que corresponden al número total de inmuebles y 13 columnas que describen los atributos de cada registro, así mismo, se visualiza cuatro variables tipo texto y nueve tipo numérico, cabe resaltar que, sin bien estrato está definida como tipo número, esta variable es categórica; la variables tipo numérico contienen información referente al identificador de inmueble, el estrato socieconomico, precio del inmueble,área construida, número de parqueaderos, número de baños,número de habitaciones, longitud y latitud del inmueble; por otro lado, las variables tipo texto contienen información relacionada con el nombre de la zona del inmueble, tipo de inmueble (casa o apartamento), nombre del barrio, número de piso en donde se ubica el que se encuentra el inmueble.

Análisis exploratorio toda la base

Table continues below
id estrato preciom areaconst
Min. : 1 Min. :3.000 Min. : 58.0 Min. : 30.0
1st Qu.:2080 1st Qu.:4.000 1st Qu.: 220.0 1st Qu.: 80.0
Median :4160 Median :5.000 Median : 330.0 Median : 123.0
Mean :4160 Mean :4.634 Mean : 433.9 Mean : 174.9
3rd Qu.:6240 3rd Qu.:5.000 3rd Qu.: 540.0 3rd Qu.: 229.0
Max. :8319 Max. :6.000 Max. :1999.0 Max. :1745.0
NA’s :3 NA’s :3 NA’s :2 NA’s :3
parqueaderos banios habitaciones longitud latitud
Min. : 1.000 Min. : 0.000 Min. : 0.000 Min. :-76.59 Min. :3.333
1st Qu.: 1.000 1st Qu.: 2.000 1st Qu.: 3.000 1st Qu.:-76.54 1st Qu.:3.381
Median : 2.000 Median : 3.000 Median : 3.000 Median :-76.53 Median :3.416
Mean : 1.835 Mean : 3.111 Mean : 3.605 Mean :-76.53 Mean :3.418
3rd Qu.: 2.000 3rd Qu.: 4.000 3rd Qu.: 4.000 3rd Qu.:-76.52 3rd Qu.:3.452
Max. :10.000 Max. :10.000 Max. :10.000 Max. :-76.46 Max. :3.498
NA’s :1605 NA’s :3 NA’s :3 NA’s :3 NA’s :3
pander(
  vivienda %>%
    select(where(~ is.character(.) | is.factor(.))) %>%
    summarise(across(everything(), ~ paste(names(table(.))[table(.) == max(table(.), na.rm = TRUE)], collapse = ", "))),
  caption = "Moda de las Variables Categóricas"
)
Moda de las Variables Categóricas
zona piso tipo barrio
Zona Sur 02 Apartamento valle del lili
library(plotly)
library(reshape2)
library(htmltools)

vivienda_melt <- melt(vivienda[, sapply(vivienda, is.numeric)])
grupos <- list(c("id", "preciom", "areaconst"), c("estrato", "parqueaderos", "banios", "habitaciones"), c("longitud", "latitud"))

browsable(tagList(lapply(grupos, \(g) subplot(lapply(g, \(v) 
  plot_ly(vivienda_melt[vivienda_melt$variable == v, ], y = ~value, type = "box", name = v)), 
  nrows = 1, shareX = TRUE, titleX = TRUE))))
vars_categoricas <- names(vivienda)[sapply(vivienda, is.character)]
print(vars_categoricas)
## [1] "zona"   "piso"   "tipo"   "barrio"
vivienda$estrato <- as.factor(vivienda$estrato)
library(plotly)
library(dplyr)
library(purrr)

vars_categoricas <- names(vivienda)[sapply(vivienda, is.character) | sapply(vivienda, is.factor)]

plots <- map(vars_categoricas, function(var) {
  data_cat <- vivienda %>%
    group_by(!!as.name(var)) %>%
    summarise(count = n(), .groups = "drop")
  
  plot_ly(data_cat, x = ~get(var), y = ~count, type = "bar", name = var) %>%
    layout(title = paste("Distribución de", var), 
           xaxis = list(title = var), 
           yaxis = list(title = "Cantidad"))
})
plot_ly(vivienda, x = ~zona, y = ~preciom, type = "box", color = ~zona) %>%
  layout(title = "Distribución del Precio por Zona")
plot_ly(vivienda, x = ~tipo, y = ~preciom, type = "box", color = ~tipo) %>%
  layout(title = "Distribución del Precio por Tipo de Vivienda")
vivienda$estrato <- as.factor(vivienda$estrato)

plot_ly(vivienda, x = ~estrato, y = ~preciom, type = "box", color = ~estrato) %>%
  layout(title = "Distribución del Precio de Inmuebles por Estrato",
         xaxis = list(title = "Estrato"),
         yaxis = list(title = "Precio (millones de pesos)"))
plot_ly(vivienda, x = ~zona, y = ~preciom, type = "bar", color = ~tipo) %>%
  layout(title = "Distribución de Tipos de Vivienda por Zona", barmode = "stack")

Una vez finalizado el análisis exploratorio, observamos que la mayoría de los inmuebles se encuentran ubicados en el estrato 4 y 5, los precios de los inmuebles oscilan entre 58 M hasta 1999 M, con un valor promedio de 434 M, encontramos inmuebles con áreas desde 30 hasta 1745 metros cuadrados, así mismo, el promedio de parqueaderos por inmueble es de 2, baños 3 y habitaciones 3; Por otro lado, se evidencia que predominan los apartamentos sobre las casas y la mayoría de las propiedades se ubican en el barrio Valle de Lili en la zona Sur de Cali. Continuando con el análisis identificamos valores atípicos en la variable precios, área de construcción, parqueaderos y habitaciones, también, se presentan datos incoherentes como inmuebles sin baño y sin habitaciones; de igual forma, valores nulos en la variable estrato, precio, área de construcción, parqueadero, baños y habitaciones.

Observamos que los precios de las Casas son más altos en comparación con los apartamentos; en la Zona Oeste y Norte se encuentran los inmuebles más costosos, en la Zona Centro y Oriente los precios más bajos y en la Zona Sur los precios son intermedios respecto de las demás zonas, respectivamente observamos que, entre más alto el estrato más costosos los inmuebles.

Finalizando, se concluye que para los datos atípicos, nulos e incoherentes, es necesario implementar una estrategia para su adecuado tratamiento porque pueden reducir la precisión del modelo, por otro lado, preliminarmente se puede direccionar los esfuerzos localizando inmuebles en la Zona Oeste y Sur de la ciudad para cumplir con los requerimientos del cliente

Limpieza de datos

vivienda_limpia <- vivienda

vivienda_limpia <- vivienda_limpia %>% select(-id)

if (!"id" %in% names(vivienda_limpia)) {
  print("La columna ID hn sido eliminada correctamente.")
} else {
  print("Error: La columna ID sigue presente en los datos.")
}
## [1] "La columna ID hn sido eliminada correctamente."
cantidad_filas_duplicadas <- sum(duplicated(vivienda_limpia))

print(paste("Cantidad de filas duplicadas:", cantidad_filas_duplicadas))
## [1] "Cantidad de filas duplicadas: 58"
vivienda_clean <- vivienda_limpia %>% distinct()
print("Filas duplicadas eliminadas correctamente.")
## [1] "Filas duplicadas eliminadas correctamente."
library(dplyr)
library(knitr)

find_issues <- function(df) {
  total_filas <- nrow(df)
  
  if (total_filas == 0) return(
    tibble(Variable = character(), 
           Valores_Nulos = integer(), Porcentaje_Nulos = numeric(),
           Caracteres_Extraños = integer(), Porcentaje_Caracteres_Extraños = numeric(),
           Campos_Vacios = integer(), Porcentaje_Campos_Vacios = numeric())
  )
  
  valores_nulos <- sapply(df, function(x) sum(is.na(x)))
  caracteres_extranos <- sapply(df, function(x) sum(grepl("[^ -~]", as.character(na.omit(x)), perl = TRUE)))
  campos_vacios <- sapply(df, function(x) sum(nchar(trimws(as.character(na.omit(x)))) == 0))
  
  tibble(
    Variable = names(df),
    Valores_Nulos = valores_nulos,
    Porcentaje_Nulos = round((valores_nulos / total_filas) * 100, 2),
    Caracteres_Extraños = caracteres_extranos,
    Porcentaje_Caracteres_Extraños = round((caracteres_extranos / total_filas) * 100, 2),
    Campos_Vacios = campos_vacios,
    Porcentaje_Campos_Vacios = round((campos_vacios / total_filas) * 100, 2)
  ) %>%
    arrange(desc(Valores_Nulos))
}

issues_df <- find_issues(vivienda_limpia)

kable(issues_df, format = "markdown", align = "c", caption = "Resumen de Problemas en los Datos")
Resumen de Problemas en los Datos
Variable Valores_Nulos Porcentaje_Nulos Caracteres_Extraños Porcentaje_Caracteres_Extraños Campos_Vacios Porcentaje_Campos_Vacios
piso 2638 31.70 0 0.00 0 0
parqueaderos 1605 19.29 0 0.00 0 0
zona 3 0.04 0 0.00 0 0
estrato 3 0.04 0 0.00 0 0
areaconst 3 0.04 0 0.00 0 0
banios 3 0.04 0 0.00 0 0
habitaciones 3 0.04 0 0.00 0 0
tipo 3 0.04 0 0.00 0 0
barrio 3 0.04 1267 15.22 0 0
longitud 3 0.04 0 0.00 0 0
latitud 3 0.04 0 0.00 0 0
preciom 2 0.02 0 0.00 0 0
sin_banio <- vivienda_limpia %>%
  filter(banios == 0) %>%
  group_by(tipo) %>%
  summarise(Sin_Banio = n(), .groups = "drop")

sin_habitaciones <- vivienda_limpia %>%
  filter(habitaciones == 0) %>%
  group_by(tipo) %>%
  summarise(Sin_Habitaciones = n(), .groups = "drop")

resultado <- full_join(sin_banio, sin_habitaciones, by = "tipo") %>%
  replace_na(list(Sin_Banio = 0, Sin_Habitaciones = 0))  

resultado <- resultado %>%
  bind_rows(
    tibble(
      tipo = "Total",
      Sin_Banio = sum(resultado$Sin_Banio, na.rm = TRUE),
      Sin_Habitaciones = sum(resultado$Sin_Habitaciones, na.rm = TRUE)
    )
  )

print("Cantidad de apartamentos y casas sin baño y sin habitaciones (incluyendo total):")
## [1] "Cantidad de apartamentos y casas sin baño y sin habitaciones (incluyendo total):"
print(resultado)
## # A tibble: 3 × 3
##   tipo        Sin_Banio Sin_Habitaciones
##   <chr>           <int>            <int>
## 1 Apartamento        14               21
## 2 Casa               31               45
## 3 Total              45               66
vivienda_limpia <- vivienda_limpia %>%
  filter(banios > 0 & habitaciones > 0)

print(paste("Cantidad de registros después de la limpieza:", nrow(vivienda_limpia)))
## [1] "Cantidad de registros después de la limpieza: 8243"
print("Cantidad de apartamentos y casas sin baño y sin habitaciones (incluyendo total):")
## [1] "Cantidad de apartamentos y casas sin baño y sin habitaciones (incluyendo total):"
print(resultado)
## # A tibble: 3 × 3
##   tipo        Sin_Banio Sin_Habitaciones
##   <chr>           <int>            <int>
## 1 Apartamento        14               21
## 2 Casa               31               45
## 3 Total              45               66
library(dplyr)

contar_na <- function(df, vars) {
  bind_rows(lapply(vars, function(v) {
    df %>% group_by(tipo) %>% summarise(Cantidad_NA = sum(is.na(.data[[v]])), .groups = "drop") %>%
      mutate(Variable = v)
  })) %>%
  mutate(tipo = replace_na(tipo, "Desconocido")) %>%
  as.data.frame() %>%
  print(row.names = FALSE)
}

contar_na(vivienda_limpia, c("piso", "parqueaderos"))
##         tipo Cantidad_NA     Variable
##  Apartamento        1374         piso
##         Casa        1219         piso
##  Apartamento         857 parqueaderos
##         Casa         698 parqueaderos
library(dplyr)

vivienda_limpia <- vivienda_limpia %>%
  mutate(
    piso = as.numeric(piso),
    piso = if_else(is.na(piso), if_else(tipo == "Casa", 1, median(piso, na.rm = TRUE)), piso),
    parqueaderos = replace_na(parqueaderos, 0)
  ) %>%
  drop_na(zona, estrato, areaconst, banios, habitaciones, tipo, barrio, preciom)

colSums(is.na(vivienda_limpia))
##         zona         piso      estrato      preciom    areaconst parqueaderos 
##            0            0            0            0            0            0 
##       banios habitaciones         tipo       barrio     longitud      latitud 
##            0            0            0            0            0            0
vivienda_limpia <- vivienda_limpia %>%
  mutate(across(c(estrato, piso), as.factor))

variables_numericas <- names(select(vivienda_limpia, where(is.numeric)))
variables_categoricas <- names(select(vivienda_limpia, where(is.factor) | where(is.character)))

list(Numéricas = variables_numericas, Categóricas = variables_categoricas)
## $Numéricas
## [1] "preciom"      "areaconst"    "parqueaderos" "banios"       "habitaciones"
## [6] "longitud"     "latitud"     
## 
## $Categóricas
## [1] "piso"    "estrato" "zona"    "tipo"    "barrio"

En la etapa de de limpieza, se realizaron ajustes para mejorar la calidad de los datos, inicialmente se detectaron filas duplicadas que fueron eliminadas, la variable id se excluye de la base de datos porque no aportan información relevante para el análisis; así mismo, generamos un resumen para detectar campos nulos, caracteres extraños y campos vacíos, identificamos valores nulos en todas las variables, optamos por eliminar los registros con valores nulos en las variables: zona, estrato, área construida, baños,habitaciones,tipo,barrio y precio, debido a que representan menos del 1% de los registros de la base de datos y, por lo tanto no presentan un riesgo en termino de perdida de datos, identificamos ademas, que la variable barrio contiene 1.267 caracteres extraños, sin embargo, es natural por tratarse de una variable tipo texto que contiene los nombres de los barrios, por lo tanto, no requirió tratamiento alguno, por último no se detectaron campos vacíos.

En cuanto a las variables piso y parqueadero, detectamos valores nulos con una participación del 38% y 19% en la base de datos respectivamente, dada importante participación en la base de datos se define una estrategia para su tratamiento para no eliminarlas. Analizando la variable piso observamos que de los 2.638 nulos, 1.254 corresponden a casa y los imputamos con el valor 1 porque tiene más sentido que las casas estén ubicadas en la primera planta, referente a los tipos apartamento, optamos por la estrategia de imputarlos con la moda de la zona a la que pertenecen.

Anteriormente, en la etapa de exploración de los datos, identificamos datos incoherentes como inmuebles tipo casa y apartamentos sin habitaciones, validando detectamos 45 inmuebles sin baño y 66 sin habitaciones, por tratarse de menos del 2% de los datos totales optamos por eliminarlos.

Por último definimos las variables categóricas y numéricas.

Analisis exploratorio tipo casa

# Filtrar las casas ubicadas en la zona norte
casas_norte <- vivienda_limpia %>%
  filter(tipo == "Casa" & zona == "Zona Norte")

# Mostrar la tabla con los resultados
library(DT)
datatable(casas_norte, options = list(scrollX = TRUE))
# Mostrar el total de casas
print(paste("Total de casas en la Zona Norte:", nrow(casas_norte)))
## [1] "Total de casas en la Zona Norte: 700"
# Filtrar las casas ubicadas en la zona norte y mostrar los tres primeros registros
casas_norte <- vivienda_limpia %>%
  filter(tipo == "Casa" & zona == "Zona Norte") %>%
  head(3)

# Mostrar la tabla con los resultados
library(DT)
datatable(casas_norte, options = list(scrollX = TRUE))

Enfocaremos nuestro análisis en identificar las casas de la Zona Norte de la Ciudad, de acuerdo con el requerimiento de nuestro Cliente, en esta paso filtramos la base de datos por las viviendas tipo Casa ubicadas en el Norte de la Ciudad y obtenemos un total de 700 casas en esta zona.

library(leaflet)

# Definir la paleta de colores
paleta_colores <- colorFactor(
  palette = c("blue", "green", "purple", "red", "orange"), 
  domain = vivienda_limpia$zona
)

# Generar el mapa
leaflet(vivienda_limpia) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    color = ~paleta_colores(zona), fillOpacity = 0.7, radius =0.5,
    label = ~paste("Barrio:", barrio),
    popup = ~paste("Zona:", zona, "<br>",
                   "Precio:", preciom, "M", "<br>",
                   "Tipo:", tipo,"<br>",
                   "Área:", areaconst, "m²")
  ) %>%
  addLegend(
    "topright", pal = paleta_colores, values = vivienda_limpia$zona,
    title = "Zonas de la ciudad"
  )

Para obtener un visual gráfica generamos un mapa con la ubicación geográfica de las casas en Cali, identificando con un color según la zona asignada con las coordenadas de la base de datos, se evidencia asignaciones incorrectas,como por ejemplo viviendas con etiquetas de zonas de zona norte; sin embargo sus coordenadas son de la zona centro, sur oriente, esta misma casuística se presenta para el resto de las zonas, posiblemente este error se deriva de fallas en los procesos de captura de información o deficiencias en los programas para realizar las asignaciones de los registros, para evitar un error en la propuesta al Cliente se debe realizar la corrección del etiquetado de las Zonas, evitando ofrecer inmuebles que según su etiqueta figure como una casa en la zona de Cali, pero sus coordenadas apunten a otro sector de la Ciudad.

Abordamos esta corrección ubicando fuentes oficiales en donde podamos observar la limitación por zonas en la Ciudad de Cali, esta observación la obtenemos del repositorio de información de la Alcaldía de Cali y la utilizamos para definir las fronteras entre zonas.

library(dplyr)
library(geosphere)
library(leaflet)

# Definir las coordenadas promedio aproximadas de cada zona con los ajustes requeridos
zonas_coords <- data.frame(
  zona = c("Zona Norte", "Zona Oriente", "Zona Oeste", "Zona Sur", "Zona Centro"),
  latitud = c(3.4700, 3.4500, 3.4400, 3.4000, 3.4372), 
  longitud = c(-76.5000, -76.4800, -76.5600, -76.5200, -76.5225)
)

# Función para calcular la zona más cercana
asignar_zona <- function(lat, lon, zonas_coords) {
  distancias <- distHaversine(c(lon, lat), zonas_coords[, c("longitud", "latitud")])
  zona_cercana <- zonas_coords$zona[which.min(distancias)]
  return(zona_cercana)
}

# Asignar la zona a cada registro en vivienda_limpia
vivienda_limpia <- vivienda_limpia %>%
  rowwise() %>%
  mutate(zona_corr = asignar_zona(latitud, longitud, zonas_coords))

# Crear una paleta de colores para las zonas
paleta_colores <- colorFactor(
  palette = c("blue", "green", "purple", "red", "orange"),
  domain = vivienda_limpia$zona_corr
)

# Generar el mapa con la nueva asignación de zonas
leaflet(vivienda_limpia) %>%
  addTiles() %>%
  addCircleMarkers(
    ~longitud, ~latitud, 
    color = ~paleta_colores(zona_corr), fillOpacity = 0.7, radius =0.5,
    label = ~paste("Barrio:", barrio),
    popup = ~paste("Zona:", zona_corr, "<br>",
                   "Precio:", preciom, "M", "<br>",
                   "Tipo:", tipo,"<br>",
                   "Área:", areaconst, "m²")
  ) %>%
  addLegend(
    "topright", pal = paleta_colores, values = vivienda_limpia$zona_corr,
    title = "Zonas de la ciudad"
  )
# Filtrar solo las casas ubicadas en la Zona Norte
casas_norte <- vivienda_limpia %>%
  filter(tipo == "Casa" & zona_corr == "Zona Norte")

# Contar el número total de casas en la Zona Norte
total_casas_norte <- nrow(casas_norte)

# Crear un data frame con el resultado
tabla_casas_norte <- data.frame(
  Zona = "Zona Norte",
  Total_Casas = total_casas_norte
)

# Mostrar la tabla
print(tabla_casas_norte)
##         Zona Total_Casas
## 1 Zona Norte         521
# Opcional: Mostrar la tabla de forma interactiva
library(DT)
datatable(tabla_casas_norte, options = list(scrollX = TRUE))

Observamos que con la resignación de las Zonas, la oferta de casas en la Zona Norte, disminuye en 179 unidades, pasando de 700 a 521.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Casas)
vivienda_filtrada <- vivienda_limpia %>%
  filter(tipo == "Casa") %>%  # Filtrar solo casas
  select(preciom, zona) %>%
  na.omit()  # Eliminar valores faltantes

# Definir los colores personalizados según la imagen
colores_zonas <- c("Zona Centro" = "#76c6b8",  # Verde menta
                   "Zona Norte" = "#e4805e",   # Naranja
                   "Zona Oeste" = "#597dbd",  # Azul
                   "Zona Oriente" = "#c779b1", # Rosa
                   "Zona Sur" = "#7db66f")    # Verde claro

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_zona <- plot_ly(vivienda_filtrada, 
                        x = ~factor(zona, levels = c("Zona Centro", "Zona Norte", "Zona Oeste", "Zona Oriente", "Zona Sur")), 
                        y = ~preciom, 
                        type = "box", 
                        color = ~factor(zona), 
                        colors = colores_zonas) %>%
  layout(title = "Distribución del Precio por Zona (Solo Casas)",
         xaxis = list(title = "Zona", categoryorder = "array"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_zona

Analizando el comportamiento de los precios según la ubicación de las casas en las distintas zonas, observamos que las casas más costosas se ubican en la zona oeste, seguido por la zona sur y, los precios más bajos se ubican en la zona norte, centro y oriente respectivamente, preliminarmente podríamos inferir que en la zona oeste se ubican las casas con el metro cuadrado más costoso y posiblemente porque son zonas exclusivas de la ciudad de estratos altos.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Norte y solo Casas)
vivienda_norte <- vivienda_limpia %>%
  filter(zona == "Zona Norte", tipo == "Casa") %>%  # Filtrar solo casas en la Zona Norte
  select(preciom, areaconst) %>%  # Seleccionar Precio y Área Construida
  na.omit()  # Eliminar valores faltantes

# Calcular la regresión lineal
modelo <- lm(preciom ~ areaconst, data = vivienda_norte)

# Crear datos de predicción para la línea de tendencia
predicciones <- data.frame(
  areaconst = seq(min(vivienda_norte$areaconst), max(vivienda_norte$areaconst), length.out = 100)
)
predicciones$preciom <- predict(modelo, newdata = predicciones)

# Crear el gráfico de dispersión con la línea de tendencia
grafico_area_norte <- plot_ly() %>%
  # Agregar puntos de dispersión
  add_trace(data = vivienda_norte, 
            x = ~areaconst, 
            y = ~preciom, 
            type = "scatter", 
            mode = "markers",
            marker = list(size = 5, opacity = 0.5, color = 'blue'),
            name = "Casas en Zona Norte") %>%
  # Agregar la línea de tendencia
  add_trace(data = predicciones, 
            x = ~areaconst, 
            y = ~preciom, 
            type = "scatter", 
            mode = "lines",
            line = list(color = 'red', width = 2), 
            name = "Línea de Tendencia") %>%
  # Configuración del gráfico
  layout(title = "Área Construida vs. Precio (Solo Casas en Zona Norte)",
         xaxis = list(title = "Área Construida (m²)"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_area_norte

Observamos que se presenta una relación positiva entre la variable precio y área construida, preliminarmente de concluye que a medida que el área construida incrementa el precio aumenta, y tiene sentido,en el mercado inmobiliario las viviendas son mas costosas según su espacio.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Norte y solo Casas)
vivienda_norte_casas <- vivienda_limpia %>%
  filter(zona == "Zona Norte", tipo == "Casa") %>%  # Filtrar solo casas en la Zona Norte
  select(preciom, estrato) %>%  # Seleccionar solo Estrato y Precio
  na.omit()  # Eliminar valores faltantes

# Definir colores personalizados según las imágenes anteriores
colores_estrato <- c("#76c6b8", "#e4805e", "#597dbd", "#c779b1", "#7db66f") 

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_estrato_norte_casas <- plot_ly(vivienda_norte_casas, 
                                       x = ~factor(estrato, levels = sort(unique(vivienda_norte_casas$estrato))), 
                                       y = ~preciom, 
                                       type = "box", 
                                       color = ~factor(estrato), 
                                       colors = colores_estrato) %>%
  layout(title = "Distribución del Precio por Estrato (Solo Casas en Zona Norte)",
         xaxis = list(title = "Estrato", categoryorder = "category ascending"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_estrato_norte_casas

En cuanto al estrato, observamos también una relación positiva con el precio, a medida que el estrato aumenta el precio se incrementa, también tiene sentido, usualmente las zonas más exclusivas de la ciudad se encuentran ubicados en los estratos más altos y el metro cuadrado en estas ubicaciones privilegiadas suelen ser más costosos que el promedio en otros estratos.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Norte y solo Casas)
vivienda_norte_casas <- vivienda_limpia %>%
  filter(zona == "Zona Norte", tipo == "Casa") %>%  # Filtrar solo casas en la Zona Norte
  select(preciom, banios) %>%  # Seleccionar solo Número de Baños y Precio
  na.omit()  # Eliminar valores faltantes

# Ordenar los niveles del factor para que el número de baños más alto aparezca al final
niveles_ordenados <- sort(unique(vivienda_norte_casas$banios))  # Ordenar numéricamente
niveles_ordenados <- c(niveles_ordenados[niveles_ordenados != max(niveles_ordenados)], max(niveles_ordenados))  # Mover el mayor al final

# Definir colores personalizados para el número de baños
colores_banios <- c("#76c6b8", "#e4805e", "#597dbd", "#c779b1", "#7db66f", "#ffcc00", "#8e44ad", "#ff5733", "#2a9d8f", "#8e44ad")

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_banios_norte_casas <- plot_ly(vivienda_norte_casas, 
                                      x = ~factor(banios, levels = niveles_ordenados), 
                                      y = ~preciom, 
                                      type = "box", 
                                      color = ~factor(banios), 
                                      colors = colores_banios) %>%
  layout(title = "Distribución del Precio por Número de Baños (Solo Casas en Zona Norte)",
         xaxis = list(title = "Número de Baños"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_banios_norte_casas

Referente a la relación entre precio y número de baños, se aprecia una relación positiva, el precio es mayor en la medida que el inmueble tiene más baños, una de las razones de las variaciones del precio, es que si hay más baños la vivienda puede tener más metros cuadrados.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Norte y solo Casas)
vivienda_norte_casas <- vivienda_limpia %>%
  filter(zona == "Zona Norte", tipo == "Casa") %>%  # Filtrar solo casas en la Zona Norte
  select(preciom, habitaciones) %>%  # Seleccionar solo Número de Habitaciones y Precio
  na.omit()  # Eliminar valores faltantes

# Ordenar los niveles del factor para que el número de habitaciones más alto aparezca al final
niveles_ordenados <- sort(unique(vivienda_norte_casas$habitaciones))  # Ordenar numéricamente
niveles_ordenados <- c(niveles_ordenados[niveles_ordenados != max(niveles_ordenados)], max(niveles_ordenados))  # Mover el mayor al final

# Definir colores personalizados para el número de habitaciones
colores_habitaciones <- c("#76c6b8", "#e4805e", "#597dbd", "#c779b1", "#7db66f", "#ffcc00", "#8e44ad", "#ff5733", "#2a9d8f", "#8e44ad")

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_habitaciones_norte_casas <- plot_ly(vivienda_norte_casas, 
                                            x = ~factor(habitaciones, levels = niveles_ordenados), 
                                            y = ~preciom, 
                                            type = "box", 
                                            color = ~factor(habitaciones), 
                                            colors = colores_habitaciones) %>%
  layout(title = "Distribución del Precio por Número de Habitaciones (Solo Casas en Zona Norte)",
         xaxis = list(title = "Número de Habitaciones"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_habitaciones_norte_casas

Por último, observamos que la relación entre el precio y el número de habitaciones también es positiva, entre mas habitaciones el precio es más alto, comportamiento similar al de los baños, se infiere que entre mas habitaciones tenga la vivienda el área es grande y por ende su precio.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar solo viviendas tipo Casa en todas las zonas y excluir longitud y latitud
vivienda_casas <- vivienda_limpia %>%
  filter(tipo == "Casa") %>%  # Filtrar solo casas
  select_if(is.numeric) %>%  # Seleccionar solo columnas numéricas
  select(-longitud, -latitud) %>%  # Excluir longitud y latitud
  na.omit()  # Eliminar valores faltantes

# Calcular la matriz de correlación
matriz_correlacion <- cor(vivienda_casas, use = "complete.obs")

# Crear etiquetas de texto con valores de correlación
texto_correlacion <- round(matriz_correlacion, 2)  # Redondear a 2 decimales

# Crear el heatmap de correlación con escala de naranjas y valores dentro del gráfico
grafico_correlacion <- plot_ly(
  z = matriz_correlacion, 
  x = colnames(matriz_correlacion), 
  y = colnames(matriz_correlacion), 
  type = "heatmap",
  colorscale = "YlOrBr",  # Escala de colores en tonos de naranja
  text = texto_correlacion,  # Agregar los valores de correlación como texto
  texttemplate = "%{text}",  # Mostrar los valores directamente en las celdas
  hoverinfo = "text",  # Mostrar los valores al pasar el mouse
  showscale = TRUE  # Mostrar la escala de colores
) %>%
  layout(title = "Matriz de Correlación (Solo Casas en Todas las Zonas)",
         xaxis = list(title = "Variables"),
         yaxis = list(title = "Variables"))

# Mostrar el gráfico
grafico_correlacion
# Cargar librerías necesarias
library(GGally)
library(ggplot2)
library(dplyr)

# Filtrar solo viviendas tipo Casa en todas las zonas y excluir longitud y latitud
vivienda_casas <- vivienda_limpia %>%
  filter(tipo == "Casa") %>%  # Filtrar solo casas
  select(preciom, areaconst, banios, habitaciones) %>%  # Seleccionar variables relevantes
  na.omit()  # Eliminar valores faltantes

# Crear la matriz de correlación con ggpairs
ggpairs(vivienda_casas,
        lower = list(continuous = ggally_points),  # Gráficos de dispersión en la parte inferior
        diag = list(continuous = ggally_densityDiag),  # Histogramas de densidad en la diagonal
        upper = list(continuous = ggally_cor)) +  # Correlaciones en la parte superior
  theme_minimal()  # Estilo limpio

Observando las correlaciones respecto a la variables objetivo precio, identificamos una correlación fuerte positiva con área construida del 0.65. lo que sugiere que, dado su grado de correlación es la más influyente en el precio de las casas, seguida de parqueaderos con una correlación moderada positiva del 0.60 que puede ser un valor relevante en el precio, continuando con baños con una correlación moderada del 0.58,que influye en el precio pero en menor medida y, finalizando con habitaciones con un menor grado de correlación de 0.10, indicando que el número de habitaciones es irrelevante en la predicción del precio.

Modelado tipo casa

# Cargar librerías necesarias
library(dplyr)

# Filtrar solo viviendas tipo Casa y seleccionar variables relevantes
vivienda_modelo <- vivienda_limpia %>%
  filter(tipo == "Casa") %>%  # Filtrar solo casas
  select(preciom, areaconst, estrato, banios, habitaciones, zona) %>%  # Variables del modelo
  na.omit()  # Eliminar valores faltantes

# Convertir variables categóricas a factores para que el modelo las interprete correctamente
vivienda_modelo$zona <- as.factor(vivienda_modelo$zona)
vivienda_modelo$estrato <- as.factor(vivienda_modelo$estrato)

# Ajustar el modelo de regresión lineal múltiple
modelo <- lm(preciom~ areaconst + estrato + banios + habitaciones + zona, data = vivienda_modelo)

# Resumen del modelo
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + banios + habitaciones + 
##     zona, data = vivienda_modelo)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1274.52   -93.49   -19.85    55.99  1176.58 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       63.36915   22.90276   2.767 0.005693 ** 
## areaconst          0.81947    0.02499  32.787  < 2e-16 ***
## estrato4          38.05227   12.41981   3.064 0.002204 ** 
## estrato5         115.32674   12.25892   9.408  < 2e-16 ***
## estrato6         425.17185   15.01324  28.320  < 2e-16 ***
## banios            46.30798    3.34225  13.855  < 2e-16 ***
## habitaciones     -10.29233    2.68508  -3.833 0.000129 ***
## zonaZona Norte   -38.41303   22.18943  -1.731 0.083524 .  
## zonaZona Oeste    44.91279   26.50614   1.694 0.090282 .  
## zonaZona Oriente -82.15296   23.12684  -3.552 0.000387 ***
## zonaZona Sur      -6.59032   22.23330  -0.296 0.766931    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 196.5 on 3158 degrees of freedom
## Multiple R-squared:  0.7013, Adjusted R-squared:  0.7003 
## F-statistic: 741.4 on 10 and 3158 DF,  p-value: < 2.2e-16

El modelo se construye según los requerimientos del ejercicio planteado, en el que especifica que se deben incluir solo las variables predictoras: área construida, estrato, número de de baños, número de habitaciones, y zona de ubicación de la vivienda, para nuestro caso ubicación de las viviendas tipo casa, dado que nuestro objetivo es predecir en este punto la variable objetivo el precio de las casas, si bien, en el análisis de correlación observamos que la variable número de habitaciones no aportaba significativamente en la predicción, por requerimiento del ejercicio se incluye.

Los residuales indican desviaciones entre el precio real y el valor pronosticado, analizando los resultados de los residuos observamos que el valor de la mediana es bajo (-19.85), indicando que el modelo no se desvía significativamente del precio real evitando subestimar y sobrestimar los precios de las casas. continuando con el análisis, la mayoría de los errores se ubican entre -93.49 y 55.99, lo que significa que cuando el modelo se equivoca en la predicción el valor no es muy grande, es decir, que el valor no se desvía significativamente del precio real, por último, cuando revisamos el resultado de los valores los valores mínimos (-1274.52) y máximo (1176.58) de los errores, evidenciamos que los valores son altos, indicando que existen situaciones que el modelo no logra predecir con exactitud arrojando predicciones con errores muy altos, esto se puede atribuir al efecto de los datos atípicos.

Validando los resultados de p-valor de la prueba t-Student, que por regla nos indica que antes valores menores a 0.05 la variable contribuye a la predicción del modelo y, en caso contrario para valores mayores a 0.05 la variable no es significativa y no contribuye a la predicción del modelo, contrastando los resultados con esta regla, obtenemos que las variables que más aportan son área construida, estrato y número de baños, así mismo, validando observamos un dato con cierto grado de incoherencia, en el análisis de correlación observamos que el número de habitaciones, influye en un grado muy débil el precio de la vivienda y, cuando verificamos el p-valor ratificamos que influye en el precio; sin embargo, entre mas habitaciones el precio disminuye según los datos, pero en el mundo real entre más habitaciones tiene una vivienda su valor es más alto.

En cuanto al análisis de los coeficientes de variación (Betas) obtenemos los siguientes resultados:

  • βo = 63.37 (Intercepto) indica el precio cuando todas las variables son 0.
  • β1 = 0.82 (área construida) indica que cada metro cuadrado adicional aumenta el precio en 0.82 unidades
  • β5 = 46.31 (Número de baños) indica que cada baño adicional incrementa el precio en 46.31 unidades.
  • β6 = 10.29 (Número de habitaciones) indica que cada habitación adicional reduce el precio en 10.29 unidades
  • β2 = 38.05 (Estrato 4) indica que las casas en estrato 4 cuestan 38.05 unidades más que en el estrato 3
  • β3 = 115.33(Estrato 5) indica que las casas en estrato 5 cuestan 115.33 unidades más que en el estrato 3
  • β4 = 425.17(Estrato 6) indica que las casas en estrato 6 cuestan 425.17 unidades más que en el estrato 3
  • β7 = -38.41 (Zona Norte) indica que vivir en Zona Norte reduce el precio en 38.41 unidades comparado con Zona Centro
  • β8 = 44.91 (Zona Oeste) indica que vivir en Zona Oeste aumenta el precio en 44.91 unidades comparado con Zona Centro
  • β9 = -82.15 (Zona Oriente) indicar que Vivir en Zona Oriente reduce el precio en 82.15 unidades comparado con Zona Centro
  • β10 = -6.59 (Zona Sur) indica No hay una gran diferencia en el precio con respecto a Zona Centro.

De acuerdo con los resultados de los coeficientes podemos concluir que las variables estadísticamente significativas con p-valor menores a 0.05 son: área construida, estrato, número de baños y zona oriente,es decir que tienen un impacto importante en la predicción del precio de las casas, en caso contrario, las que más disminuyen el precio son: zona y número de habitaciones.

Analizando el error estándar de los coeficientes que nos indica que tan precisas son las estimaciones, obtenemos que las estimaciones con mayor grado de precisión son área construida y número de baños, y las menos confiables zona.

Evaluando los indicadores claves del modelo, podemos concluir que el modelo explica el 70.13% de la variabilidad en los precios de las casas , con un ajuste según el R² ajustado de 70.03%. El error estándar de los residuos (196.5) indica imprecisiones en las predicciones, atribuibles a factores no identificados. El estadístico F de 741.4 y el p-value menor a 2.2e-16 aseguran que el modelo es aceptable, sin embargo, aunque es confiable, se puede mejorar seleccionado variables con un valor más significativo y descartando las que menos le aporten al modelo como por ejemplo zona norte, zona oeste y zona sur.

# Ejecutar ANOVA en el modelo de regresión
anova_modelo <- anova(modelo)

# Mostrar resultados
print(anova_modelo)
## Analysis of Variance Table
## 
## Response: preciom
##                Df    Sum Sq   Mean Sq  F value    Pr(>F)    
## areaconst       1 174724888 174724888 4524.364 < 2.2e-16 ***
## estrato         3 101219586  33739862  873.667 < 2.2e-16 ***
## banios          1   7875712   7875712  203.935 < 2.2e-16 ***
## habitaciones    1    674929    674929   17.477 2.987e-05 ***
## zona            4   1824834    456209   11.813 1.586e-09 ***
## Residuals    3158 121957748     38619                       
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Para confirmar los resultados de la etapa anterior, aplicamos un análisis ANOVA, para evaluar el grado de importancia de las variables en términos predictivos del modelo, analizando la variabilidad de cada uno de los predictores obtenemos que la suma de los cuadrados es 286.319.949 (SCR) y la suma de los cuadrados de los errores es 121.957.748 (SCE), indicando que la variación del precio de las casas es explicada en gran parte por los predictores (SCR); sin embargo, existe una variabilidad que no es explicada por los predictores, concluyendo como el SCR es mayor a SCR, gran parte de la variación de los precios es explicada por la variables elegidas y podemos afirmar que el modelo es aceptable.

Validación supuestos tipo casa

# Cargar librerías necesarias
# Cargar librerías necesarias
library(dplyr)
library(knitr)

# Calcular resumen estadístico de los residuos
resumen_residuos <- data.frame(
  Estadística = c("Mínimo", "Q1", "Mediana", "Media", "Q3", "Máximo", "Desviación Estándar", "Asimetría", "Curtosis"),
  Valor = c(min(residuals(modelo)), quantile(residuals(modelo), c(0.25, 0.5, 0.75)), mean(residuals(modelo)), 
            max(residuals(modelo)), sd(residuals(modelo)), 
            mean((residuals(modelo) - mean(residuals(modelo)))^3) / sd(residuals(modelo))^3, 
            mean((residuals(modelo) - mean(residuals(modelo)))^4) / sd(residuals(modelo))^4 - 3)
)

# Mostrar tabla formateada
kable(resumen_residuos, format = "markdown", align = "c", caption = "Resumen Estadístico de los Residuos")
Resumen Estadístico de los Residuos
Estadística Valor
Mínimo -1274.522029
Q1 -93.493137
Mediana -19.850542
Media 55.994491
Q3 0.000000
Máximo 1176.578958
Desviación Estándar 196.205923
Asimetría 1.352024
Curtosis 6.218037

Supuesto de la mediana de los errores es siempre 0

# Calcular la mediana de los residuos del modelo
mediana_residuos <- median(residuals(modelo))

# Mostrar el resultado
print(paste("Mediana de los residuos:", round(mediana_residuos, 4)))
## [1] "Mediana de los residuos: -19.8505"

Interpretando los resultados, concluimos que el supuesto no se cumple, la mediana de los residuos es -19.8505, que es distinto de Cero; sin embargo, no es una desviación leve que nos indica que nuestro modelo presenta un pequeño sesgo subestimando el precio de las casas.

Supuesto de independencia de los errores

# Cargar la librería necesaria
library(lmtest)

# Realizar la prueba de Durbin-Watson
dw_test <- dwtest(modelo)

# Mostrar los resultados
print(dw_test)
## 
##  Durbin-Watson test
## 
## data:  modelo
## DW = 1.7384, p-value = 4.373e-14
## alternative hypothesis: true autocorrelation is greater than 0

Validando los resultados, obtuvimos que el supuesto no se cumple, aplicando el test, el estadístico DW = 1.74, cercano a 2 que nos indica que existe una leve correlación; sin embargo, contrastando con el resultado de p-valor = 4.373e-14, es muy cercano a 0, confirmando la hipótesis alternativa (existe correlación positiva en los residuos)

Supuesto de Varianza constante de los errores (Homocedasticidad)

# Cargar la librería necesaria
library(lmtest)

# Realizar la prueba de Breusch-Pagan
bp_test <- bptest(modelo)

# Mostrar los resultados
print(bp_test)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo
## BP = 476.73, df = 10, p-value < 2.2e-16

De acuerdo con los resultados obtenidos, este supuesto tampoco se cumple, el modelo presenta heterocedasticidad, revisando el p-valor < 2.2e-16 es muy cercano a 0 por lo que se rechaza la hipótesis nula (los errores tienen varianza constante), es decir, que los errores no presentan una varianza constante, lo que plantea dudas sobre precisión de los coeficientes estimados.

Supuesto de normalidad de los errores

# Gráfico Q-Q de los residuos
qqnorm(residuals(modelo))
qqline(residuals(modelo), col = "red")

# Prueba de Shapiro-Wilk para normalidad
shapiro_test <- shapiro.test(residuals(modelo))

# Mostrar los resultados
print(shapiro_test)
## 
##  Shapiro-Wilk normality test
## 
## data:  residuals(modelo)
## W = 0.86986, p-value < 2.2e-16

Para validar el cumplimiento de este supuesto, realizamos dos pruebas, Shapiro-Wilk y graficamos el Q-Q, el primer test nos arroja un p-valor muy cercano a 0; por lo tanto se rechaza la hipótesis nula (Los residuos siguen una distribución normal), para contrastar el resultado, observamos el comportamiento en el gráfico y verificamos que, si bien, en la parte central lo puntos se ajustan a la linea, sugiriendo que en esta zona los datos siguen un distribución aparentemente normal, en las colas se evidencia una desviación significativa a la linea de normalidad, por lo tanto, se concluye que no existe una distribución normal en los errores del modelo.

Supuesto de no colinealidad

# Cargar la librería necesaria
library(car)

# Calcular el VIF
vif_values <- vif(modelo)

# Mostrar los resultados
print(vif_values)
##                  GVIF Df GVIF^(1/(2*Df))
## areaconst    1.505755  1        1.227092
## estrato      2.609473  3        1.173344
## banios       2.137518  1        1.462025
## habitaciones 1.695554  1        1.302134
## zona         1.812101  4        1.077142

Validando este supuesto, el objetivo es verificar si se presenta colinealidad entre las variable predictoras, verificando los resultados del VF (factor de inflación de la varianza) de los errores, obtenemos que las cinco variables predicadoras presentan resultados menores a cinco, confirmando que no existe colinealidad entre las variables predictoras; por lo tanto, este es el único supuesto que se cumple.

Una vez evaluados los cinco supuestos y analizadas las métricas de desempeño, concluimos que, el modelo no presenta un buen desempeño en términos de normalidad, independencias y homocedasticidad de los errores, la mayoría de las métricas evaluadas no cumplen los criterios, para mejorar la precisión y predicción del modelo, es necesario aplicar estrategias de tratamiento a los valores atípicos e incluir otras variables predictoras que mejoren el desempeño y la confiabilidad de los resultados.

Predicción tipo casa

Estrato 4

# Crear un nuevo dataframe con las características de la vivienda a predecir
nueva_vivienda <- data.frame(
  areaconst = 200,    # Área construida
  estrato = factor(4, levels = levels(vivienda_modelo$estrato)),  # Estrato 4
  banios = 2,         # Número de baños
  habitaciones = 4,   # Número de habitaciones
  zona = factor("Zona Norte", levels = levels(vivienda_modelo$zona)) # Zona Norte
)

# Realizar la predicción del precio de la vivienda
prediccion_precio <- predict(modelo, newdata = nueva_vivienda)

# Mostrar el resultado de la predicción
print(paste("El precio estimado de la vivienda es:", round(prediccion_precio, 2), "millones de pesos"))
## [1] "El precio estimado de la vivienda es: 278.35 millones de pesos"

Una vez ejecutamos el modelo, para una vivienda de acuerdo con estas características: Área construida: 200 m², estrato 4, con dos baños, cuatro habitaciones y ubicado en la zona norte, obtenemos que el precio estimado es aproximadamente de 278 Millones de pesos, cabe resaltar que la variable parqueaderos no se complementó en el modelo de acuerdo con los requerimientos del ejercicio , por lo tanto, la predicción incluye esa variable.

Estrato 5

# Crear un nuevo dataframe con las características de la vivienda a predecir
nueva_vivienda <- data.frame(
  areaconst = 200,    # Área construida
  estrato = factor(5, levels = levels(vivienda_modelo$estrato)),  # Estrato 4
  banios = 2,         # Número de baños
  habitaciones = 4,   # Número de habitaciones
  zona = factor("Zona Norte", levels = levels(vivienda_modelo$zona)) # Zona Norte
)

# Realizar la predicción del precio de la vivienda
prediccion_precio <- predict(modelo, newdata = nueva_vivienda)

# Mostrar el resultado de la predicción
print(paste("El precio estimado de la vivienda es:", round(prediccion_precio, 2), "millones de pesos"))
## [1] "El precio estimado de la vivienda es: 355.62 millones de pesos"

En la segunda estimación, para una vivienda de acuerdo con estas características: Área construida: 200 m², estrato 5, con dos baños, cuatro habitaciones y ubicado en la zona norte, obtenemos que el precio estimado es aproximadamente de 356 Millones de pesos.

Comparando ambas estimaciones y contemplando la restricción presupuestaria indicando que el valor del inmueble debe tener un precio menor o igual a 350 Millones, basados en esa restricción la oferta que cumple con ese requisito es la casa en el estrato 4.

Ofertas tipo casa

# Cargar librerías necesarias
library(dplyr)
library(leaflet)

# Definir el nuevo rango de precios entre 300M y 380M (para incluir más opciones)
precio_min <- 300  
precio_max <- 350  

# Filtrar solo casas en la Zona Norte con criterios más flexibles
casas_filtradas <- vivienda_limpia %>%
  filter(
    zona_corr == "Zona Norte",  
    tipo == "Casa",             
    areaconst >= 200,  # Ampliar rango de área construida
    estrato %in% c(4, 5),       # Estrato 4 o 5
    banios >= 2,                # Al menos 2 baños
    habitaciones >= 4,          # Al menos 3 habitaciones
    parqueaderos >= 1,          # Incluir casas con o sin parqueaderos
    preciom >= precio_min & preciom <= precio_max  # Nuevo rango de precios más amplio
  )

# Verificar si hay datos después del filtro
print(nrow(casas_filtradas))  # Mostrar la cantidad de casas que cumplen con los criterios
## [1] 27
# Definir iconos para las banderas de estrato 4 (rojo) y estrato 5 (verde)
iconos_bandera <- awesomeIcons(
  icon = 'flag',
  iconColor = 'white',
  library = 'fa',
  markerColor = ifelse(casas_filtradas$estrato == 4, "red", "green")
)

# Crear mapa interactivo con las casas filtradas
leaflet(casas_filtradas) %>%
  addTiles() %>%
  setView(lng = -76.5, lat = 3.45, zoom = 12) %>%
  addAwesomeMarkers(
    lng = ~longitud, lat = ~latitud,
    icon = iconos_bandera,
    popup = ~paste("Zona:", zona_corr, "<br>",
                   "Estrato:", estrato, "<br>",
                   "Precio:", preciom, "M", "<br>",
                   "Área:", areaconst, "m²", "<br>",
                   "Baños:", banios, "<br>",
                   "Habitaciones:", habitaciones, "<br>",
                   "Parqueaderos:", parqueaderos)
  ) %>%
  addLegend(
    position = "topright",
    colors = c("red", "green"),
    labels = c("Estrato 4", "Estrato 5"),
    title = "Casas en Zona Norte (300M - 350M)"
  )
# Cargar librerías necesarias
library(dplyr)
library(DT)

# Calcular el precio por metro cuadrado
casas_filtradas <- casas_filtradas %>%
  mutate(precio_m2 = preciom / areaconst) %>% 
  arrange(precio_m2)  # Ordenar de menor a mayor precio por metro cuadrado

# Verificar cuántas casas hay en el mapa
print(paste("Número total de casas identificadas:", nrow(casas_filtradas)))
## [1] "Número total de casas identificadas: 27"
# Mostrar todas las casas identificadas en la tabla interactiva
datatable(casas_filtradas, 
          options = list(scrollX = TRUE, pageLength = nrow(casas_filtradas)), 
          caption = "Todas las Casas Identificadas en la Zona Norte")
# Identificar las cinco mejores ofertas (menor precio por m²)
mejores_ofertas <- casas_filtradas %>%
  head(5)  # Tomar las 5 con menor precio por m²

# Mostrar las cinco mejores ofertas en una tabla interactiva
datatable(mejores_ofertas, 
          options = list(scrollX = TRUE, pageLength = 5), 
          caption = "Las 5 Mejores Ofertas en la Zona Norte (Basado en Precio por m²)")

De acuerdo con los requerimientos del cliente, realizamos una búsqueda exhaustiva de las ofertas disponibles de casas en el mercado inmobiliario de Cali, inicialmente aplicamos los siguientes criterios de búsqueda:

  • Ubicación en la zona norte de Cali
  • Rango de precios entre 300 - 350 Millones de pesos.
  • Área contruida desde 200 m².
  • Estrato 4 o 5.
  • Con cuatro habitaciones o más.
  • Con al menos un parqueadero.
  • Con dos baños o más.

Como resultado obtuvimos una litado preliminar de 27 inmueble que cumplen esas condiciones, analizamos cada un de las propiedades y para realizar la oferta definimos criterios de valorización, ubicación y relación precio metro cuadrado, una vez analizados estos criterios se determina que las cinco ofertas más atractivas por su costo y valorización con las que se presentan en la tabla.

Analisis exploratorio tipo apto

# Filtrar los apartamentos ubicados en la zona sur
apartamentos_sur <- vivienda_limpia %>%
  filter(tipo == "Apartamento" & zona == "Zona Sur")

# Mostrar la tabla con los resultados
library(DT)
datatable(apartamentos_sur, options = list(scrollX = TRUE))
# Mostrar el total de apartamentos
print(paste("Total de apartamentos en la Zona Sur:", nrow(apartamentos_sur)))
## [1] "Total de apartamentos en la Zona Sur: 2777"
# Filtrar los apartamentos ubicados en la zona sur y mostrar los tres primeros registros
apartamentos_sur <- vivienda_limpia %>%
  filter(tipo == "Apartamento" & zona == "Zona Sur") %>%
  head(3)

# Mostrar la tabla con los resultados
library(DT)
datatable(apartamentos_sur, options = list(scrollX = TRUE))
# Filtrar solo los apartamentos ubicados en la Zona Sur
apartamentos_sur <- vivienda_limpia %>%
  filter(tipo == "Apartamento" & zona_corr == "Zona Sur")

# Contar el número total de apartamentos en la Zona Sur
total_apartamentos_sur <- nrow(apartamentos_sur)

# Crear un data frame con el resultado
tabla_apartamentos_sur <- data.frame(
  Zona = "Zona Sur",
  Total_Apartamentos = total_apartamentos_sur
)

# Mostrar la tabla
print(tabla_apartamentos_sur)
##       Zona Total_Apartamentos
## 1 Zona Sur               2416
# Opcional: Mostrar la tabla de forma interactiva
library(DT)
datatable(tabla_apartamentos_sur, options = list(scrollX = TRUE))
# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Apartamentos)
vivienda_filtrada <- vivienda_limpia %>%
  filter(tipo == "Apartamento") %>%  # Filtrar solo apartamentos
  select(preciom, zona) %>%
  na.omit()  # Eliminar valores faltantes

# Definir los colores personalizados según la imagen
colores_zonas <- c("Zona Centro" = "#76c6b8",  # Verde menta
                   "Zona Norte" = "#e4805e",   # Naranja
                   "Zona Oeste" = "#597dbd",  # Azul
                   "Zona Oriente" = "#c779b1", # Rosa
                   "Zona Sur" = "#7db66f")    # Verde claro

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_zona <- plot_ly(vivienda_filtrada, 
                        x = ~factor(zona, levels = c("Zona Centro", "Zona Norte", "Zona Oeste", "Zona Oriente", "Zona Sur")), 
                        y = ~preciom, 
                        type = "box", 
                        color = ~factor(zona), 
                        colors = colores_zonas) %>%
  layout(title = "Distribución del Precio por Zona (Solo Apartamentos)",
         xaxis = list(title = "Zona", categoryorder = "array"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_zona

Analizando el comportamiento de los precios según la ubicación de las apartamentos en las distintas zonas, observamos que las apartamentos más costosos se ubican en la zona oeste, seguido por la zona sur y norte, los precios más bajos se ubican en la zona centro y oriente respectivamente, preliminarmente podríamos inferir que en la zona oeste se ubican los apartamentos con el metro cuadrado más costoso y posiblemente porque son zonas exclusivas de la ciudad de estratos altos.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Sur y solo Apartamentos)
vivienda_sur <- vivienda_limpia %>%
  filter(zona == "Zona Sur", tipo == "Apartamento") %>%  # Filtrar solo apartamentos en la Zona Sur
  select(preciom, areaconst) %>%  # Seleccionar Precio y Área Construida
  na.omit()  # Eliminar valores faltantes

# Calcular la regresión lineal
modelo <- lm(preciom ~ areaconst, data = vivienda_sur)

# Crear datos de predicción para la línea de tendencia
predicciones <- data.frame(
  areaconst = seq(min(vivienda_sur$areaconst), max(vivienda_sur$areaconst), length.out = 100)
)
predicciones$preciom <- predict(modelo, newdata = predicciones)

# Crear el gráfico de dispersión con la línea de tendencia
grafico_area_sur <- plot_ly() %>%
  # Agregar puntos de dispersión
  add_trace(data = vivienda_sur, 
            x = ~areaconst, 
            y = ~preciom, 
            type = "scatter", 
            mode = "markers",
            marker = list(size = 5, opacity = 0.5, color = 'blue'),
            name = "Apartamentos en Zona Sur") %>%
  # Agregar la línea de tendencia
  add_trace(data = predicciones, 
            x = ~areaconst, 
            y = ~preciom, 
            type = "scatter", 
            mode = "lines",
            line = list(color = 'red', width = 2), 
            name = "Línea de Tendencia") %>%
  # Configuración del gráfico
  layout(title = "Área Construida vs. Precio (Solo Apartamentos en Zona Sur)",
         xaxis = list(title = "Área Construida (m²)"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_area_sur

Observamos que se presenta una relación positiva entre la variable precio y área construida, preliminarmente de concluye que a medida que el área construida incrementa el precio aumenta, y tiene sentido,en el mercado inmobiliario las viviendas son mas costosas según su espacio.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Sur y solo Apartamentos)
vivienda_sur_apartamentos <- vivienda_limpia %>%
  filter(zona == "Zona Sur", tipo == "Apartamento") %>%  # Filtrar solo apartamentos en la Zona Sur
  select(preciom, estrato) %>%  # Seleccionar solo Estrato y Precio
  na.omit()  # Eliminar valores faltantes

# Definir colores personalizados según las imágenes anteriores
colores_estrato <- c("#76c6b8", "#e4805e", "#597dbd", "#c779b1", "#7db66f") 

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_estrato_sur_apartamentos <- plot_ly(vivienda_sur_apartamentos, 
                                       x = ~factor(estrato, levels = sort(unique(vivienda_sur_apartamentos$estrato))), 
                                       y = ~preciom, 
                                       type = "box", 
                                       color = ~factor(estrato), 
                                       colors = colores_estrato) %>%
  layout(title = "Distribución del Precio por Estrato (Solo Apartamentos en Zona Sur)",
         xaxis = list(title = "Estrato", categoryorder = "category ascending"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_estrato_sur_apartamentos

En cuanto al estrato, observamos también una relación positiva con el precio, a medida que el estrato aumenta el precio se incrementa, también tiene sentido, usualmente las zonas más exclusivas de la ciudad se encuentran ubicados en los estratos más altos y el metro cuadrado en estas ubicaciones privilegiadas suelen ser más costosos que el promedio en otros estratos.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Sur y solo Apartamentos)
vivienda_sur_apartamentos <- vivienda_limpia %>%
  filter(zona == "Zona Sur", tipo == "Apartamento") %>%  # Filtrar solo apartamentos en la Zona Sur
  select(preciom, banios) %>%  # Seleccionar solo Número de Baños y Precio
  na.omit()  # Eliminar valores faltantes

# Ordenar los niveles del factor para que el número de baños más alto aparezca al final
niveles_ordenados <- sort(unique(vivienda_sur_apartamentos$banios))  # Ordenar numéricamente
niveles_ordenados <- c(niveles_ordenados[niveles_ordenados != max(niveles_ordenados)], max(niveles_ordenados))  # Mover el mayor al final

# Definir colores personalizados para el número de baños
colores_banios <- c("#76c6b8", "#e4805e", "#597dbd", "#c779b1", "#7db66f", "#ffcc00", "#8e44ad", "#ff5733", "#2a9d8f", "#8e44ad")

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_banios_sur_apartamentos <- plot_ly(vivienda_sur_apartamentos, 
                                      x = ~factor(banios, levels = niveles_ordenados), 
                                      y = ~preciom, 
                                      type = "box", 
                                      color = ~factor(banios), 
                                      colors = colores_banios) %>%
  layout(title = "Distribución del Precio por Número de Baños (Solo Apartamentos en Zona Sur)",
         xaxis = list(title = "Número de Baños"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_banios_sur_apartamentos

Referente a la relación entre precio y número de baños, se aprecia una relación positiva, el precio es mayor en la medida que el inmueble tiene más baños, una de las razones de las variaciones del precio, es que si hay más baños la vivienda puede tener más metros cuadrados.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar y limpiar los datos (Solo Zona Sur y solo Apartamentos)
vivienda_sur_apartamentos <- vivienda_limpia %>%
  filter(zona == "Zona Sur", tipo == "Apartamento") %>%  # Filtrar solo apartamentos en la Zona Sur
  select(preciom, habitaciones) %>%  # Seleccionar solo Número de Habitaciones y Precio
  na.omit()  # Eliminar valores faltantes

# Ordenar los niveles del factor para que el número de habitaciones más alto aparezca al final
niveles_ordenados <- sort(unique(vivienda_sur_apartamentos$habitaciones))  # Ordenar numéricamente
niveles_ordenados <- c(niveles_ordenados[niveles_ordenados != max(niveles_ordenados)], max(niveles_ordenados))  # Mover el mayor al final

# Definir colores personalizados para el número de habitaciones
colores_habitaciones <- c("#76c6b8", "#e4805e", "#597dbd", "#c779b1", "#7db66f", "#ffcc00", "#8e44ad", "#ff5733", "#2a9d8f", "#8e44ad")

# Crear el gráfico de cajas (boxplot) con colores específicos
grafico_habitaciones_sur_apartamentos <- plot_ly(vivienda_sur_apartamentos, 
                                            x = ~factor(habitaciones, levels = niveles_ordenados), 
                                            y = ~preciom, 
                                            type = "box", 
                                            color = ~factor(habitaciones), 
                                            colors = colores_habitaciones) %>%
  layout(title = "Distribución del Precio por Número de Habitaciones (Solo Apartamentos en Zona Sur)",
         xaxis = list(title = "Número de Habitaciones"),
         yaxis = list(title = "Precio (M)"))

# Mostrar el gráfico
grafico_habitaciones_sur_apartamentos

Por último, observamos que la relación entre el precio y el número de habitaciones también es positiva, entre mas habitaciones el precio es más alto, comportamiento similar al de los baños, se infiere que entre mas habitaciones tenga la vivienda el área es grande y por ende su precio también.

# Cargar librerías necesarias
library(dplyr)
library(plotly)

# Filtrar solo viviendas tipo Apartamento y excluir longitud y latitud
vivienda_apartamentos <- vivienda_limpia %>%
  filter(tipo == "Apartamento") %>%  # Filtrar solo apartamentos
  select_if(is.numeric) %>%  # Seleccionar solo columnas numéricas
  select(-longitud, -latitud) %>%  # Excluir longitud y latitud
  na.omit()  # Eliminar valores faltantes

# Calcular la matriz de correlación
matriz_correlacion <- cor(vivienda_apartamentos, use = "complete.obs")

# Crear etiquetas de texto con valores de correlación
texto_correlacion <- round(matriz_correlacion, 2)  # Redondear a 2 decimales

# Crear el heatmap de correlación con escala de naranjas y valores dentro del gráfico
grafico_correlacion <- plot_ly(
  z = matriz_correlacion, 
  x = colnames(matriz_correlacion), 
  y = colnames(matriz_correlacion), 
  type = "heatmap",
  colorscale = "YlOrBr",  # Escala de colores en tonos de naranja
  text = texto_correlacion,  # Agregar los valores de correlación como texto
  texttemplate = "%{text}",  # Mostrar los valores directamente en las celdas
  hoverinfo = "text",  # Mostrar los valores al pasar el mouse
  showscale = TRUE  # Mostrar la escala de colores
) %>%
  layout(title = "Matriz de Correlación (Solo Apartamentos)",
         xaxis = list(title = "Variables"),
         yaxis = list(title = "Variables"))

# Mostrar el gráfico
grafico_correlacion
# Cargar librerías necesarias
library(GGally)
library(ggplot2)
library(dplyr)

# Filtrar solo viviendas tipo Apartamento en todas las zonas y excluir longitud y latitud
vivienda_apartamentos <- vivienda_limpia %>%
  filter(tipo == "Apartamento") %>%  # Filtrar solo apartamentos
  select(preciom, areaconst, banios, habitaciones) %>%  # Seleccionar variables relevantes
  na.omit()  # Eliminar valores faltantes

# Crear la matriz de correlación con ggpairs
ggpairs(vivienda_apartamentos,
        lower = list(continuous = ggally_points),  # Gráficos de dispersión en la parte inferior
        diag = list(continuous = ggally_densityDiag),  # Histogramas de densidad en la diagonal
        upper = list(continuous = ggally_cor)) +  # Correlaciones en la parte superior
  theme_minimal()  # Estilo limpio

Observando las correlaciones respecto a la variables objetivo precio, identificamos una correlación fuerte positiva con área construida del 0.83 lo que sugiere que, dado su grado de correlación es la más influyente en el precio de los apartamentos, seguida de baños con una correlación fuerte positiva del 0.75 que puede ser un valor relevante en el precio, continuando con parqueaderos con una correlación fuerte del 0.7 y, finalizando con habitaciones con un menor grado de correlación de 0.3, indicando que el número de habitaciones es irrelevante en la predicción del precio.

Modelado tipo apartamento

# Cargar librerías necesarias
library(dplyr)

# Filtrar solo viviendas tipo Apartamento y seleccionar variables relevantes
vivienda_modelo <- vivienda_limpia %>%
  filter(tipo == "Apartamento") %>%  # Filtrar solo apartamentos
  select(preciom, areaconst, estrato, banios, habitaciones, zona) %>%  # Variables del modelo
  na.omit()  # Eliminar valores faltantes

# Convertir variables categóricas a factores para que el modelo las interprete correctamente
vivienda_modelo$zona <- as.factor(vivienda_modelo$zona)
vivienda_modelo$estrato <- as.factor(vivienda_modelo$estrato)

# Ajustar el modelo de regresión lineal múltiple
modelo <- lm(preciom ~ areaconst + estrato + banios + habitaciones + zona, data = vivienda_modelo)

# Resumen del modelo
summary(modelo)
## 
## Call:
## lm(formula = preciom ~ areaconst + estrato + banios + habitaciones + 
##     zona, data = vivienda_modelo)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1881.44   -48.72     1.96    44.21   950.88 
## 
## Coefficients:
##                   Estimate Std. Error t value Pr(>|t|)    
## (Intercept)      -50.83077   28.05010  -1.812  0.07002 .  
## areaconst          2.23167    0.04257  52.422  < 2e-16 ***
## estrato4          18.85254    6.89473   2.734  0.00627 ** 
## estrato5          47.03699    6.85074   6.866 7.40e-12 ***
## estrato6         183.09264    8.69129  21.066  < 2e-16 ***
## banios            58.88372    3.01425  19.535  < 2e-16 ***
## habitaciones     -37.08801    3.43837 -10.787  < 2e-16 ***
## zonaZona Norte    47.34843   26.87116   1.762  0.07812 .  
## zonaZona Oeste   110.12558   27.21805   4.046 5.29e-05 ***
## zonaZona Oriente   6.71178   31.39613   0.214  0.83073    
## zonaZona Sur      38.93694   26.83363   1.451  0.14683    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 129.9 on 5063 degrees of freedom
## Multiple R-squared:  0.7989, Adjusted R-squared:  0.7985 
## F-statistic:  2011 on 10 and 5063 DF,  p-value: < 2.2e-16

El modelo se construye según los requerimientos del ejercicio planteado, en el que especifica que se deben incluir solo las variables predictoras: área construida, estrato, número de de baños, número de habitaciones, y zona de ubicación de la vivienda, para nuestro caso ubicación de las viviendas tipo apartamento, dado que nuestro objetivo es predecir en este punto la variable objetivo el precio de las casas, si bien, en el análisis de correlación observamos que la variable número de habitaciones no aportaba significativamente en la predicción, por requerimiento del ejercicio se incluye.

Los residuales indican desviaciones entre el precio real y el valor pronosticado, analizando los resultados de los residuos observamos que el valor de la mediana es bajo (1.96), indicando que el modelo no se desvía significativamente del precio real evitando subestimar y sobrestimar los precios de los apartamentos. continuando con el análisis, la mayoría de los errores se ubican entre -48.72 y 44.21, lo que significa que cuando el modelo se equivoca en la predicción el valor no es muy grande, es decir, que el valor no se desvía significativamente del precio real, por último, cuando revisamos el resultado de los valores los valores mínimos (-1881.44) y máximo (950.88) de los errores, evidenciamos que los valores son altos, indicando que existen situaciones que el modelo no logra predecir con exactitud arrojando predicciones con errores muy altos, esto se puede atribuir al efecto de los datos atípicos.

Validando los resultados de p-valor de la prueba t-Student, que por regla nos indica que antes valores menores a 0.05 la variable contribuye a la predicción del modelo y, en caso contrario para valores mayores a 0.05 la variable no es significativa y no contribuye a la predicción del modelo, contrastando los resultados con esta regla, obtenemos que las variables que más aportan son área construida, estrato y número de baños, así mismo, validando observamos un dato con cierto grado de incoherencia, en el análisis de correlación observamos que el número de habitaciones, influye en el precio de la vivienda y, cuando verificamos el p-valor ratificamos que influye en el precio; sin embargo, entre mas habitaciones el precio disminuye según los datos, pero en el mundo real entre más habitaciones tiene una vivienda su valor es más alto.

En cuanto al análisis de los coeficientes de variación (Betas) obtenemos los siguientes resultados:

  • βo = -50.83 (Intercepto) indica el precio cuando todas las variables son 0.
  • β1 = 2.2316 (área construida) indica que cada metro cuadrado adicional aumenta el precio en 2.23 unidades
  • β5 = 58.88 (Número de baños) indica que cada baño adicional incrementa el precio en 58.88 unidades.
  • β6 = -37.08 (Número de habitaciones) indica que cada habitación adicional reduce el precio en -37.08 unidades
  • β2 = 18.85 (Estrato 4) indica que los apartamentos en estrato 4 cuestan 18.85 unidades más que en el estrato 3
  • β3 = 47.03 (Estrato 5) indica que los apartamentos en estrato 5 cuestan 47.03 unidades más que en el estrato 3
  • β4 = 183.09(Estrato 6) indica que los apartamentos en estrato 6 cuestan 183.09 unidades más que en el estrato 3
  • β7 = 47.34 (Zona Norte) indica que vivir en Zona Norte aumenta el precio en 47.34 unidades comparado con Zona Centro
  • β8 = 110.12 (Zona Oeste) indica que vivir en Zona Oeste aumenta el precio en 110.12 unidades comparado con Zona Centro
  • β9 = 6.71 (Zona Oriente) indicar que Vivir en Zona Oriente aumenta el precio en 6.71 unidades comparado con Zona Centro
  • β10 = 38.93 (Zona Sur) indicar que Vivir en Zona Oriente aumenta el precio en 38.93 unidades comparado con Zona Centro

De acuerdo con los resultados de los coeficientes podemos concluir que las variables estadísticamente significativas con p-valor menores a 0.05 son: área construida, estrato, número de baños y zona oriente,es decir que tienen un impacto importante en la predicción del precio de los apartamentos, en caso contrario, la que más disminuye el precio es:número de habitaciones.

Analizando el error estándar de los coeficientes que nos indica que tan precisas son las estimaciones, obtenemos que las estimaciones con mayor grado de precisión son área construida y número de baños, y las menos confiables zona.

Evaluando los indicadores claves del modelo, podemos concluir que el modelo explica el 79.89 % de la variabilidad en los precios de las casas ,con un ajuste según el R² ajustado de 79.85%. El error estándar de los residuos (129.9) indica imprecisiones en las predicciones, atribuibles a factores no identificados. El estadístico F de 2011 y el p-value menor a < 2.2e-16 aseguran que el modelo es aceptable, sin embargo, aunque es confiable, se puede mejorar seleccionado variables con un valor más significativo y descartando las que menos le aporten al modelo.

# Ejecutar ANOVA en el modelo de regresión para apartamentos
anova_modelo_apartamentos <- anova(modelo)

# Mostrar resultados
print(anova_modelo_apartamentos)
## Analysis of Variance Table
## 
## Response: preciom
##                Df    Sum Sq   Mean Sq   F value    Pr(>F)    
## areaconst       1 295692993 295692993 17514.658 < 2.2e-16 ***
## estrato         3  33887303  11295768   669.077 < 2.2e-16 ***
## banios          1   4386702   4386702   259.836 < 2.2e-16 ***
## habitaciones    1   2656627   2656627   157.359 < 2.2e-16 ***
## zona            4   2880671    720168    42.657 < 2.2e-16 ***
## Residuals    5063  85476613     16883                        
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Para confirmar los resultados de la etapa anterior, aplicamos un análisis ANOVA, para evaluar el grado de importancia de las variables en términos predictivos del modelo, analizando la variabilidad de cada uno de los predictores obtenemos que la suma de los cuadrados es 336.349.296 (SCR) y la suma de los cuadrados de los errores es 85.476.613 (SCE), indicando que la variación del precio de los apartamentos es explicada en gran parte por los predictores (SCR); sin embargo, existe una variabilidad que no es explicada por los predictores, concluyendo como el SCR es mayor a SCR, gran parte de la variación de los precios es explicada por la variables elegidas y podemos afirmar que el modelo es aceptable.

Validación supuestos tipo apto

# Cargar librerías necesarias
library(dplyr)
library(knitr)

# Filtrar solo apartamentos y seleccionar variables relevantes
vivienda_modelo_apart <- vivienda_limpia %>%
  filter(tipo == "Apartamento") %>%  # Filtrar solo apartamentos
  select(preciom, areaconst, estrato, banios, habitaciones, zona) %>%
  na.omit()  # Eliminar valores faltantes

# Convertir variables categóricas a factores para el modelo
vivienda_modelo_apart$zona <- as.factor(vivienda_modelo_apart$zona)
vivienda_modelo_apart$estrato <- as.factor(vivienda_modelo_apart$estrato)

# Ajustar el modelo de regresión lineal múltiple para apartamentos
modelo_apart <- lm(preciom ~ areaconst + estrato + banios + habitaciones + zona, data = vivienda_modelo_apart)

# Calcular resumen estadístico de los residuos
resumen_residuos_apart <- data.frame(
  Estadística = c("Mínimo", "Q1", "Mediana", "Media", "Q3", "Máximo", 
                  "Desviación Estándar", "Asimetría", "Curtosis"),
  Valor = c(
    min(residuals(modelo_apart)), 
    quantile(residuals(modelo_apart), c(0.25, 0.5, 0.75)), 
    mean(residuals(modelo_apart)), 
    max(residuals(modelo_apart)), 
    sd(residuals(modelo_apart)), 
    mean((residuals(modelo_apart) - mean(residuals(modelo_apart)))^3) / sd(residuals(modelo_apart))^3, 
    mean((residuals(modelo_apart) - mean(residuals(modelo_apart)))^4) / sd(residuals(modelo_apart))^4 - 3
  )
)

# Mostrar tabla formateada
kable(resumen_residuos_apart, format = "markdown", align = "c", caption = "Resumen Estadístico de los Residuos (Solo Apartamentos)")
Resumen Estadístico de los Residuos (Solo Apartamentos)
Estadística Valor
Mínimo -1881.443211
Q1 -48.720427
Mediana 1.964738
Media 44.211493
Q3 0.000000
Máximo 950.876808
Desviación Estándar 129.804940
Asimetría 0.291357
Curtosis 18.885811

Supuesto de la mediana de los errores es siempre 0

# Calcular la mediana de los residuos del modelo ajustado para apartamentos
mediana_residuos_apart <- median(residuals(modelo_apart))

# Mostrar el resultado
print(paste("Mediana de los residuos (Solo Apartamentos):", round(mediana_residuos_apart, 4)))
## [1] "Mediana de los residuos (Solo Apartamentos): 1.9647"

Interpretando los resultados, concluimos que el supuesto no se cumple, la mediana de los residuos es 1.9647, que es distinto de Cero; sin embargo, no es una desviación mínima que nos indica que nuestro modelo presenta un pequeño sesgo subestimando el precio de los apartamentos.

Supuesto de independencia de los errores

# Cargar la librería necesaria
library(lmtest)

# Realizar la prueba de Durbin-Watson para el modelo de apartamentos
dw_test_apart <- dwtest(modelo_apart)

# Mostrar los resultados
print(dw_test_apart)
## 
##  Durbin-Watson test
## 
## data:  modelo_apart
## DW = 1.7647, p-value < 2.2e-16
## alternative hypothesis: true autocorrelation is greater than 0

Validando los resultados, obtuvimos que el supuesto no se cumple, aplicando el test, el estadístico DW = 1.76, cercano a 2 que nos indica que existe una leve correlación; sin embargo, contrastando con el resultado de p-valor < 2.2e-16, es muy cercano a 0, confirmando la hipótesis alternativa (existe correlación positiva en los residuos)

Supuesto de Varianza constante de los errores (Homocedasticidad)

# Cargar la librería necesaria
library(lmtest)

# Realizar la prueba de Breusch-Pagan para el modelo de apartamentos
bp_test_apart <- bptest(modelo_apart)

# Mostrar los resultados
print(bp_test_apart)
## 
##  studentized Breusch-Pagan test
## 
## data:  modelo_apart
## BP = 1413.1, df = 10, p-value < 2.2e-16

De acuerdo con los resultados obtenidos, este supuesto tampoco se cumple, el modelo presenta heterocedasticidad, revisando el p-valor < 2.2e-16 es muy cercano a 0 por lo que se rechaza la hipótesis nula (los errores tienen varianza constante), es decir, que los errores no presentan una varianza constante, lo que plantea dudas sobre precisión de los coeficientes estimados.

Supuesto de normalidad de los errores

# Cargar librerías necesarias
library(nortest)  # Para Anderson-Darling test
library(ggplot2)  # Para histogramas y gráficos

# Extraer residuos del modelo de apartamentos
residuos_apart <- residuals(modelo_apart)

# Gráfico Q-Q Plot
qqnorm(residuos_apart)
qqline(residuos_apart, col = "red")

# Histograma de los residuos
ggplot(data = data.frame(residuos_apart), aes(x = residuos_apart)) +
  geom_histogram(aes(y=..density..), bins = 30, fill="blue", alpha=0.5) +
  geom_density(color="red", size=1) +
  ggtitle("Histograma de los Residuos - Apartamentos") +
  xlab("Residuos") + ylab("Densidad")

# Prueba de Kolmogorov-Smirnov
ks_test_apart <- ks.test(residuos_apart, "pnorm", mean(residuos_apart), sd(residuos_apart))
print(ks_test_apart)
## 
##  Asymptotic one-sample Kolmogorov-Smirnov test
## 
## data:  residuos_apart
## D = 0.15227, p-value < 2.2e-16
## alternative hypothesis: two-sided
# Prueba de Anderson-Darling (Mejor para muestras grandes)
ad_test_apart <- ad.test(residuos_apart)
print(ad_test_apart)
## 
##  Anderson-Darling normality test
## 
## data:  residuos_apart
## A = 214.84, p-value < 2.2e-16

validando los resultados de las pruebas de normalidad Kolmogorov-Smirnov y Anderson-Darling muestran p-valores menores a 2.2e-16, lo que indica que los residuos no siguen una distribución normal. El estadístico D en KS (0.15227) y A en AD (214.84) refuerzan esta conclusión, señalando una desviación significativa respecto a la normalidad, por otro lado, observamos el comportamiento en el gráfico y verificamos que, si bien, en la parte central lo puntos se ajustan a la linea, sugiriendo que en esta zona los datos siguen un distribución aparentemente normal, en las colas se evidencia una desviación significativa a la linea de normalidad, por lo tanto, se concluye que no existe una distribución normal en los errores del modelo.

Supuesto de no colinealidad

# Cargar la librería necesaria
library(car)

# Calcular el VIF solo para apartamentos
vivienda_modelo_apart <- vivienda_limpia %>%
  filter(tipo == "Apartamento") %>%  # Filtrar solo apartamentos
  select(preciom, areaconst, estrato, banios, habitaciones, zona) %>%  # Seleccionar variables del modelo
  na.omit()  # Eliminar valores faltantes

# Convertir variables categóricas a factores
vivienda_modelo_apart$zona <- as.factor(vivienda_modelo_apart$zona)
vivienda_modelo_apart$estrato <- as.factor(vivienda_modelo_apart$estrato)

# Ajustar el modelo de regresión lineal múltiple para apartamentos
modelo_apart <- lm(preciom ~ areaconst + estrato + banios + habitaciones + zona, data = vivienda_modelo_apart)

# Calcular el VIF
vif_values_apart <- vif(modelo_apart)

# Mostrar los resultados
print(vif_values_apart)
##                  GVIF Df GVIF^(1/(2*Df))
## areaconst    2.568904  1        1.602780
## estrato      2.545711  3        1.168516
## banios       3.070394  1        1.752254
## habitaciones 1.495524  1        1.222916
## zona         1.748648  4        1.072353

Validando este supuesto, el objetivo es verificar si se presenta colinealidad entre las variable predictoras, verificando los resultados del VF (factor de inflación de la varianza) de los errores, obtenemos que las cinco variables predicadoras presentan resultados menores a cinco, confirmando que no existe colinealidad entre las variables predictoras; por lo tanto, este es el único supuesto que se cumple.

Una vez evaluados los cinco supuestos y analizadas las métricas de desempeño, concluimos que, el modelo presenta un desempeño aceptable; sin embargo, en términos de normalidad, independencias y homocedasticidad de los errores, la mayoría de las métricas evaluadas no cumplen los criterios, para mejorar la precisión y predicción del modelo, es necesario aplicar estrategias de tratamiento a los valores atípicos e incluir otras variables predictoras que mejoren el desempeño y la confiabilidad de los resultados.

Predicción tipo apartamento

Estrato 5

# Crear un nuevo dataframe con las características del apartamento a predecir
nueva_vivienda_apart <- data.frame(
  areaconst = 300,    # Área construida
  estrato = factor(5, levels = levels(vivienda_modelo_apart$estrato)),  # Estrato 5
  banios = 3,         # Número de baños
  habitaciones = 5,   # Número de habitaciones
  zona = factor("Zona Sur", levels = levels(vivienda_modelo_apart$zona)) # Zona Sur
)

# Realizar la predicción del precio del apartamento
prediccion_precio_apart <- predict(modelo_apart, newdata = nueva_vivienda_apart)

# Mostrar el resultado de la predicción
print(paste("El precio estimado del apartamento es:", round(prediccion_precio_apart, 2), "millones de pesos"))
## [1] "El precio estimado del apartamento es: 695.85 millones de pesos"

Una vez ejecutamos el modelo, para una vivienda de acuerdo con estas características: Área construida: 300 m², estrato 5, con tres baños, cinco habitaciones y ubicado en la zona sur, obtenemos que el precio estimado es aproximadamente de 696 Millones de pesos, cabe resaltar que la variable parqueaderos no se complementó en el modelo de acuerdo con los requerimientos del ejercicio , por lo tanto, la predicción incluye esa variable.

Estrato 6

# Crear un nuevo dataframe con las características del apartamento a predecir
nueva_vivienda_apart <- data.frame(
  areaconst = 300,    # Área construida
  estrato = factor(6, levels = levels(vivienda_modelo_apart$estrato)),  # Estrato 5
  banios = 3,         # Número de baños
  habitaciones = 5,   # Número de habitaciones
  zona = factor("Zona Sur", levels = levels(vivienda_modelo_apart$zona)) # Zona Sur
)

# Realizar la predicción del precio del apartamento
prediccion_precio_apart <- predict(modelo_apart, newdata = nueva_vivienda_apart)

# Mostrar el resultado de la predicción
print(paste("El precio estimado del apartamento es:", round(prediccion_precio_apart, 2), "millones de pesos"))
## [1] "El precio estimado del apartamento es: 831.91 millones de pesos"

Una vez ejecutamos el modelo, para una vivienda de acuerdo con estas características: Área construida: 300 m², estrato 6, con tres baños, cinco habitaciones y ubicado en la zona sur, obtenemos que el precio estimado es aproximadamente de 832 Millones de pesos, cabe resaltar que la variable parqueaderos no se complementó en el modelo de acuerdo con los requerimientos del ejercicio , por lo tanto, la predicción incluye esa variable

Ofertas tipo apartamento

# Cargar librerías necesarias
library(dplyr)
library(leaflet)

# Definir el nuevo rango de precios entre 300M y 350M
precio_min <- 500  
precio_max <- 1000  

# Filtrar solo apartamentos en la Zona Sur con criterios específicos
apartamentos_filtrados <- vivienda_limpia %>%
  filter(
    zona_corr == "Zona Sur",  # Filtrar solo Zona Sur
    tipo == "Apartamento",    # Solo apartamentos
    areaconst >= 300,         # Área construida mínima de 200 m²
    estrato %in% c(5, 6),     # Estrato 4 o 5
    banios >= 3,              # Al menos 2 baños
    habitaciones >= 4,        # Al menos 4 habitaciones
    parqueaderos >= 2,        # Al menos 1 parqueadero
    preciom >= precio_min & preciom <= precio_max  # Rango de precios de 300M a 350M
  )

# Verificar si hay datos después del filtro
print(nrow(apartamentos_filtrados))  # Mostrar la cantidad de apartamentos que cumplen con los criterios
## [1] 7
# Definir iconos para las banderas de estrato 4 (rojo) y estrato 5 (verde)
iconos_bandera <- awesomeIcons(
  icon = 'flag',
  iconColor = 'white',
  library = 'fa',
  markerColor = ifelse(apartamentos_filtrados$estrato == 5, "red", "green")
)

# Crear mapa interactivo con los apartamentos filtrados
leaflet(apartamentos_filtrados) %>%
  addTiles() %>%
  setView(lng = -76.5, lat = 3.45, zoom = 12) %>%
  addAwesomeMarkers(
    lng = ~longitud, lat = ~latitud,
    icon = iconos_bandera,
    popup = ~paste("Zona:", zona_corr, "<br>",
                   "Estrato:", estrato, "<br>",
                   "Precio:", preciom, "M", "<br>",
                   "Área:", areaconst, "m²", "<br>",
                   "Baños:", banios, "<br>",
                   "Habitaciones:", habitaciones, "<br>",
                   "Parqueaderos:", parqueaderos)
  ) %>%
  addLegend(
    position = "topright",
    colors = c("red", "green"),
    labels = c("Estrato 5", "Estrato 6"),
    title = "Apartamentos en Zona Sur (700M - 850M)"
  )
# Cargar librerías necesarias
library(dplyr)
library(DT)

# Calcular el precio por metro cuadrado para apartamentos
apartamentos_filtrados <- apartamentos_filtrados %>%
  mutate(precio_m2 = preciom / areaconst) %>% 
  arrange(precio_m2)  # Ordenar de menor a mayor precio por metro cuadrado

# Verificar cuántos apartamentos hay en el mapa
print(paste("Número total de apartamentos identificados:", nrow(apartamentos_filtrados)))
## [1] "Número total de apartamentos identificados: 7"
# Mostrar todos los apartamentos identificados en la tabla interactiva
datatable(apartamentos_filtrados, 
          options = list(scrollX = TRUE, pageLength = nrow(apartamentos_filtrados)), 
          caption = "Todos los Apartamentos Identificados en la Zona Sur")
# Identificar las cinco mejores ofertas (menor precio por m²)
mejores_ofertas <- apartamentos_filtrados %>%
  head(5)  # Tomar los 5 con menor precio por m²

# Mostrar las cinco mejores ofertas en una tabla interactiva
datatable(mejores_ofertas, 
          options = list(scrollX = TRUE, pageLength = 5), 
          caption = "Las 5 Mejores Ofertas en la Zona Sur (Basado en Precio por m²)")

De acuerdo con los requerimientos del cliente, realizamos una búsqueda exhaustiva de las ofertas disponibles de apartamentos en el mercado inmobiliario de Cali, inicialmente aplicamos los siguientes criterios de búsqueda:

  • Ubicación en la zona sur de Cali
  • Rango de precios entre 500 - 1.000 Millones de pesos.
  • Área contruida desde 300 m².
  • Estrato 5 o 6.
  • Con cuatro habitaciones o más.
  • Con al menos dos parqueadero.
  • Con tres baños o más.

Como resultado obtuvimos una litado preliminar de 7 inmueble que cumplen algunas de las condiciones, analizamos cada un de las propiedades y para realizar la oferta definimos criterios de valorización, ubicación y relación precio metro cuadrado, una vez analizados estos criterios se determina que las cinco ofertas más atractivas por su costo y valorización con las que se presentan en la tabla.