Detectar zonas que se van a revalorizar: XGBoost + message-passing sobre un grafo de barrios
XGBoost de 3 clases + propagación tipo GNN entre barrios vecinos para capturar el "efecto contagio" de la gentrificación. Datos sintéticos calibrados con el INE. Con código y métricas reales.
La gentrificación es contagiosa. Cuando un barrio se pone de moda, el de al lado sube justo después: los que no pueden pagar el primero se mudan al vecino, y el efecto se propaga por el mapa como una mancha de aceite. Un modelo que puntúe cada barrio de forma aislada nunca capturará esto. La solución tiene dos piezas: un clasificador fuerte y una propagación sobre el grafo de vecindad.
Pieza 1: XGBoost de 3 clases
El clasificador base es un XGBoost (600 árboles,
max_depth=6, learning_rate=0.04) que predice la
probabilidad de que un barrio tenga revalorización Baja / Media /
Alta a partir de 18 features socioeconómicas: precio/m²,
tendencia a 1 y 3 años, inversión en infraestructura, accesibilidad de transporte,
licencias de obra nueva y de rehabilitación, renta media, densidad, vacancia
comercial, edad de los edificios, etc.
En lugar de quedarme con la clase más probable, convierto las tres probabilidades en un score continuo 0-100, que se propaga mucho mejor que una etiqueta discreta:
proba = model.predict_proba(X) # [P(baja), P(media), P(alta)]
score = (proba[:, 2] * 1.0 + proba[:, 1] * 0.5) * 100 # 0-100
Pieza 2: message-passing sobre el grafo de barrios
Aquí es donde entra el "efecto contagio". Construyo un grafo donde cada barrio se conecta con sus vecinos geográficos y aplico 2 rondas de message-passing: cada barrio mezcla su propio score con la media de sus vecinos. Es el mismo principio que una Graph Neural Network, con una diferencia que conviene declarar con honestidad: los pesos son fijos, no aprendidos. Es una difusión/suavizado sobre el grafo, no una GNN entrenada con backpropagation:
def message_passing(score, neighbors):
# Ronda 1: 65% score propio + 35% media de vecinos
s1 = {i: 0.65 * score[i] + 0.35 * mean(score[j] for j in neighbors[i])
for i in score}
# Ronda 2: 72% + 28% (converge, suaviza sin aplanar)
s2 = {i: 0.72 * s1[i] + 0.28 * mean(s1[j] for j in neighbors[i])
for i in s1}
return s2
El resultado: un barrio con score medio pero rodeado de barrios al alza sube; uno alto pero aislado se modera. Justo la dinámica espacial que un modelo tabular puro no ve.
Datos: sintéticos, pero calibrados (y lo digo)
Punto de honestidad importante: el dataset son 4.000 barrios sintéticos, no un registro real de transacciones. Ahora bien, no son números al azar: están calibrados con el Índice de Precios de Vivienda del INE (2015-2024) para que las distribuciones y correlaciones se parezcan a las de España. Sirve para demostrar la arquitectura y el razonamiento; no para decidir dónde comprar tu casa.
Resultados honestos
| Métrica | Valor |
|---|---|
| F1-score (macro) | 0.61 |
| MCC | 0.41 |
| CV F1 (5-fold) | 0.59 ± 0.02 |
Con 3 clases equilibradas, el azar da un F1 de 0.33; el modelo llega a 0.61, un 28% por encima del azar. No es magia —predecir revalorización es genuinamente difícil— pero la varianza baja en validación cruzada (±0.02) dice que generaliza y no está sobreajustado. Prefiero un 0.61 honesto y estable que un 0.9 sospechoso.
Qué mejoraría
Lo siguiente sería sustituir el message-passing de pesos fijos por una GNN de verdad (GraphSAGE o GAT) que aprenda cuánto pesa cada vecino según sus features, y alimentarla con datos reales de portales inmobiliarios en lugar de sintéticos.
Pruébalo tú mismo
Es un proyecto personal y open source: no vendo nada, solo comparto cómo está construido. Puedes probar la demo en vivo y trastear con ella. El código está en GitHub. Si te resulta útil o tienes una idea para mejorarlo, encantado de leerla.
Sobre este proyecto
Deteccion de Zonas de Revalorizacion
XGBoost (3 clases: Baja/Media/Alta revalorización) + GNN con 2 rounds de message-passing espacial. F1-macro=0.61 · MCC=0.41 clasificando zonas con datos sintéticos calibrados con e...
Newsletter
¿Te aviso de nuevos proyectos y artículos?
Sin spam. Solo un email cuando publico algo nuevo de IA. Baja en un clic.