UNIVERSIDAD PONTIFICIA JAVERIANA CALI
Maestría en Ciencia de Datos
Actividad 2, Caso C&A: análisis, modelación y recomendación de viviendas
Presentado por:
Ana Isabel Giraldo Alzate
Modelos Estadísticos para la Toma de Decisiones
2026
Este informe analiza dos solicitudes de vivienda recibidas por C&A: una casa en la zona norte y un apartamento en la zona sur de Cali. El proceso integra descripción inicial, calidad de datos, imputación KNN condicionada, limpieza, comparación antes-después, mapas, correlaciones, regresión lineal múltiple, validación de supuestos, predicciones y selección de ofertas compatibles con los presupuestos aprobados.
María fundó C&A después de adquirir experiencia inmobiliaria en Cali y Bogotá. En un escenario de desaceleración de las ventas, una compañía internacional solicitó apoyo para ubicar a dos empleados con sus familias. La vivienda 1 debe ser una casa en el norte, de 200 m², un parqueadero, dos baños, cuatro habitaciones, estrato 4 o 5 y presupuesto máximo de $350 millones. La vivienda 2 debe ser un apartamento en el sur, de 300 m², tres parqueaderos, tres baños, cinco habitaciones, estrato 5 o 6 y presupuesto máximo de $850 millones.
La valoración inmobiliaria puede abordarse mediante modelos
hedónicos, en los que el precio se explica por atributos físicos,
socioeconómicos y de localización [1]. En Colombia, el análisis de precios
debe considerar la dinámica del crédito y la heterogeneidad territorial
[2, 3]. La regresión múltiple permite
cuantificar asociaciones parciales, pero requiere validar linealidad,
normalidad de residuos, homocedasticidad, independencia y
multicolinealidad [4, 5].
Los intervalos de predicción comunican la incertidumbre de una vivienda
individual [6]. El trabajo se desarrolla de forma
reproducible en R Markdown [7], con gráficos plotly
[8], mapas
leaflet [9] y datos de
paqueteMODELOS [10].
Estimar y validar modelos de regresión múltiple que permitan predecir precios y recomendar ofertas compatibles con las características y presupuestos de las dos solicitudes.
data("vivienda", package="paqueteMODELOS")
req <- c("zona","estrato","preciom","areaconst","parqueaderos","banios","habitaciones","tipo","barrio","longitud","latitud")
stopifnot(all(req %in% names(vivienda)))
vivienda0 <- vivienda |>
mutate(zona=trimws(as.character(zona)), tipo=trimws(as.character(tipo)), barrio=trimws(as.character(barrio)),
across(c(estrato,preciom,areaconst,parqueaderos,banios,habitaciones,longitud,latitud),as.numeric))
cat("Base original:", nrow(vivienda0), "filas y", ncol(vivienda0), "columnas.")
## Base original: 8322 filas y 13 columnas.
dic <- tibble::tribble(~Variable,~Descripción,~Tipo,~Unidad,
"zona","Sector de ubicación","Categórica nominal","Categoría","piso","Piso ocupado","Ordinal","Piso",
"estrato","Estrato socioeconómico","Ordinal","1 a 6","preciom","Precio ofertado","Continua","Millones COP",
"areaconst","Área construida","Continua","m²","parqueaderos","Número de parqueaderos","Discreta","Conteo",
"banios","Número de baños","Discreta","Conteo","habitaciones","Número de habitaciones","Discreta","Conteo",
"tipo","Tipo de vivienda","Nominal","Casa/Apartamento","barrio","Barrio","Nominal","Nombre",
"longitud","Coordenada longitudinal","Continua","Grados","latitud","Coordenada latitudinal","Continua","Grados")
tabla(dic,"Diccionario de datos")
| Variable | Descripción | Tipo | Unidad |
|---|---|---|---|
| zona | Sector de ubicación | Categórica nominal | Categoría |
| piso | Piso ocupado | Ordinal | Piso |
| estrato | Estrato socioeconómico | Ordinal | 1 a 6 |
| preciom | Precio ofertado | Continua | Millones COP |
| areaconst | Área construida | Continua | m² |
| parqueaderos | Número de parqueaderos | Discreta | Conteo |
| banios | Número de baños | Discreta | Conteo |
| habitaciones | Número de habitaciones | Discreta | Conteo |
| tipo | Tipo de vivienda | Nominal | Casa/Apartamento |
| barrio | Barrio | Nominal | Nombre |
| longitud | Coordenada longitudinal | Continua | Grados |
| latitud | Coordenada latitudinal | Continua | Grados |
tipo_var <- function(x) if(is.numeric(x)) "Cuantitativa" else "Categórica"
res_antes <- tibble(Variable=names(vivienda0), Tipo=vapply(vivienda0,tipo_var,character(1)),
Observaciones=vapply(vivienda0,function(x)sum(!is.na(x)),numeric(1)),
Faltantes=vapply(vivienda0,function(x)sum(is.na(x)),numeric(1))) |>
mutate(Pct_faltantes=round(100*Faltantes/nrow(vivienda0),2))
tabla(res_antes,"Variables antes de la limpieza")
| Variable | Tipo | Observaciones | Faltantes | Pct_faltantes |
|---|---|---|---|---|
| id | Cuantitativa | 8319 | 3 | 0.04 |
| zona | Categórica | 8319 | 3 | 0.04 |
| piso | Categórica | 5684 | 2638 | 31.70 |
| estrato | Cuantitativa | 8319 | 3 | 0.04 |
| preciom | Cuantitativa | 8320 | 2 | 0.02 |
| areaconst | Cuantitativa | 8319 | 3 | 0.04 |
| parqueaderos | Cuantitativa | 6717 | 1605 | 19.29 |
| banios | Cuantitativa | 8319 | 3 | 0.04 |
| habitaciones | Cuantitativa | 8319 | 3 | 0.04 |
| tipo | Categórica | 8319 | 3 | 0.04 |
| barrio | Categórica | 8319 | 3 | 0.04 |
| longitud | Cuantitativa | 8319 | 3 | 0.04 |
| latitud | Cuantitativa | 8319 | 3 | 0.04 |
vars_num <- intersect(c("preciom","areaconst","estrato","parqueaderos","banios","habitaciones"),names(vivienda0))
describir <- function(d){bind_rows(lapply(vars_num,function(v){x=d[[v]];tibble(Variable=v,N=sum(!is.na(x)),Media=mean(x,na.rm=TRUE),Mediana=median(x,na.rm=TRUE),DE=sd(x,na.rm=TRUE),Min=min(x,na.rm=TRUE),Q1=quantile(x,.25,na.rm=TRUE),Q3=quantile(x,.75,na.rm=TRUE),Max=max(x,na.rm=TRUE))}))}
tabla(describir(vivienda0),"Estadísticos antes de la limpieza")
| Variable | N | Media | Mediana | DE | Min | Q1 | Q3 | Max |
|---|---|---|---|---|---|---|---|---|
| preciom | 8320 | 433.89 | 330 | 328.65 | 58 | 220 | 540 | 1999 |
| areaconst | 8319 | 174.93 | 123 | 142.96 | 30 | 80 | 229 | 1745 |
| estrato | 8319 | 4.63 | 5 | 1.03 | 3 | 4 | 5 | 6 |
| parqueaderos | 6717 | 1.84 | 2 | 1.12 | 1 | 1 | 2 | 10 |
| banios | 8319 | 3.11 | 3 | 1.43 | 0 | 2 | 4 | 10 |
| habitaciones | 8319 | 3.61 | 3 | 1.46 | 0 | 3 | 4 | 10 |
g <- lapply(seq_along(vars_num),function(i){v<-vars_num[i];p<-ggplot(vivienda0,aes(x=.data[[v]],text=paste(v,.data[[v]])))+geom_histogram(bins=30,fill=pastel[i],color="white",na.rm=TRUE)+theme_minimal()+labs(title=paste("Distribución inicial de",v),x=v,y="Frecuencia");ggplotly(p,tooltip="text")});htmltools::tagList(g)
for(v in intersect(c("zona","tipo","piso"),names(vivienda0))){
f<-vivienda0|>mutate(Categoria=fct_explicit_na(factor(.data[[v]]),na_level="Sin dato"))|>count(Categoria,sort=TRUE)|>mutate(Porcentaje=round(100*n/sum(n),2))
print(tabla(f,paste("Frecuencias de",v)))
p<-ggplot(f,aes(reorder(Categoria,n),n,text=paste0("n: ",n,"<br>",Porcentaje,"%")))+geom_col(fill=pastel[4])+coord_flip()+theme_minimal()+labs(title=paste("Frecuencias de",v),x=NULL,y="Frecuencia");print(ggplotly(p,tooltip="text"))
}
## <table class="table table-striped table-hover table-responsive" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Frecuencias de zona</caption>
## <thead>
## <tr>
## <th style="text-align:left;"> Categoria </th>
## <th style="text-align:right;"> n </th>
## <th style="text-align:right;"> Porcentaje </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Zona Sur </td>
## <td style="text-align:right;"> 4726 </td>
## <td style="text-align:right;"> 56.79 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Zona Norte </td>
## <td style="text-align:right;"> 1920 </td>
## <td style="text-align:right;"> 23.07 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Zona Oeste </td>
## <td style="text-align:right;"> 1198 </td>
## <td style="text-align:right;"> 14.40 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Zona Oriente </td>
## <td style="text-align:right;"> 351 </td>
## <td style="text-align:right;"> 4.22 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Zona Centro </td>
## <td style="text-align:right;"> 124 </td>
## <td style="text-align:right;"> 1.49 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Sin dato </td>
## <td style="text-align:right;"> 3 </td>
## <td style="text-align:right;"> 0.04 </td>
## </tr>
## </tbody>
## </table><table class="table table-striped table-hover table-responsive" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Frecuencias de tipo</caption>
## <thead>
## <tr>
## <th style="text-align:left;"> Categoria </th>
## <th style="text-align:right;"> n </th>
## <th style="text-align:right;"> Porcentaje </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Apartamento </td>
## <td style="text-align:right;"> 5100 </td>
## <td style="text-align:right;"> 61.28 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Casa </td>
## <td style="text-align:right;"> 3219 </td>
## <td style="text-align:right;"> 38.68 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> Sin dato </td>
## <td style="text-align:right;"> 3 </td>
## <td style="text-align:right;"> 0.04 </td>
## </tr>
## </tbody>
## </table><table class="table table-striped table-hover table-responsive" style="width: auto !important; margin-left: auto; margin-right: auto;">
## <caption>Frecuencias de piso</caption>
## <thead>
## <tr>
## <th style="text-align:left;"> Categoria </th>
## <th style="text-align:right;"> n </th>
## <th style="text-align:right;"> Porcentaje </th>
## </tr>
## </thead>
## <tbody>
## <tr>
## <td style="text-align:left;"> Sin dato </td>
## <td style="text-align:right;"> 2638 </td>
## <td style="text-align:right;"> 31.70 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 02 </td>
## <td style="text-align:right;"> 1450 </td>
## <td style="text-align:right;"> 17.42 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 03 </td>
## <td style="text-align:right;"> 1097 </td>
## <td style="text-align:right;"> 13.18 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 01 </td>
## <td style="text-align:right;"> 860 </td>
## <td style="text-align:right;"> 10.33 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 04 </td>
## <td style="text-align:right;"> 607 </td>
## <td style="text-align:right;"> 7.29 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 05 </td>
## <td style="text-align:right;"> 567 </td>
## <td style="text-align:right;"> 6.81 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 06 </td>
## <td style="text-align:right;"> 245 </td>
## <td style="text-align:right;"> 2.94 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 08 </td>
## <td style="text-align:right;"> 211 </td>
## <td style="text-align:right;"> 2.54 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 07 </td>
## <td style="text-align:right;"> 204 </td>
## <td style="text-align:right;"> 2.45 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 09 </td>
## <td style="text-align:right;"> 146 </td>
## <td style="text-align:right;"> 1.75 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 10 </td>
## <td style="text-align:right;"> 130 </td>
## <td style="text-align:right;"> 1.56 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 11 </td>
## <td style="text-align:right;"> 84 </td>
## <td style="text-align:right;"> 1.01 </td>
## </tr>
## <tr>
## <td style="text-align:left;"> 12 </td>
## <td style="text-align:right;"> 83 </td>
## <td style="text-align:right;"> 1.00 </td>
## </tr>
## </tbody>
## </table>
coord_mala <- with(vivienda0,!is.na(longitud)&!is.na(latitud)&!(between(longitud,-77,-76)&between(latitud,3,4)))
cal_antes <- tibble(Indicador=c("Registros","Duplicados","Celdas faltantes","Precios no positivos","Áreas no positivas","Estratos inválidos","Coordenadas no plausibles"),Antes=c(nrow(vivienda0),sum(duplicated(vivienda0)),sum(is.na(vivienda0)),sum(vivienda0$preciom<=0,na.rm=TRUE),sum(vivienda0$areaconst<=0,na.rm=TRUE),sum(!vivienda0$estrato%in%1:6,na.rm=TRUE),sum(coord_mala)))
tabla(cal_antes,"Calidad antes de la limpieza")
| Indicador | Antes |
|---|---|
| Registros | 8322 |
| Duplicados | 1 |
| Celdas faltantes | 4275 |
| Precios no positivos | 0 |
| Áreas no positivas | 0 |
| Estratos inválidos | 3 |
| Coordenadas no plausibles | 0 |
naniar::gg_miss_var(vivienda0,show_pct=TRUE)+theme_minimal()+labs(title="Faltantes antes de la limpieza")
La imputación se restringe a predictores numéricos del modelo. No se imputan zona, tipo, barrio ni coordenadas, pues ello podría crear categorías o ubicaciones artificiales.
vars_modelo<-c("preciom","areaconst","estrato","parqueaderos","banios","habitaciones")
n_imp<-sum(is.na(vivienda0[vars_modelo]))
if(n_imp>0){set.seed(2026);vivienda_imp<-VIM::kNN(vivienda0,variable=vars_modelo,k=5,imp_var=FALSE);cat("Se imputaron",n_imp,"celdas mediante KNN, k=5.")}else{vivienda_imp<-vivienda0;cat("No fue necesario aplicar KNN.")}
## Se imputaron 1619 celdas mediante KNN, k=5.
vivienda_limpia<-vivienda_imp|>distinct()|>filter(!is.na(zona),!is.na(tipo),preciom>0,areaconst>0,estrato%in%1:6,parqueaderos>=0,banios>0,habitaciones>0)
coord_mala2<-with(vivienda_limpia,!is.na(longitud)&!is.na(latitud)&!(between(longitud,-77,-76)&between(latitud,3,4)))
cal_despues<-tibble(Indicador=cal_antes$Indicador,Despues=c(nrow(vivienda_limpia),sum(duplicated(vivienda_limpia)),sum(is.na(vivienda_limpia)),sum(vivienda_limpia$preciom<=0),sum(vivienda_limpia$areaconst<=0),sum(!vivienda_limpia$estrato%in%1:6),sum(coord_mala2)))
comparacion_calidad<-left_join(cal_antes,cal_despues,by="Indicador")|>mutate(Cambio=Despues-Antes)
tabla(comparacion_calidad,"Calidad antes y después de la limpieza")
| Indicador | Antes | Despues | Cambio |
|---|---|---|---|
| Registros | 8322 | 8243 | -79 |
| Duplicados | 1 | 0 | -1 |
| Celdas faltantes | 4275 | 2593 | -1682 |
| Precios no positivos | 0 | 0 | 0 |
| Áreas no positivas | 0 | 0 | 0 |
| Estratos inválidos | 3 | 0 | -3 |
| Coordenadas no plausibles | 0 | 0 | 0 |
res_despues<-tibble(Variable=names(vivienda_limpia),Tipo=vapply(vivienda_limpia,tipo_var,character(1)),Observaciones=vapply(vivienda_limpia,function(x)sum(!is.na(x)),numeric(1)),Faltantes=vapply(vivienda_limpia,function(x)sum(is.na(x)),numeric(1)))|>mutate(Pct_faltantes=round(100*Faltantes/nrow(vivienda_limpia),2))
tabla(res_despues,"Variables después de la limpieza")
| Variable | Tipo | Observaciones | Faltantes | Pct_faltantes |
|---|---|---|---|---|
| id | Cuantitativa | 8243 | 0 | 0.00 |
| zona | Categórica | 8243 | 0 | 0.00 |
| piso | Categórica | 5650 | 2593 | 31.46 |
| estrato | Cuantitativa | 8243 | 0 | 0.00 |
| preciom | Cuantitativa | 8243 | 0 | 0.00 |
| areaconst | Cuantitativa | 8243 | 0 | 0.00 |
| parqueaderos | Cuantitativa | 8243 | 0 | 0.00 |
| banios | Cuantitativa | 8243 | 0 | 0.00 |
| habitaciones | Cuantitativa | 8243 | 0 | 0.00 |
| tipo | Categórica | 8243 | 0 | 0.00 |
| barrio | Categórica | 8243 | 0 | 0.00 |
| longitud | Cuantitativa | 8243 | 0 | 0.00 |
| latitud | Cuantitativa | 8243 | 0 | 0.00 |
tabla(describir(vivienda_limpia),"Estadísticos después de la limpieza")
| Variable | N | Media | Mediana | DE | Min | Q1 | Q3 | Max |
|---|---|---|---|---|---|---|---|---|
| preciom | 8243 | 433.27 | 330 | 328.93 | 58 | 220 | 540 | 1999 |
| areaconst | 8243 | 174.04 | 122 | 142.32 | 30 | 80 | 227 | 1745 |
| estrato | 8243 | 4.64 | 5 | 1.03 | 3 | 4 | 5 | 6 |
| parqueaderos | 8243 | 1.73 | 1 | 1.06 | 1 | 1 | 2 | 10 |
| banios | 8243 | 3.13 | 3 | 1.41 | 1 | 2 | 4 | 10 |
| habitaciones | 8243 | 3.63 | 3 | 1.43 | 1 | 3 | 4 | 10 |
comp<-bind_rows(vivienda0|>select(all_of(vars_modelo))|>mutate(Estado="Antes"),vivienda_limpia|>select(all_of(vars_modelo))|>mutate(Estado="Después"))|>pivot_longer(-Estado,names_to="Variable",values_to="Valor")
p<-ggplot(comp,aes(Valor,fill=Estado))+geom_density(alpha=.45,na.rm=TRUE)+facet_wrap(~Variable,scales="free",ncol=2)+scale_fill_manual(values=c("Antes"=pastel[2],"Después"=pastel[3]))+theme_minimal()+labs(title="Distribuciones antes y después",x=NULL);ggplotly(p)
filtrar<-function(d,t,z)d|>filter(grepl(paste0("^",t,"$"),tipo,ignore.case=TRUE),grepl(z,zona,ignore.case=TRUE))|>mutate(estrato_f=factor(estrato))
mapa<-function(d,titulo){g<-d|>filter(between(longitud,-77,-76),between(latitud,3,4));leaflet(g)|>addProviderTiles(providers$CartoDB.Positron)|>addCircleMarkers(~longitud,~latitud,radius=4,color="#7CB9E8",fillOpacity=.75,popup=~paste0("<b>",tipo,"</b><br>",barrio,"<br>$",preciom," millones"))|>addControl(titulo,"topright")}
cor_tab<-function(d)bind_rows(lapply(vars_modelo[-1],function(v){z<-cor.test(d[[v]],d$preciom,method="spearman",exact=FALSE);tibble(Variable=v,Rho=unname(z$estimate),p_valor=z$p.value,Decision=ifelse(z$p.value<.05,"Significativa","No significativa"))}))
eda<-function(d,titulo){htmltools::tagList(lapply(seq_along(vars_modelo[-1]),function(i){v<-vars_modelo[-1][i];p<-ggplot(d,aes(.data[[v]],preciom,text=paste0("Barrio: ",barrio,"<br>Precio: ",preciom)))+geom_point(color=pastel[i],alpha=.7)+geom_smooth(method="lm",color="#355C7D",fill=pastel[5])+theme_minimal()+labs(title=paste(titulo,"Precio vs",v),y="Millones COP");ggplotly(p,tooltip="text")}))}
ajustar<-function(d)lm(preciom~areaconst+estrato_f+habitaciones+parqueaderos+banios,data=d)
diagnosticar<-function(m){r<-residuals(m);bp<-bptest(m);dw<-dwtest(m);vf<-car::vif(m);vm<-if(is.matrix(vf))max(vf[,"GVIF"]^(1/(2*vf[,"Df"])))else max(vf);pn<-if(length(r)<=5000)shapiro.test(r)$p.value else nortest::ad.test(r)$p.value;tibble(Supuesto=c("Normalidad","Homocedasticidad","Independencia","Multicolinealidad"),Prueba=c(ifelse(length(r)<=5000,"Shapiro-Wilk","Anderson-Darling"),"Breusch-Pagan","Durbin-Watson","VIF/GVIF"),p_valor=c(pn,bp$p.value,dw$p.value,NA),Resultado=c(ifelse(pn>=.05,"No se rechaza H0","Se rechaza H0"),ifelse(bp$p.value>=.05,"Varianza constante","Heterocedasticidad"),ifelse(dw$p.value>=.05,"Sin evidencia de autocorrelación","Posible autocorrelación"),ifelse(vm<5,"Aceptable","Revisar colinealidad")))}
graf_diag<-function(m){a<-augment(m);p1<-ggplot(a,aes(.fitted,.std.resid))+geom_point(color=pastel[1])+geom_hline(yintercept=0)+geom_smooth(se=FALSE)+theme_minimal()+labs(title="Residuos vs ajustados");p2<-ggplot(a,aes(sample=.std.resid))+stat_qq(color=pastel[4])+stat_qq_line()+theme_minimal()+labs(title="Gráfico Q-Q");p1+p2}
predecir<-function(m,d,area,est,hab,parq,ban){n<-data.frame(areaconst=area,estrato_f=factor(as.character(est),levels=levels(d$estrato_f)),habitaciones=hab,parqueaderos=parq,banios=ban);as.data.frame(predict(m,n,interval="prediction"))|>mutate(Estrato=est,.before=1)}
ofertas<-function(d,pres,area,ests,hab,parq,ban,n=5){x<-d|>filter(preciom<=pres,estrato%in%ests,between(longitud,-77,-76),between(latitud,3,4));x|>mutate(distancia=sqrt(((areaconst-area)/pmax(sd(areaconst),1))^2+((habitaciones-hab)/pmax(sd(habitaciones),1))^2+((parqueaderos-parq)/pmax(sd(parqueaderos),1))^2+((banios-ban)/pmax(sd(banios),1))^2+((estrato-mean(ests))/pmax(sd(estrato),1))^2))|>arrange(distancia)|>slice_head(n=n)|>mutate(Prioridad=row_number())}
mapa_ofertas<-function(x,titulo)leaflet(x)|>addProviderTiles(providers$CartoDB.Positron)|>addAwesomeMarkers(~longitud,~latitud,popup=~paste0("<b>Prioridad ",Prioridad,"</b><br>",barrio,"<br>$",preciom," millones<br>",areaconst," m²"))|>addControl(titulo,"topright")
base1<-filtrar(vivienda_limpia,"Casa","Norte");stopifnot(nrow(base1)>=10)
tabla(head(base1,3),"Primeros tres registros de base1");tabla(base1|>count(tipo,zona),"Comprobación del filtro");mapa(base1,"Casas del norte")
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud | estrato_f |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1209 | Zona Norte | 02 | 5 | 320 | 150 | 2 | 4 | 6 | Casa | acopi | -76.51 | 3.48 | 5 |
| 1592 | Zona Norte | 02 | 5 | 780 | 380 | 2 | 3 | 3 | Casa | acopi | -76.52 | 3.49 | 5 |
| 4057 | Zona Norte | 02 | 6 | 750 | 445 | 1 | 7 | 6 | Casa | acopi | -76.53 | 3.39 | 6 |
| tipo | zona | n |
|---|---|---|
| Casa | Zona Norte | 700 |
Los puntos fuera del sector visualmente esperado pueden originarse en errores de geocodificación, coordenadas aproximadas o diferencias entre zonificación comercial y administrativa. Una confirmación rigurosa requiere polígonos oficiales.
eda(base1,"Caso 1:");tabla(cor_tab(base1),"Spearman: caso 1",4)
| Variable | Rho | p_valor | Decision |
|---|---|---|---|
| areaconst | 0.8196 | 0 | Significativa |
| estrato | 0.7166 | 0 | Significativa |
| parqueaderos | 0.5973 | 0 | Significativa |
| banios | 0.6447 | 0 | Significativa |
| habitaciones | 0.4351 | 0 | Significativa |
Se eligió Spearman porque estrato es ordinal; habitaciones, baños y parqueaderos son discretas; y precio/área pueden ser asimétricos y contener valores extremos. Spearman examina relaciones monótonas mediante rangos sin exigir normalidad ni linealidad estricta, a diferencia de Pearson.
m1<-ajustar(base1);tabla(tidy(m1,conf.int=TRUE)|>mutate(Significativo=ifelse(p.value<.05,"Sí","No")),"Coeficientes: caso 1",4);aj1<-glance(m1);tabla(aj1|>select(r.squared,adj.r.squared,sigma,statistic,p.value,nobs),"Ajuste: caso 1",4);tabla(diagnosticar(m1),"Supuestos: caso 1",4);graf_diag(m1)
| term | estimate | std.error | statistic | p.value | conf.low | conf.high | Significativo |
|---|---|---|---|---|---|---|---|
| (Intercept) | 8.6917 | 18.7678 | 0.4631 | 0.6434 | -28.1569 | 45.5403 | No |
| areaconst | 0.7561 | 0.0457 | 16.5335 | 0.0000 | 0.6664 | 0.8459 | Sí |
| estrato_f4 | 65.4790 | 17.5917 | 3.7222 | 0.0002 | 30.9395 | 100.0185 | Sí |
| estrato_f5 | 126.8010 | 16.7560 | 7.5675 | 0.0000 | 93.9023 | 159.6997 | Sí |
| estrato_f6 | 311.0661 | 27.2823 | 11.4017 | 0.0000 | 257.5000 | 364.6322 | Sí |
| habitaciones | 3.3904 | 4.7157 | 0.7190 | 0.4724 | -5.8684 | 12.6492 | No |
| parqueaderos | 21.9668 | 5.5669 | 3.9460 | 0.0001 | 11.0369 | 32.8968 | Sí |
| banios | 25.6966 | 5.9108 | 4.3474 | 0.0000 | 14.0914 | 37.3019 | Sí |
| r.squared | adj.r.squared | sigma | statistic | p.value | nobs |
|---|---|---|---|---|---|
| 0.6707 | 0.6674 | 154.4191 | 201.39 | 0 | 700 |
| Supuesto | Prueba | p_valor | Resultado |
|---|---|---|---|
| Normalidad | Shapiro-Wilk | 0 | Se rechaza H0 |
| Homocedasticidad | Breusch-Pagan | 0 | Heterocedasticidad |
| Independencia | Durbin-Watson | 0 | Posible autocorrelación |
| Multicolinealidad | VIF/GVIF | NA | Aceptable |
Para cada coeficiente, H0 establece que beta es cero y H1 que es diferente de cero. Un coeficiente positivo de área es económicamente lógico porque una mayor superficie suele elevar el valor. Signos positivos en estrato, baños o parqueaderos también son plausibles por ubicación y comodidad. Un signo contrario o falta de significancia debe discutirse considerando colinealidad, segmentación y variables omitidas. En normalidad H0 indica residuos normales; en Breusch-Pagan, varianza constante; en Durbin-Watson, ausencia de autocorrelación. El VIF no es una prueba de hipótesis formal y se interpreta como diagnóstico, con alerta práctica desde 5.
pred1<-bind_rows(predecir(m1,base1,200,4,4,1,2),predecir(m1,base1,200,5,4,1,2));tabla(pred1,"Predicción vivienda 1, millones COP")
| Estrato | fit | lwr | upr | |
|---|---|---|---|---|
| 1…1 | 4 | 312.32 | 7.56 | 617.09 |
| 1…2 | 5 | 373.64 | 69.12 | 678.17 |
of1<-ofertas(base1,350,200,c(4,5),4,1,2);tabla(of1|>select(Prioridad,barrio,preciom,areaconst,estrato,habitaciones,parqueaderos,banios,distancia),"Cinco ofertas vivienda 1");mapa_ofertas(of1,"Ofertas vivienda 1")
| Prioridad | barrio | preciom | areaconst | estrato | habitaciones | parqueaderos | banios | distancia |
|---|---|---|---|---|---|---|---|---|
| 1 | ciudad los álamos | 215 | 143.85 | 4 | 4 | 1 | 2 | 0.94 |
| 2 | acopi | 275 | 190.00 | 4 | 3 | 1 | 2 | 1.08 |
| 3 | la merced | 350 | 216.00 | 5 | 4 | 2 | 2 | 1.14 |
| 4 | los andes | 280 | 180.00 | 4 | 4 | 1 | 3 | 1.15 |
| 5 | la merced | 330 | 240.00 | 4 | 3 | 1 | 2 | 1.21 |
La predicción se contrasta con $350 millones. Si el estimado o buena parte del intervalo supera ese valor, existe riesgo presupuestal y conviene negociar o flexibilizar atributos. Las cinco ofertas no se eligen solo por precio: pertenecen al segmento solicitado, respetan el crédito y minimizan conjuntamente diferencias estandarizadas de área, estrato, habitaciones, baños y parqueaderos.
base2<-filtrar(vivienda_limpia,"Apartamento","Sur");stopifnot(nrow(base2)>=10)
tabla(head(base2,3),"Primeros tres registros de base2");tabla(base2|>count(tipo,zona),"Comprobación del filtro");mapa(base2,"Apartamentos del sur");eda(base2,"Caso 2:");tabla(cor_tab(base2),"Spearman: caso 2",4)
| id | zona | piso | estrato | preciom | areaconst | parqueaderos | banios | habitaciones | tipo | barrio | longitud | latitud | estrato_f |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5098 | Zona Sur | 05 | 4 | 290 | 96 | 1 | 2 | 3 | Apartamento | acopi | -76.53 | 3.45 | 4 |
| 698 | Zona Sur | 02 | 3 | 78 | 40 | 1 | 1 | 2 | Apartamento | aguablanca | -76.50 | 3.40 | 3 |
| 8199 | Zona Sur | NA | 6 | 875 | 194 | 2 | 5 | 3 | Apartamento | aguacatal | -76.56 | 3.46 | 6 |
| tipo | zona | n |
|---|---|---|
| Apartamento | Zona Sur | 2777 |
| Variable | Rho | p_valor | Decision |
|---|---|---|---|
| areaconst | 0.8659 | 0 | Significativa |
| estrato | 0.7517 | 0 | Significativa |
| parqueaderos | 0.6808 | 0 | Significativa |
| banios | 0.7055 | 0 | Significativa |
| habitaciones | 0.3920 | 0 | Significativa |
m2<-ajustar(base2);tabla(tidy(m2,conf.int=TRUE)|>mutate(Significativo=ifelse(p.value<.05,"Sí","No")),"Coeficientes: caso 2",4);aj2<-glance(m2);tabla(aj2|>select(r.squared,adj.r.squared,sigma,statistic,p.value,nobs),"Ajuste: caso 2",4);tabla(diagnosticar(m2),"Supuestos: caso 2",4);graf_diag(m2)
| term | estimate | std.error | statistic | p.value | conf.low | conf.high | Significativo |
|---|---|---|---|---|---|---|---|
| (Intercept) | -42.4131 | 10.3493 | -4.0982 | 0.0000 | -62.7061 | -22.1200 | Sí |
| areaconst | 1.3007 | 0.0475 | 27.3883 | 0.0000 | 1.2076 | 1.3938 | Sí |
| estrato_f4 | 28.2584 | 6.8813 | 4.1065 | 0.0000 | 14.7653 | 41.7514 | Sí |
| estrato_f5 | 53.5087 | 7.1052 | 7.5309 | 0.0000 | 39.5766 | 67.4408 | Sí |
| estrato_f6 | 204.7992 | 8.9655 | 22.8431 | 0.0000 | 187.2195 | 222.3789 | Sí |
| habitaciones | -12.8016 | 3.2910 | -3.8899 | 0.0001 | -19.2547 | -6.3486 | Sí |
| parqueaderos | 63.9413 | 3.7606 | 17.0028 | 0.0000 | 56.5674 | 71.3152 | Sí |
| banios | 39.7720 | 2.9650 | 13.4138 | 0.0000 | 33.9582 | 45.5859 | Sí |
| r.squared | adj.r.squared | sigma | statistic | p.value | nobs |
|---|---|---|---|---|---|
| 0.7902 | 0.7896 | 87.5483 | 1489.457 | 0 | 2777 |
| Supuesto | Prueba | p_valor | Resultado |
|---|---|---|---|
| Normalidad | Shapiro-Wilk | 0 | Se rechaza H0 |
| Homocedasticidad | Breusch-Pagan | 0 | Heterocedasticidad |
| Independencia | Durbin-Watson | 0 | Posible autocorrelación |
| Multicolinealidad | VIF/GVIF | NA | Aceptable |
pred2<-bind_rows(predecir(m2,base2,300,5,5,3,3),predecir(m2,base2,300,6,5,3,3));tabla(pred2,"Predicción vivienda 2, millones COP")
| Estrato | fit | lwr | upr | |
|---|---|---|---|---|
| 1…1 | 5 | 648.44 | 475.58 | 821.30 |
| 1…2 | 6 | 799.73 | 626.84 | 972.62 |
of2<-ofertas(base2,850,300,c(5,6),5,3,3);tabla(of2|>select(Prioridad,barrio,preciom,areaconst,estrato,habitaciones,parqueaderos,banios,distancia),"Cinco ofertas vivienda 2");mapa_ofertas(of2,"Ofertas vivienda 2")
| Prioridad | barrio | preciom | areaconst | estrato | habitaciones | parqueaderos | banios | distancia |
|---|---|---|---|---|---|---|---|---|
| 1 | capri | 350 | 270.00 | 5 | 4 | 3 | 3 | 1.26 |
| 2 | San Fernando | 350 | 258.00 | 5 | 5 | 2 | 4 | 1.71 |
| 3 | el ingenio | 700 | 250.00 | 6 | 5 | 2 | 4 | 1.79 |
| 4 | cuarto de legua | 410 | 295.55 | 5 | 4 | 2 | 4 | 1.80 |
| 5 | cuarto de legua | 520 | 320.00 | 5 | 4 | 2 | 4 | 1.84 |
La predicción se compara con $850 millones bajo el mismo criterio. Las alternativas priorizadas equilibran cercanía al perfil, ubicación y viabilidad financiera. La revisión final debe incluir visita, tradición, gravámenes, administración, antigüedad, estado físico y avalúo.
comparacion<-bind_rows(tibble(Caso="Casa norte",N=aj1$nobs,R2=aj1$r.squared,R2_ajustado=aj1$adj.r.squared,RMSE=aj1$sigma,AIC=aj1$AIC,Presupuesto=350),tibble(Caso="Apartamento sur",N=aj2$nobs,R2=aj2$r.squared,R2_ajustado=aj2$adj.r.squared,RMSE=aj2$sigma,AIC=aj2$AIC,Presupuesto=850));tabla(comparacion,"Comparación de modelos")
| Caso | N | R2 | R2_ajustado | RMSE | AIC | Presupuesto |
|---|---|---|---|---|---|---|
| Casa norte | 700 | 0.67 | 0.67 | 154.42 | 9052.01 | 350 |
| Apartamento sur | 2777 | 0.79 | 0.79 | 87.55 | 32729.32 | 850 |
El presente análisis logró cumplir satisfactoriamente el objetivo general de desarrollar y validar modelos estadísticos que permitieran estimar el valor comercial de viviendas a partir de sus principales características físicas y socioeconómicas, proporcionando una herramienta objetiva para apoyar los procesos de búsqueda, evaluación y comercialización de inmuebles. A través de una rigurosa depuración de la base de datos, el análisis exploratorio de la información y la construcción de modelos de regresión múltiple diferenciados por segmento de mercado, fue posible identificar los factores que más influyen en la formación de precios y generar predicciones útiles para orientar decisiones inmobiliarias fundamentadas en evidencia.
Los resultados evidencian que el área construida constituye el principal determinante del precio en ambos segmentos analizados, seguida por variables como el estrato socioeconómico, el número de baños y la cantidad de parqueaderos. Estas características explican una proporción considerable de la variabilidad observada en el mercado, lo que confirma que los precios de los inmuebles responden, en gran medida, a atributos observables y cuantificables. Para el caso de las viviendas ubicadas en la zona norte, el modelo alcanzó una capacidad explicativa cercana al 67%, permitiendo establecer que una vivienda con las características requeridas por el cliente presenta un valor esperado aproximado de $312 millones en estrato 4 y de $374 millones en estrato 5. Estos resultados indican que el presupuesto disponible de $350 millones resulta compatible con inmuebles de estrato 4 y se encuentra en el límite para viviendas de estrato 5, situación que exige procesos de negociación más activos o una flexibilización de algunos atributos deseados.
Por su parte, el modelo desarrollado para apartamentos en la zona sur mostró un desempeño superior, explicando cerca del 79% de la variación observada en los precios y presentando un menor error de predicción. Las estimaciones indican que un apartamento con las características definidas por el cliente tendría un valor esperado cercano a los $648 millones en estrato 5 y de aproximadamente $800 millones en estrato 6. Bajo este escenario, el presupuesto de $850 millones permite acceder a una oferta amplia y competitiva en estrato 5 y a una parte importante del mercado de estrato 6, aunque con mayores riesgos de sobrecostos en este último segmento.
El ejercicio de priorización de ofertas permitió identificar alternativas reales que, aun cuando no reproducen exactamente todas las características requeridas por los clientes, representan opciones viables desde la perspectiva económica y funcional. Este hallazgo confirma una realidad frecuente del mercado inmobiliario: la vivienda perfecta rara vez existe en la oferta disponible, por lo que las decisiones de compra suelen sustentarse en la capacidad de equilibrar atributos, ubicación y presupuesto.
De manera general, el estudio demuestra que los modelos predictivos constituyen una herramienta valiosa para fundamentar estrategias de comercialización, optimizar los procesos de asesoría y reducir la incertidumbre en la valoración de inmuebles. Sin embargo, también evidencia que las proyecciones deben interpretarse como referencias técnicas y no como avalúos definitivos, dado que factores como el estado físico del inmueble, su antigüedad, las condiciones jurídicas, la seguridad del sector, las características urbanísticas y la dinámica coyuntural del mercado pueden influir significativamente en el valor final de negociación.
En consecuencia, los resultados obtenidos permiten concluir que la combinación entre análisis estadístico, conocimiento del mercado y validación comercial constituye la estrategia más robusta para respaldar decisiones inmobiliarias informadas, eficientes y alineadas con las necesidades reales de compradores y vendedores.
Alcance: Los precios corresponden a ofertas, no necesariamente a transacciones cerradas. Por tanto, el modelo orienta negociación y comparación, pero no reemplaza el avalúo profesional.
Adoptar una comercialización basada en valor y no únicamente en precio: Los resultados muestran que variables como área, estrato, baños y parqueaderos impactan directamente la valoración de los inmuebles. Por ello, las estrategias comerciales deben enfocarse en comunicar claramente estos atributos diferenciales al cliente y no limitarse a competir por precio. Acción recomendada: construir fichas comerciales que resalten los atributos que más valor generan según el modelo estadístico.
Utilizar los modelos predictivos como soporte para negociaciones: Las predicciones obtenidas permiten establecer rangos razonables de negociación y detectar inmuebles sobrevalorados o subvalorados dentro del mercado. Acción recomendada: incorporar las estimaciones del modelo en las reuniones de asesoría para sustentar técnicamente las ofertas y contraofertas.
Priorizar inmuebles con mayor ajuste entre presupuesto y perfil del cliente: La investigación evidenció que el mayor obstáculo en muchos procesos no es el presupuesto sino encontrar inmuebles que cumplan simultáneamente todas las características deseadas. Acción recomendada: desarrollar un sistema de puntuación que mida el nivel de coincidencia entre los requerimientos del cliente y cada inmueble disponible, permitiendo priorizar las alternativas con mayor probabilidad de cierre.
Fortalecer la comercialización del segmento de estrato 5: Los resultados muestran que este segmento ofrece el mejor equilibrio entre precio, disponibilidad de oferta y compatibilidad con los presupuestos analizados. Acción recomendada: concentrar campañas de captación y promoción en zonas con alta presencia de inmuebles de estrato 5, donde existe una mayor probabilidad de concretar negocios.
Implementar estrategias de negociación diferenciadas para estrato 6: Aunque el mercado de estrato 6 ofrece mayor valorización y rentabilidad potencial, también presenta mayores riesgos de exceder los presupuestos de los compradores. Acción recomendada: diseñar procesos de negociación anticipada con propietarios para identificar márgenes reales de descuento antes de presentar los inmuebles a los clientes.
Incorporar variables de comportamiento comercial en futuros modelos: Actualmente, la predicción se fundamenta principalmente en características físicas y socioeconómicas del inmueble.
Acción recomendada: registrar y analizar variables como:
Esto permitirá construir modelos predictivos más precisos y alineados con el comportamiento real del mercado.