En la compañia de inmuebles C&A , Maria, su fundadora requiere apoyo de analisis sobre dos solicitudes de vivienda en la ciudad de Cali. se nos pide realizar 6 solicitudes a modo de informe en donde esten las recomendaciones de dichas viviendas y se debe adjuntar las estimaciones, validaciones y comparaciones de los modelos requeridos.
las solicitudes son las siguientes
| Tipo | area_construida | parqueaderos | banos | habitaciones | estrato1 | estrato2 | zona | credito_preaprobado_mill |
|---|---|---|---|---|---|---|---|---|
| Casa | 200 | 1 | 2 | 4 | 4 | 5 | Zona Norte | 350 |
| Apartamento | 300 | 3 | 3 | 5 | 5 | 6 | Zona Sur | 850 |
las consultas son las siguientes:
Filtros sobre las zonas y visualización
Correlación entre las variables
Estimación de un modelo de regresión lineal multiple
Validación del modelo planteado
Predicción de las viviendas con base en lo buscado
Realizar propuestas que se ajusten al presupuesto del pre aprobado
Se nos va ha compartir una base de datos con la información de 3 meses del sector, llamada vivienda
#data("vivienda")
ruta_del_archivo <- "/Users/Julian/Documents/Material Estudio/MDS Javeriana/Sem 2/[006]EstadTomaDeci/Act1/vivienda.xlsx"
data <- readxl::read_excel(ruta_del_archivo)
A continuación realizaremos un entendimiento de los datos suministrados
kable(do.call(data.frame, head(data)),
format = "markdown",
caption = "**Tabla de los primeros registros base vivienda**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| 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 |
Observamos que tenemos 12 campos que constituyen los campos de la base, alguno de ellos categoricos y otros númericos.
resumen_data <- list(
num_filas = nrow(data),
num_col= ncol(data),
vacios_totales = sum(is.na(data)),
porc_vacios = sprintf('%.2f%%',sum(is.na(data))/(nrow(data)*ncol(data))*100),
filas_con_mas_un_vacio = sum(apply(is.na(data), 1, any)),
filas_all_vacios = sum(apply(is.na(data), 1, all)),
col_all_vacios = sum(colSums(is.na(data)) == nrow(data)),
filas_duplicadas= nrow(data[duplicated(data), ])
)
kable(do.call(data.frame, resumen_data),
format = "markdown",
caption = "**Tabla Resumen de dataset antes de tratamientos**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| num_filas | num_col | vacios_totales | porc_vacios | filas_con_mas_un_vacio | filas_all_vacios | col_all_vacios | filas_duplicadas |
|---|---|---|---|---|---|---|---|
| 8322 | 13 | 4275 | 3.95% | 3514 | 2 | 0 | 1 |
Se ha identificado la existencia de dos filas vacías y nueve registros duplicados en el conjunto de datos. Estos elementos, por varias razones, no parecen ser esenciales para los objetivos de este estudio:
Procedemos a observar un analisis enfocado a los valores faltantes
grafico_vacios <-md.pattern(data, rotate.names = TRUE)
Se identifica que los registros vacios a eliminar no tienen valor en el
campo ‘id’
Iniciamos tratamiento de los registros que no vamos a requerir
#Se remueve los registros llenos de campos sin datos
data <- data[rowSums(is.na(data)) < ncol(data), ]
#Se remueve los duplicados
data <- data[!duplicated(data), ]
#Se remueve el campo que solo tiene el precio (se saca por id, es mas facil de identificar)
data <- data[!is.na(data$id), ]
#Removiendo columnas no requeridas
data <- select(data,-piso)
Debido a que contamos con solo el campo de zonas y barrios para responder a las necesidades del cliente frente a sus solicitudes en terminos georeferencias (con latitud y longitud , identificar zonas de la ciudad es complejo), procedemos a analizar :
lista_graficos_cuali <- list()
for (col in c('barrio','zona')) {
frecuencia_palabras_temp = aggregate(data[[col]], list(data[[col]]), FUN=length)
colnames(frecuencia_palabras_temp)=c("Palabra","Frecuencia")
lista_graficos_cuali[[col]] <- as.data.frame(frecuencia_palabras_temp)
}
frecuencia_palabras_temp = lista_graficos_cuali[['barrio']]
wordcloud(words = frecuencia_palabras_temp$Palabra,
freq = frecuencia_palabras_temp$Frecuencia,
min.freq = 2,
max.words = 60,
colors = c("#515354","#50AC05","#0576FF", "#FF0576"),
random.order = F,
random.color = F,
scale = c(4 ,0.5),
rot.per = 0.25)
Se puede observar que hay barrios con una falta de estandarización en los datos. Ejemplos de esto incluyen el uso inconsistente de mayúsculas, abreviaciones, puntuaciones y símbolos, lo que indica problemas en el manejo de la normalización de la información.
se plantea la siguiente función para normalizar los datos
reemplazar_caracteres <- function(texto) {
#Función empleada para hacer limpieza de los caracteres encontrados en la base de datos
#**NOTA**: No se trata de una función genérica, sino de una función personalizada
#construida específicamente para estos datos.
#
#@texto(string): el texto que vamos a procesar
#@return(string): el texto previamente tratado
caractares_remplazar <- c('Á','É','Í','Ó','Ú','√©','√∫')
caracteres_remplazado <- c('A','E','I','O','U','E','U')
texto <- toupper(texto)
for (i in 1:length(caractares_remplazar)){
texto <- gsub(caractares_remplazar[i],caracteres_remplazado[i],texto)
}
return (texto)
}
frecuencia_palabras_temp = lista_graficos_cuali[['zona']]
wordcloud(words = frecuencia_palabras_temp$Palabra,
freq = frecuencia_palabras_temp$Frecuencia,
min.freq = 2,
max.words = 60,
colors = c("#515354","#50AC05","#0576FF", "#FF0576"),
random.order = F,
random.color = F,
scale = c(4 ,0.5),
rot.per = 0.25)
Se observa que el nombre de las zonas estas estandarizado
Con base en el listado oficial de barrios de la gobernaciona de Cali-Planeación, se realiza la siguiente depuración de datos .
nota: Por motivos practicos de informe, solo se muestran los primeros 10 registros en el reporte:
knitr::kable(head(datos_barrio_zona_depurados, 10),
format = "markdown",
caption = "**Tabla Asociación de nombres entre las zonas de los datos y el mapa de planeación (10 registros)**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| zona | barrio | zona_revisada |
|---|---|---|
| Zona Norte | acopi | Zona Norte |
| Zona Sur | acopi | Zona Norte |
| Zona Norte | alameda del río | Zona Norte |
| Zona Norte | alameda del rio | Zona Norte |
| Zona Norte | alamos | Zona Norte |
| Zona Norte | alcazares | Zona Norte |
| Zona Norte | altos de menga | Zona Norte |
| Zona Sur | arboleda | Zona Norte |
| Zona Norte | barranquilla | Zona Norte |
| Zona Norte | berlin | Zona Norte |
La idea es comparar con la asociació entre zona y barrio y cruzarlo con esta depuración de los datos que se realizó manualmente. permitiendo tener una nueva zona sugerida frente a barrios que pertenecen a la zona norte y sur de la ciudad.
Con base en el mapa de Cali desde la gobernaciona de Cali-Planeación. se realiza la siguiente asociación entre zonas y comunas con base en poligonos (cuadrados)de coordenadas observadas en el mapa
df_asociacion <- data.frame(
'Zonas Planeacion' = c('Oriente', 'Sur', 'Norte', 'Occidente', 'Centro geografico y Centro historico Comercial'),
'Zona C&C' = c('Zona Oriente', 'Zona Sur', 'Zona Norte', 'Zona Oeste', 'Zona Centro')
)
knitr::kable(df_asociacion,
format = "markdown",
caption = "**Tabla Asoacion de nombres entre los datos y el mapa de planeación**",
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| Zonas.Planeacion | Zona.C.C |
|---|---|
| Oriente | Zona Oriente |
| Sur | Zona Sur |
| Norte | Zona Norte |
| Occidente | Zona Oeste |
| Centro geografico y Centro historico Comercial | Zona Centro |
A partir de esta asociación y las coordenadas en grados observados en el mapa, podemos determinar unos poligonos (cuadrados) para determinar si dada una coordenada aproximadamente pueda estar dentro de la zona en mención. A continuación se enseña coordenadas (las esquinas superior-izquierda e inferior-derecha de los cuadrados de área) que permitirán tener una aproximación:
coordenadas_norte = list(
c(c(3.454564, -76.558411),c( 3.446252, -76.543127)),#comunda 2
c(c(3.462423, -76.543127),c( 3.448916, -76.537159)),#comunda 2
c(c(3.495136, -76.537159),c( 3.452614, -76.534739)),#comunda 2
c(c(3.495136, -76.534739),c( 3.455389, -76.524408)),#comunda 2
c(c(3.495136, -76.524408),c(3.461866, -76.496936)), #comunda 2
c(c(3.488201, -76.520113),c(3.454628, -76.495372)), #comunda 4
c(c(3.485687, -76.505831),c(3.461366, -76.485033)), #comunda 5
c(c(3.503089, -76.503122),c(3.466336, -76.476343)) #comunda 6
)
coordenadas_sur = list(
c(c(3.411852, -76.548099),c(3.362371, -76.513402)), #comunda 17
c(c(3.373027, -76.555002),c(3.209006, -76.519925)) #comunda 22
)
Se plantean las siguientes funciones auxiliares que permitirán identificar si los registros de un dataframe pertenecen a dichas áreas sugeridas .Nota Entendemos que son una aproximación de los datos, pero nos permitiran descartar viviendas en ubicaciones muy atipicas.
crear_cuadrado <- function(coordenadas) {
#Función empleadas para convertir las cordenadas superior-izquierda e inferiro-derecha en un listado
#de coordenadas 'x' y 'y' que representan las cuatro esquinas del cuadrado que contiene la comuna
x1 <- coordenadas[1]
y1 <- coordenadas[2]
x2 <- coordenadas[3]
y2 <- coordenadas[4]
x <- c(x1, x1 , x2, x2)
y <- c(y1, y2, y1 , y2)
return(list(x = x, y = y))
}
esta_dentro_cuadrado <- function(coordenada, cuadrado) {
#Función empleada para determinar si dada una coordenada se encuentra dentro de un cuadrado ('región de la comuna')
#dado por 4 coordenadas
x <- coordenada[1]
y <- coordenada[2]
cuadrado_x <- cuadrado$x
cuadrado_y <- cuadrado$y
dentro_x <- x >= min(cuadrado_x) & x <= max(cuadrado_x)
dentro_y <- y >= min(cuadrado_y) & y <= max(cuadrado_y)
return(dentro_x & dentro_y)
}
crear_lista_cuadrados <- function(coordenadas_norte) {
#Función empleada para crear el listado de cuadrados que vamos a emplear con base en una lista de coordenadas
lista_cuadrados <- list()
for (coords in coordenadas_norte) {
cuadrado <- crear_cuadrado(coords)
lista_cuadrados <- c(lista_cuadrados, list(cuadrado))
}
return(lista_cuadrados)
}
verificar_puntos_en_cuadrados <- function(df, lista_cuadrados) {
# Función para verificar si cada punto está dentro de al menos un cuadrado
#me retorno todo un vector de respuestas boolean
puntos_dentro <- logical(nrow(df))
for (j in 1:nrow(df)) {
for (i in 1:length(lista_cuadrados)) {
cuadrado <- lista_cuadrados[[i]]
coordenada <- c(df$latitud[j], df$longitud[j])
puntos_dentro[j] <- puntos_dentro[j] | esta_dentro_cuadrado(coordenada, cuadrado)
}
}
return(puntos_dentro)
}
Iniciamos análisis de la consulta 1 de vivienda
unique_values <- unique(data$zona)
la_zona = vivienda_1$zona
datos_filtrados <- subset(data, tipo == vivienda_1$Tipo & zona == la_zona)
#tratamiento por nombre de barrio-zona
datos_filtrados = merge(datos_filtrados, datos_barrio_zona_depurados, by = c("zona", "barrio"), all.x = TRUE)
#tratamiento por latitud y longitud
lista_cuadrados <- crear_lista_cuadrados(coordenadas_norte)
puntos_validados <- verificar_puntos_en_cuadrados(datos_filtrados, lista_cuadrados)
datos_filtrados$en_poligono_de_zona <- puntos_validados
datos_filtrados$zona_revisada[is.na(datos_filtrados$zona_revisada)] <- "Fuera de zonas"
#tratamiento en nombre de los barrios
datos_filtrados$barrio <- sapply(datos_filtrados$barrio, reemplazar_caracteres)
knitr::kable(head(datos_filtrados, 3),
format = "markdown",
caption = glue("**Tabla Resumen de dataset de la zona {la_zona} **"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| zona | barrio | id | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | longitud | latitud | zona_revisada | en_poligono_de_zona |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Zona Norte | ACOPI | 4460 | 4 | 625 | 355 | 3 | 5 | 5 | Casa | -76.53179 | 3.40590 | Zona Norte | FALSE |
| Zona Norte | ACOPI | 604 | 5 | 520 | 455 | NA | 5 | 4 | Casa | -76.49966 | 3.46284 | Zona Norte | TRUE |
| Zona Norte | ACOPI | 4057 | 6 | 750 | 445 | NA | 7 | 6 | Casa | -76.52950 | 3.38527 | Zona Norte | FALSE |
Iniciaremos con la comparación de ambos acercamientos de acercamientos de zonas de Cali-Norte
td <-reshape2::dcast(datos_filtrados, zona_revisada ~ en_poligono_de_zona, value.var = "preciom", fun.aggregate = length)
knitr::kable(td,
format = "markdown",
caption = glue("**Tabla Resumen de comparación de acercamientos de zonas de Cali-Norte**"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| zona_revisada | FALSE | TRUE |
|---|---|---|
| Fuera de zonas | 23 | 7 |
| Zona Norte | 163 | 526 |
| Zona Sur | 2 | 1 |
Se realizan las siguientes conclusiones a partir de la tabla de comparación de la zona Norte:
Problemas en nombres de barrios con relación a la zonas que no corresponden a la realidad de la ciudad.
Problemas en coordenadas en latitud y longitud que no corresponden a la realidad de la ciudad
Se observan 526 registros que cumplen con el criterio de tratamiento tanto por nombre del barrio, como por longitud y latitud.
colores <- colorFactor(palette = "Set1", domain = datos_filtrados$zona_revisada)
mapa_por_barrio<-leaflet(datos_filtrados) %>%
addTiles() %>%
addCircleMarkers(lng = ~longitud,
lat = ~latitud,
radius = 3,
color = ~colores(zona_revisada),
popup = ~paste("ZonaRevisada:", zona_revisada,'Barrio:',barrio)) %>%
addLegend("bottomright", pal = colores, values = ~zona_revisada,
title = "zona_revisada",
opacity = 1)
mapa_por_barrio
En el anterior mapa, vemos que las viviendas que estas dentro de la categoria de ‘Zona Norte’ se encuentran muy dispersos a lo largo de la ciudad, y apoyados desde la revisión de Barrios, encontramos que son varios que continuan correspondiendoa zonas que no son las esperadas.
colores <- colorFactor(palette = "Set1", domain = datos_filtrados$en_poligono_de_zona)
mapa_por_poligono<-leaflet(datos_filtrados) %>%
addTiles() %>%
addCircleMarkers(lng = ~longitud,
lat = ~latitud,
radius = 3,
color = ~colores(en_poligono_de_zona),
popup = ~paste('Barrio:',barrio, 'Latitud:',latitud, 'Longitud:',longitud )) %>%
addLegend("bottomright", pal = colores, values = ~en_poligono_de_zona,
title = "en_poligono_de_zona",
opacity = 1)
mapa_por_poligono
Con base en las coordenadas latitud y longitud, observamos que se logro tener un buen acercamiento a lo reflejado en la ralidad de las comunas a la ‘zona Norte’. no obstante, debemos tener encuenta el barrios especificado en el registro apra garantizar la calidad del dato.
datos_filtrados$la_zona <- ifelse(datos_filtrados$zona_revisada == 'Zona Norte' & datos_filtrados$en_poligono_de_zona == TRUE,
'La Zona Norte', 'Otra Zona')
colores <- colorFactor(palette = "Set1", domain = datos_filtrados$la_zona)
mapa_revisado<-leaflet(datos_filtrados) %>%
addTiles() %>%
addCircleMarkers(lng = ~longitud,
lat = ~latitud,
radius = 3,
color = ~colores(la_zona),
popup = ~paste('Barrio:',barrio)) %>%
addLegend("bottomright", pal = colores, values = ~la_zona,
title = "la_zona",
opacity = 1)
mapa_revisado
Teniendo encuenta los tratamientos anteriormente planteados para la calidad del dato (por coordenada y por nombre del barrio). se observa que el mapa corresponde a cada punto en nombre de barrio y latitud y longitud de la ‘Zona norte’
df_norte <- subset(datos_filtrados, la_zona == 'La Zona Norte')
df_norte <- select(df_norte, -zona,-zona_revisada,-en_poligono_de_zona,-la_zona,-tipo)
Iniciamos análisis de la consulta 2 de vivienda
unique_values <- unique(data$zona)
la_zona = vivienda_2$zona
datos_filtrados <- subset(data, tipo == vivienda_2$Tipo & zona == la_zona)
#tratamiento por nombre de barrio-zona
datos_filtrados = merge(datos_filtrados, datos_barrio_zona_depurados, by = c("zona", "barrio"), all.x = TRUE)
#tratamiento por latitud y longitud
lista_cuadrados <- crear_lista_cuadrados(coordenadas_sur)
puntos_validados <- verificar_puntos_en_cuadrados(datos_filtrados, lista_cuadrados)
datos_filtrados$en_poligono_de_zona <- puntos_validados
datos_filtrados$zona_revisada[is.na(datos_filtrados$zona_revisada)] <- "Fuera de zonas"
#tratamiento en nombre de los barrios
datos_filtrados$barrio <- sapply(datos_filtrados$barrio, reemplazar_caracteres)
knitr::kable(head(datos_filtrados, 3),
format = "markdown",
caption = glue("**Tabla Resumen de dataset de la zona {la_zona} **"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| zona | barrio | id | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | longitud | latitud | zona_revisada | en_poligono_de_zona |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Zona Sur | ACOPI | 5098 | 4 | 290 | 96 | 1 | 2 | 3 | Apartamento | -76.53464 | 3.44987 | Zona Norte | FALSE |
| Zona Sur | AGUABLANCA | 698 | 3 | 78 | 40 | 1 | 1 | 2 | Apartamento | -76.50100 | 3.40000 | Fuera de zonas | FALSE |
| Zona Sur | AGUACATAL | 8199 | 6 | 875 | 194 | 2 | 5 | 3 | Apartamento | -76.55700 | 3.45900 | Fuera de zonas | FALSE |
Iniciaremos con la comparación de ambos acercamientos de acercamientos de zonas de Cali-Sur
td <-reshape2::dcast(datos_filtrados, zona_revisada ~ en_poligono_de_zona, value.var = "preciom", fun.aggregate = length)
knitr::kable(td,
format = "markdown",
caption = glue("**Tabla Resumen de comparación de acercamientos de zonas de Cali-Sur**"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| zona_revisada | FALSE | TRUE |
|---|---|---|
| Fuera de zonas | 407 | 195 |
| Zona Norte | 14 | 0 |
| Zona Sur | 341 | 1830 |
Se realizan las siguientes conclusiones a partir de la tabla de comparación de la zona Sur:
Problemas en nombres de barrios con relación a la zonas que no corresponden a la realidad de la ciudad.
Problemas en coordenadas en latitud y longitud que no corresponden a la realidad de la ciudad
Se observan 1830 registros que cumplen con el criterio de tratamiento tanto por nombre del barrio, como por longitud y latitud.
colores <- colorFactor(palette = "Set1", domain = datos_filtrados$zona_revisada)
mapa_por_barrio<-leaflet(datos_filtrados) %>%
addTiles() %>%
addCircleMarkers(lng = ~longitud,
lat = ~latitud,
radius = 3,
color = ~colores(zona_revisada),
popup = ~paste("ZonaRevisada:", zona_revisada,'Barrio:',barrio)) %>%
addLegend("bottomright", pal = colores, values = ~zona_revisada,
title = "zona_revisada",
opacity = 1)
mapa_por_barrio
En el anterior mapa, vemos que las viviendas que estas dentro de la categoria de ‘Zona Sur’ se encuentran muy dispersos a lo largo de la ciudad, y apoyados desde la revisión de Barrios, encontramos que son varios que continuan correspondiendoa zonas que no son las esperadas.
colores <- colorFactor(palette = "Set1", domain = datos_filtrados$en_poligono_de_zona)
mapa_por_poligono<-leaflet(datos_filtrados) %>%
addTiles() %>%
addCircleMarkers(lng = ~longitud,
lat = ~latitud,
radius = 3,
color = ~colores(en_poligono_de_zona),
popup = ~paste('Barrio:',barrio, 'Latitud:',latitud, 'Longitud:',longitud )) %>%
addLegend("bottomright", pal = colores, values = ~en_poligono_de_zona,
title = "en_poligono_de_zona",
opacity = 1)
mapa_por_poligono
Con base en las coordenadas latitud y longitud, observamos que se logro tener un buen acercamiento a lo reflejado en la ralidad de las comunas a la ‘zona Sur’. no obstante, debemos tener en cuenta el barrios especificado en el registro apra garantizar la calidad del dato.
datos_filtrados$la_zona <- ifelse(datos_filtrados$zona_revisada == 'Zona Sur' & datos_filtrados$en_poligono_de_zona == TRUE,
'La Zona Sur', 'Otra Zona')
colores <- colorFactor(palette = "Set1", domain = datos_filtrados$la_zona)
mapa_revisado<-leaflet(datos_filtrados) %>%
addTiles() %>%
addCircleMarkers(lng = ~longitud,
lat = ~latitud,
radius = 3,
color = ~colores(la_zona),
popup = ~paste('Barrio:',barrio)) %>%
addLegend("bottomright", pal = colores, values = ~la_zona,
title = "la_zona",
opacity = 1)
mapa_revisado
Teniendo encuenta los tratamientos anteriormente planteados para la calidad del dato (por coordenada y por nombre del barrio). se observa que el mapa corresponde a cada punto en nombre de barrio y latitud y longitud de la ‘Zona sur’
df_sur <- subset(datos_filtrados, la_zona == 'La Zona Sur')
df_sur <- select(df_sur, -zona,-zona_revisada,-en_poligono_de_zona,-la_zona,-tipo)
Antes de realizar la correlación entre variables, debemos normalizar llos datos para obtener una mejor correlación, para ello procedemos a osbervar al dispersión de dos campos que tienen la escala muy diferente al resto de los datos
plot(df_norte$preciom, df_norte$areaconst,
xlab = "preciom", ylab = "areaconst",
main = "Gráfico de Dispersión de preciom vs areaconst")
La dispersión de los datos, nos muestra que uan normalización con log podria ser la mas adecuada.
Frente al campo parqueadero, requerimos tratar los campos vacios, y al tener filtrados los datos, es un buen momento de tratarlos con la moda
Mode <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
moda <- Mode(na.omit(df_norte$parqueaderos))
Realizamos la normalización de los datos
df_norte$parqueaderos[is.na(df_norte$parqueaderos)] <- moda
df_norte$preciomlog=log(df_norte$preciom)
df_norte$areaconstlog=log(df_norte$areaconst)
Realizamos la construcción de la matriz de correlación
datos_normalizados <- select(df_norte,-barrio, -id,-longitud,-latitud, - areaconst, - preciom) %>%
scale()
r<- cor(datos_normalizados) #datos_normalizados
p<-ggcorrplot(r,type="lower",
title="Correlaciones",
colors=c("red","yellow","blue"),
outline.color="black", ggtheme = theme_test() + theme(text = element_text(size = 7)))
plotly::ggplotly(p)
Se observa una alta correlacion entre el precio y el área de contruicción en su transformación de logaritmo (0.84). le siguen la relación de los campos anteriormente mencionadoscon el campo estrato (0.7 y 0.6 respectivamente).
Repetimos la comparación entre los campos con mayor cambio de escala
plot(df_sur$preciom, df_sur$areaconst,
xlab = "preciom", ylab = "areaconst",
main = "Gráfico de Dispersión de preciom vs areaconst")
La dispersión de los datos, nos muestra que uan normalización con log podria ser la mas adecuada.
Frente al campo parqueadero, requerimos tratar los campos vacios, y al tener filtrados los datos, es un buen momento de tratarlos con la moda
Mode <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}
moda <- Mode(na.omit(df_sur$parqueaderos))
Realizamos la normalización de los datos
df_sur$parqueaderos[is.na(df_sur$parqueaderos)] <- moda
df_sur$preciomlog=log(df_sur$preciom)
df_sur$areaconstlog=log(df_sur$areaconst)
Realizamos la construcción de la matriz de correlación
datos_normalizados <- select(df_sur,-barrio, -id,-longitud,-latitud, - areaconst, - preciom) %>%
scale()
r<- cor(datos_normalizados) #datos_normalizados
p<-ggcorrplot(r,type="lower",
title="Correlaciones",
colors=c("red","yellow","blue"),
outline.color="black", ggtheme = theme_test() + theme(text = element_text(size = 7)))
plotly::ggplotly(p)
Se observa una alta correlacion entre el precio y el área de contruicción en su transformación de logaritmo (0.88). le siguen la relación de los campos anteriormente mencionadoscon el campo estrato (0.76 y 0.65 respectivamente). tambien, vemos que tenemos una relación itneresante entre el precio, área de contrucción con el número de baños (0.76) y número de parqueaderos (0.71 y 0.69 respectivamente).
Primer modelo empleando todos los campos
modelo_norte <- lm(preciomlog ~ banios + parqueaderos + areaconstlog + estrato, data = df_norte)
# Mostrar un resumen del modelo
summary(modelo_norte)
##
## Call:
## lm(formula = preciomlog ~ banios + parqueaderos + areaconstlog +
## estrato, data = df_norte)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.76287 -0.16354 -0.01841 0.14962 1.06938
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.150838 0.106096 20.273 < 2e-16 ***
## banios 0.053171 0.009733 5.463 7.26e-08 ***
## parqueaderos 0.029325 0.010111 2.900 0.00389 **
## areaconstlog 0.519273 0.025779 20.143 < 2e-16 ***
## estrato 0.169054 0.014945 11.312 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2597 on 521 degrees of freedom
## Multiple R-squared: 0.7872, Adjusted R-squared: 0.7855
## F-statistic: 481.7 on 4 and 521 DF, p-value: < 2.2e-16
Vemos que el modelo inicial reporta un buen \(R^2\) de 0.7855, en donde todos los campos y el intercepto tener un buen grado de significaciía con excepción de parqueaderos.
Se desea mejorar elmodelo, para ello, empleamos dummy en campos caegoricos, es por ello que el estrato debemos pasarlo a olumnas por todos los valores presentes en la base de datos
datos_con_ficticias_norte <- dummy_cols(df_norte, select_columns = c("estrato"))
modelo_nortev2 <- lm(preciomlog ~ banios + parqueaderos + areaconstlog + estrato_3+ estrato_4+ estrato_5+ estrato_6, data = datos_con_ficticias_norte)
summary(modelo_nortev2)
##
## Call:
## lm(formula = preciomlog ~ banios + parqueaderos + areaconstlog +
## estrato_3 + estrato_4 + estrato_5 + estrato_6, data = datos_con_ficticias_norte)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.71911 -0.16442 -0.01321 0.14242 1.01722
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.204035 0.146692 21.842 < 2e-16 ***
## banios 0.051220 0.009746 5.256 2.16e-07 ***
## parqueaderos 0.030534 0.010059 3.036 0.00252 **
## areaconstlog 0.511552 0.025867 19.776 < 2e-16 ***
## estrato_3 -0.527045 0.059078 -8.921 < 2e-16 ***
## estrato_4 -0.269958 0.056072 -4.814 1.94e-06 ***
## estrato_5 -0.173277 0.052738 -3.286 0.00109 **
## estrato_6 NA NA NA NA
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.2581 on 519 degrees of freedom
## Multiple R-squared: 0.7905, Adjusted R-squared: 0.7881
## F-statistic: 326.5 on 6 and 519 DF, p-value: < 2.2e-16
Vemos que el modelo paso a \(R^2\) del 0.7881, permitiendo tener un buen nivel de significacia en todas las variabls, cone xcepción del estrato 6 casi no rpesenta datos)y el campo parqueaderos. NOTA Recordemos que esta solicitud tiene filtrado el tipo de vivienda ‘Casa’
Primer modelo empleando todos los campos
modelo_sur <- lm(preciomlog ~ banios + parqueaderos + areaconstlog + estrato, data = df_sur)
# Mostrar un resumen del modelo
summary(modelo_sur)
##
## Call:
## lm(formula = preciomlog ~ banios + parqueaderos + areaconstlog +
## estrato, data = df_sur)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.70240 -0.11287 0.00702 0.12686 0.72325
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.481810 0.071793 20.640 <2e-16 ***
## banios 0.066757 0.007583 8.803 <2e-16 ***
## parqueaderos 0.106548 0.009448 11.277 <2e-16 ***
## areaconstlog 0.661533 0.020478 32.305 <2e-16 ***
## estrato 0.175810 0.007722 22.768 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1908 on 1825 degrees of freedom
## Multiple R-squared: 0.8569, Adjusted R-squared: 0.8566
## F-statistic: 2733 on 4 and 1825 DF, p-value: < 2.2e-16
Vemos que el modelo inicial reporta un buen \(R^2\) de 0.8566, en donde todos los campos y el intercepto tener un buen grado de significaciía con excepción de parqueaderos.
datos_con_ficticias_sur <- dummy_cols(df_sur, select_columns = c("estrato"))
modelo_surv2 <- lm(preciomlog ~ banios + parqueaderos + areaconstlog + estrato_3+ estrato_4+ estrato_5+ estrato_6, data = datos_con_ficticias_sur)
summary(modelo_surv2)
##
## Call:
## lm(formula = preciomlog ~ banios + parqueaderos + areaconstlog +
## estrato_3 + estrato_4 + estrato_5 + estrato_6, data = datos_con_ficticias_sur)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.69724 -0.11550 0.00884 0.12784 0.66446
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2.570403 0.083019 30.962 < 2e-16 ***
## banios 0.059167 0.007626 7.759 1.42e-14 ***
## parqueaderos 0.096114 0.009499 10.118 < 2e-16 ***
## areaconstlog 0.670746 0.020450 32.800 < 2e-16 ***
## estrato_3 -0.486148 0.040178 -12.100 < 2e-16 ***
## estrato_4 -0.386312 0.016465 -23.463 < 2e-16 ***
## estrato_5 -0.248100 0.014013 -17.705 < 2e-16 ***
## estrato_6 NA NA NA NA
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.1888 on 1823 degrees of freedom
## Multiple R-squared: 0.86, Adjusted R-squared: 0.8595
## F-statistic: 1866 on 6 and 1823 DF, p-value: < 2.2e-16
Se desea mejorar el modelo, para ello, empleamos dummy en campos caegoricos, es por ello que el estrato debemos pasarlo a olumnas por todos los valores presentes en la base de datos
par(mfrow = c(2, 2))
plot(modelo_nortev2)
Se observa que el modelo tiene una buena calidad de ajuste al modelo y su distribución de datos refleja ser un buen modelo.
Se observa que los residuos vs ajustados los puntos estén distribuidos aleatoriamente alrededor de la línea horizontal en 0.
En el gráfico de cuantiles normales (Normal Q-Q). los residuos siguen una distribución normal, los puntos caen aproximadamente en una línea diagonal.
En el gráfico ‘scalet location’. Los puntos están distribuidos aleatoriamente y la línea de regresión es horizontal, indica que la varianza de los residuos es constante.
En el gráfico ‘residuales vs influencia’ se observan pocos datos con alto grado de influencia = ‘leverage’
par(mfrow = c(2, 2))
plot(modelo_surv2)
Se observa que el modelo tiene una buena calidad de ajuste al modelo y su distribución de datos refleja ser un buen modelo.
Se observa que los residuos vs ajustados los puntos estén distribuidos aleatoriamente alrededor de la línea horizontal en 0.
En el gráfico de cuantiles normales (Normal Q-Q). los residuos siguen una distribución normal, los puntos caen aproximadamente en una línea diagonal.
En el gráfico ‘scalet location’. Los puntos están distribuidos aleatoriamente y la línea de regresión es horizontal, indica que la varianza de los residuos es constante.
En el gráfico ‘residuales vs influencia’ se observan pocos datos con alto grado de influencia = ‘leverage’
valor_pred_1 = predict(modelo_nortev2, data.frame(areaconstlog=log(vivienda_1$area_construida), estrato_3=0, estrato_4=1, estrato_5=0, estrato_6=0, parqueaderos=vivienda_1$parqueaderos, banios=vivienda_1$banos, habitaciones=vivienda_1$habitaciones),interval = "confidence")
knitr::kable(valor_pred_1,
format = "markdown",
caption = glue("**Tabla de predicciones de la consulta 1 de vivienda, en estrato 4 **"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| fit | lwr | upr |
|---|---|---|
| 5.777418 | 5.719815 | 5.835021 |
valor_pred_2 = predict(modelo_nortev2, data.frame(areaconstlog=log(vivienda_1$area_construida), estrato_3=0, estrato_4=0, estrato_5=1, estrato_6=0, parqueaderos=vivienda_1$parqueaderos, banios=vivienda_1$banos, habitaciones=vivienda_1$habitaciones),interval = "confidence")
knitr::kable(valor_pred_2,
format = "markdown",
caption = glue("**Tabla de predicciones de la consulta 1 de vivienda, en estrato 5**"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| fit | lwr | upr |
|---|---|---|
| 5.874099 | 5.82333 | 5.924868 |
Se observan en cada predicción 3 valores que simbolizan el mejor valor, el menor valor y el ajustado. En este caso, el precio es logaritmo, por lo que los valores ajustado darian: 322 millones y 346 millones en los estratos 4 y 5. valores que estan por debajo del credito maximo aprobado para el cliente.
valor_pred_1 = predict(modelo_surv2, data.frame(areaconstlog=log(vivienda_2$area_construida), estrato_3=0, estrato_4=0, estrato_5=1, estrato_6=0, parqueaderos=vivienda_2$parqueaderos, banios=vivienda_2$banos, habitaciones=vivienda_2$habitaciones),interval = "confidence")
knitr::kable(valor_pred_1,
format = "markdown",
caption = glue("**Tabla de predicciones de la consulta 2 de vivienda, en estrato 5 **"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| fit | lwr | upr |
|---|---|---|
| 6.613937 | 6.57082 | 6.657055 |
valor_pred_2 = predict(modelo_surv2, data.frame(areaconstlog=log(vivienda_2$area_construida), estrato_3=0, estrato_4=0, estrato_5=0, estrato_6=1, parqueaderos=vivienda_2$parqueaderos, banios=vivienda_2$banos, habitaciones=vivienda_2$habitaciones),interval = "confidence")
knitr::kable(valor_pred_2,
format = "markdown",
caption = glue("**Tabla de predicciones de la consulta 1 de vivienda, en estrato 6**"),
align = "c", escape = FALSE,
row.names = FALSE,
booktabs = TRUE)
| fit | lwr | upr |
|---|---|---|
| 6.862037 | 6.822633 | 6.901442 |
Se observan en cada predicción 3 valores que simbolizan el mejor valor, el menor valor y el ajustado. para este caso.En este caso, el precio es logaritmo, por lo que los valores ajustado darian: 745 millones y 955 millones en los estratos 5 y 6. valores que estan por para el primer caso accesible para el cliente , y para el estrato 6 casu 100 millones por debajo del maximo credito aprobado.
Para realizar una aproximacion a la vivienda ideal, vamos a realizar aproximación por distancia eucidiana y una suposición de pesos de variables
# Calcula la distancia ponderada euclidiana para cada fila
calcular_distancia_ponderada <- function(datos, objetivo, pesos) {
# Calcular la distancia ponderada euclidiana para cada fila
distancias_ponderadas <- apply(datos, 1, function(x) {
distancia <- sqrt(sum(as.numeric(pesos) * (x - objetivo)^2))
return(distancia)
})
return(distancias_ponderadas)
}
# Define los pesos para cada columna del modelo
pesos <- list(preciomlog=0,areaconstlog=0.6, estrato_3=0, estrato_4=0, estrato_5=0, estrato_6=0, parqueaderos=0.2, banios=0.1, habitaciones=0.3)
nota: observa que el precio es ponderado cero, porque debo filtrar por debajo de este precio, pero quiero resaltar las otras caracteristicas
df_norte_modelo = select(datos_con_ficticias_norte,preciomlog, areaconstlog, estrato_3, estrato_4, estrato_5, estrato_6, parqueaderos, banios, habitaciones)
objetivo <- c(preciomlog = log(vivienda_1$credito_preaprobado_mill),areaconstlog=log(vivienda_1$area_construida), estrato_3=0, estrato_4=1, estrato_5=0, estrato_6=0, parqueaderos=vivienda_1$parqueaderos, banios=vivienda_1$banos, habitaciones=vivienda_1$habitaciones)
#Revision estrato 4
distancias_ponderadas <- calcular_distancia_ponderada(subset(df_norte_modelo, estrato_4 == 1 &
preciomlog <= log(vivienda_1$credito_preaprobado_mill) &
areaconstlog >= log(vivienda_1$area_construida*0.9) &
parqueaderos>= vivienda_1$parqueaderos&
banios>= vivienda_1$banos &
habitaciones>=4), objetivo, pesos)
sugerencias <- subset(df_norte, estrato == 4 &
preciom <= vivienda_1$credito_preaprobado_mill &
areaconst >= vivienda_1$area_construida*0.9 &
parqueaderos>= vivienda_1$parqueaderos&
banios>= vivienda_1$banos &
habitaciones>=4)
# Agrega la distancia ponderada euclidiana como una nueva columna al DataFrame
sugerencias$distancia_ponderada <- distancias_ponderadas
# Ordena el DataFrame por la distancia ponderada
resp_norte <- head(sugerencias[order(distancias_ponderadas, decreasing = TRUE), ],2)
resp_norte <- rbind(resp_norte, head(sugerencias[order(distancias_ponderadas, decreasing = FALSE), ],2))
#Revision estrato 5
objetivo <- c(preciomlog = log(vivienda_1$credito_preaprobado_mill),areaconstlog=log(vivienda_1$area_construida), estrato_3=0, estrato_4=0, estrato_5=1, estrato_6=0, parqueaderos=vivienda_1$parqueaderos, banios=vivienda_1$banos, habitaciones=vivienda_1$habitaciones)
distancias_ponderadas <- calcular_distancia_ponderada(subset(df_norte_modelo, estrato_5 == 1 &
preciomlog <= log(vivienda_1$credito_preaprobado_mill) &
areaconstlog >= log(vivienda_1$area_construida*0.9) &
parqueaderos>= vivienda_1$parqueaderos&
banios>= vivienda_1$banos &
habitaciones>=as.numeric(vivienda_1$habitaciones)), objetivo, pesos)
sugerencias <- subset(df_norte, estrato == 5 &
preciom <= vivienda_1$credito_preaprobado_mill &
areaconst >= vivienda_1$area_construida*0.9 &
parqueaderos>= vivienda_1$parqueaderos&
banios>= vivienda_1$banos &
habitaciones>=as.numeric(vivienda_1$habitaciones))
# Agrega la distancia ponderada euclidiana como una nueva columna al DataFrame
sugerencias$distancia_ponderada <- distancias_ponderadas
# Ordena el DataFrame por la distancia ponderada
resp_norte <- rbind(resp_norte,head(sugerencias[order(distancias_ponderadas, decreasing = TRUE), ],2))
resp_norte <- rbind(resp_norte, head(sugerencias[order(distancias_ponderadas, decreasing = FALSE), ],2))
mapa_propuestas<-leaflet(resp_norte) %>%
addTiles() %>%
addCircleMarkers(lng=~longitud,lat=~latitud,radius=7,
popup = ~paste('Barrio:',barrio,'Area:',areaconst,'|Parqueaderos:',parqueaderos,'|baños:',banios, '|habitaciones:',habitaciones,'|estrato:',estrato,'|precio:',preciom ))
mapa_propuestas
En el mapa se plantean 8 lugares que representan 4 propuesta para cada estrato con perspectivas muy cercanas a la buscadas y una que se alejan pero maximizan el peso del peso por variable sugerido, es decir, pueden favorecer a ciertas caracteristicas. en este caso, el cliente siempre encontrará propuestas que se ajustan idelamente a su credito sin mayor problema.
df_sur_modelo = select(datos_con_ficticias_sur,preciomlog, areaconstlog, estrato_3, estrato_4, estrato_5, estrato_6, parqueaderos, banios, habitaciones)
objetivo <- c(preciomlog = log(vivienda_2$credito_preaprobado_mill),areaconstlog=log(vivienda_2$area_construida), estrato_3=0, estrato_4=0, estrato_5=1, estrato_6=0, parqueaderos=vivienda_2$parqueaderos, banios=vivienda_2$banos, habitaciones=vivienda_2$habitaciones)
sugerencias <- subset(df_sur, estrato == 5 &
preciom <= vivienda_2$credito_preaprobado_mill &
areaconst >= vivienda_2$area_construida*0.9 &
parqueaderos>= vivienda_2$parqueaderos&
banios>= vivienda_2$banos &
habitaciones>=as.numeric(vivienda_2$habitaciones))
print(nrow(sugerencias))
## [1] 0
sugerencias <- subset(df_sur, estrato == 5 &
preciom <= vivienda_2$credito_preaprobado_mill*1.1 &
areaconst >= vivienda_2$area_construida*0.9 &
parqueaderos>= vivienda_2$parqueaderos*(2/3)&
banios>= vivienda_2$banos*(2/3) &
habitaciones>=as.numeric(vivienda_2$habitaciones)*(4/5))
print(nrow(sugerencias))
## [1] 4
Se observa que para el estrato 6 , las caracteristicas buscadas con el precio sugerido, no tiene ninguna vivienda. este comportamiento era previsible, dado que los precios estimados eran muy separados al credito buscado. se realizan algunos ajustes a la propuesta frente al área de contrucción, número de habitación, parqueadeos, baños y área de contrucción (habitualmente se coloca 90% por debajo de lo buscado para garantizar la busqueda)
#Revision estrato 5
distancias_ponderadas <- calcular_distancia_ponderada(subset(df_sur_modelo, estrato_5 == 1 &
preciomlog <= log(vivienda_2$credito_preaprobado_mill) &
areaconstlog >= log(vivienda_2$area_construida*0.9) &
parqueaderos>= vivienda_2$parqueaderos*(2/3)&
banios>= vivienda_2$banos*(2/3) &
habitaciones>=as.numeric(vivienda_2$habitaciones)*(4/5)), objetivo, pesos)
# Agrega la distancia ponderada euclidiana como una nueva columna al DataFrame
sugerencias$distancia_ponderada <- distancias_ponderadas
# Ordena el DataFrame por la distancia ponderada
resp_sur <- head(sugerencias[order(distancias_ponderadas, decreasing = TRUE), ],2)
resp_sur <- rbind(resp_sur, head(sugerencias[order(distancias_ponderadas, decreasing = FALSE), ],2))
#Revision estrato 6
objetivo <- c(preciomlog = log(vivienda_2$credito_preaprobado_mill),areaconstlog=log(vivienda_2$area_construida), estrato_3=0, estrato_4=0, estrato_5=0, estrato_6=1, parqueaderos=vivienda_2$parqueaderos, banios=vivienda_2$banos, habitaciones=vivienda_2$habitaciones)
distancias_ponderadas <- calcular_distancia_ponderada(subset(df_sur_modelo, estrato_6 == 1 &
preciomlog <= log(vivienda_2$credito_preaprobado_mill) &
areaconstlog >= log(vivienda_2$area_construida*(200/300)) &
parqueaderos>= vivienda_2$parqueaderos*(2/3)&
banios>= vivienda_2$banos*(2/3) &
habitaciones>=as.numeric(vivienda_2$habitaciones)*(4/5)), objetivo, pesos)
sugerencias <- subset(df_sur, estrato == 6 &
preciom <= vivienda_2$credito_preaprobado_mill &
areaconst >= vivienda_2$area_construida*(200/300) &
parqueaderos>= vivienda_2$parqueaderos*(2/3)&
banios>= vivienda_2$banos*(2/3) &
habitaciones>=as.numeric(vivienda_2$habitaciones*(4/5)))
print(nrow(sugerencias))
## [1] 6
# Agrega la distancia ponderada euclidiana como una nueva columna al DataFrame
sugerencias$distancia_ponderada <- distancias_ponderadas
# Ordena el DataFrame por la distancia ponderada
resp_sur <- rbind(resp_sur,head(sugerencias[order(distancias_ponderadas, decreasing = TRUE), ],2))
resp_sur <- rbind(resp_sur, head(sugerencias[order(distancias_ponderadas, decreasing = FALSE), ],2))
mapa_propuestas_sur<-leaflet(resp_sur) %>%
addTiles() %>%
addCircleMarkers(lng=~longitud,lat=~latitud,radius=7,
popup = ~paste('Barrio:',barrio,'Area:',areaconst,'|Parqueaderos:',parqueaderos,'|baños:',banios, '|habitaciones:',habitaciones,'|estrato:',estrato,'|precio:',preciom ))
mapa_propuestas_sur
En el mapa se plantean 8 lugares que representan 4 propuesta para cada estrato con perspectivas muy cercanas a la buscadas y una que se alejan pero maximizan el peso del peso por variable sugerido, es decir, pueden favorecer a ciertas caracteristicas. en este caso, el cliente no encuentra ninguna propuesta que cumpla con la totalidad de los criterios sugeridos, pero encontrará propuestas que se acercan a lo buscado y que cumplan con el maximo credito que tiene disponible.