Metodología
Cómo se construyó este tablero, qué significa cada campo del dataset, cómo se procesó y qué no dice.
Diccionario de datos
El CSV publicable trae 49 columnas por alojamiento, agrupadas según su origen en tres bloques: las que entrega el extractor directamente de Airbnb, las que se obtienen al cruzar con el GeoJSON de INEGI (point-in-polygon) y las que se calculan en el procesamiento. La distinción importa porque cada bloque tiene un nivel distinto de confianza, ofuscación y mediación editorial.
Columnas que entrega el extractor
Lo que pyairbnb recibe del motor de búsqueda de Airbnb para cada listing y ventana de fecha. Son los datos que cualquier usuario ve al abrir el listing en la plataforma; el extractor solo los recoge en tabla.
| Campo | Tipo | Significado |
|---|---|---|
room_id |
str / int | Identificador único del listing en Airbnb. URL canónica: airbnb.com.mx/rooms/{room_id}. |
fecha_descarga |
fecha ISO | Día en que se capturó el dato. Toda la base es 2026-05-22. |
lat · lng |
float | Coordenadas ofuscadas por Airbnb (~150 m del punto real). WGS 84 (EPSG:4326). |
name |
str | Título del listing que el host escribe en Airbnb. Texto público. |
title |
str | Tipo de alojamiento (p.ej. "Home in Zapopan", "Loft in Tlaquepaque"). Texto público. |
rating |
float | Calificación promedio (0–5). 0 cuando el listing no tiene reseñas suficientes. |
reviews_count |
int | Número total de reseñas acumuladas. |
price_<periodo>_usd |
float / null | Precio total de 2 noches en USD para esa ventana. null si no estaba disponible. Nueve columnas, una por periodo. |
disponible_<periodo> |
bool | True si el calendario aceptaba reserva en esa ventana. False si estaba bloqueado. Nueve columnas. |
discount_<periodo>_usd |
float / null | Descuento que aplicó Airbnb sobre el precio nominal de esa ventana, en USD. Nueve columnas. |
Columnas que añade el cruce con el GeoJSON de INEGI
Las coordenadas del extractor (lat,
lng) se cruzan contra los polígonos del
Marco Geoestadístico Nacional 2025 de INEGI. Para cada
listing se calcula en qué municipio y colonia cae su
punto ofuscado. Estos cuatro campos son el resultado de
ese cruce, no vienen de Airbnb.
| Campo | Tipo | Significado |
|---|---|---|
cve_mun |
str | Clave INEGI del municipio (3 dígitos). 039=Guadalajara, 120=Zapopan, 098=Tlaquepaque, 101=Tonalá, 097=Tlajomulco, 070=El Salto, 051=Juanacatlán. |
nom_mun |
str | Nombre del municipio, como lo publica INEGI. |
cvegeo_colonia |
str | Clave geoestadística INEGI de la colonia (13 dígitos). Llave de cruce con colonias.geojson. |
nom_asen |
str | Nombre del asentamiento (colonia) según INEGI. |
Columnas calculadas en el procesamiento
Lo que añadimos nosotros con el script
preprocesar_datos.py: la conversión de
moneda con tipo de cambio fijo y la marca de outliers
excluidos del tablero por revisión manual.
| Campo | Tipo | Significado |
|---|---|---|
price_<periodo>_mxn |
float / null | Precio en USD convertido a MXN con tipo de cambio FIX Banxico (17.3213 MXN/USD del 22 de mayo de 2026). Nueve columnas, una por periodo. Cifra comparativa, no financiera. |
outlier_excluido |
bool | True para los 5 listings excluidos del tablero por revisión manual (ver §05). False para el resto. Permite replicar las cifras del tablero filtrando por outlier_excluido = False. |
Diccionario de conceptos
Alojamiento (listing)
Un listing —que en este tablero llamamos
alojamiento— es la unidad mínima
reservable en Airbnb: lo que aparece con un
room_id único y se puede reservar con un
click. No es una vivienda (una misma casa
puede tener varios cuartos publicados como listings
independientes), no es un host (un
operador profesional puede tener decenas o cientos), y
no es un inmueble catastral (no hay
relación 1:1 con el padrón municipal).
Periodo y ventana
Cada periodo nombra una ventana de fecha de check-in y check-out sobre la que se consultó a Airbnb. Todas las ventanas son de dos noches. La lista completa de las nueve ventanas aparece en §03.a.
Precio por noche
En el CSV crudo cada
price_<periodo>_{usd,mxn} es el precio
total para las dos noches de esa ventana,
tal como lo reporta Airbnb. Las visualizaciones del
tablero muestran USD/noche y MXN/noche (precio total
dividido entre dos).
Disponible / Ocupado
En el dataset, Disponible significa que el calendario del listing aceptaba reserva en esa ventana y que Airbnb devolvió un precio. Ocupado significa que el calendario estaba cerrado: el motor de búsqueda no devolvió precio porque no había reserva posible. Ocupado no es lo mismo que Reservado: puede ser una reserva confirmada, un bloqueo estratégico del host, un uso personal o un listing nuevo con el calendario aún cerrado. El dato no permite distinguir entre los cuatro casos (ver §05).
Saturación
Porcentaje de alojamientos del dataset con
disponible_<periodo> = False sobre el
total del periodo. La saturación del 53.9 %
para la ventana México significa que más de la mitad del
inventario tenía el calendario cerrado para esas fechas
el 22 de mayo de 2026.
Sobreprecio Mundial (premium)
Por cada alojamiento, ratio entre el precio del Mundial y el precio de la ventana de referencia (septiembre 2026). La cifra publicada en el tablero (+91 % para México, +86 % para España) es la mediana de esos ratios por alojamiento. El cálculo detallado y la elección de la referencia están en §03.c.
Mediana (no promedio)
La mediana es el valor que queda al centro si los precios se ordenan de menor a mayor: la mitad del inventario cuesta menos, la otra mitad más. Es robusta a outliers extremos y describe el alojamiento típico, no un promedio matemático arrastrado por casos atípicos. Todas las cifras agregadas del tablero —KPIs, coroplético, boxplot, distribución, top de colonias— usan mediana como estadístico base.
Coroplético
Mapa temático donde cada colonia se rellena con un color que codifica un valor numérico (alojamientos, sobreprecio, ocupación). Las colonias sin Airbnb van con un patrón diagonal sutil — el silencio se mantiene como dato visible, no se funde con el fondo del mapa.
Procedimiento
a · Extracción
En primer lugar, usé la librería pyairbnb
v2.2.1 para correr nueve veces el scraper contra la API
frontend de Airbnb el 22 de mayo de 2026,
una pasada por cada periodo. En cada vuelta le pasé al
motor de búsqueda fechas concretas de check-in y
check-out, y recibí los listings que esa noche estaban
reservables en ese rango, junto con el precio que el
host pedía en ese momento.
Las nueve ventanas que consulté fueron:
may2026 (28–30 mayo, fechas tempranas),
mexico (17–19 jun, partido México en Akron),
espana (25–27 jun, partido España en Akron),
dummy (5–7 sep, temporada normal de
referencia), nov2026, feb2027,
abr2027, jun2027 (ventanas
posteriores de comparación) y baseline
(agosto 2027, base de largo plazo).
Configuré el scraper con un user-agent identificable, respeté los límites de petición de la plataforma y distribuí las consultas a lo largo de varias horas para no saturar el servidor. Hice las búsquedas sobre el bounding box de la Zona Metropolitana de Guadalajara sin paginación agresiva. No usé credenciales privadas ni accedí a información restringida.
b · Procesamiento
Con el CSV crudo en mano, lo primero que hice fue un
filtro espacial. Apliqué
point-in-polygon de cada alojamiento contra los
polígonos INEGI 2025 de las 2,739 colonias de los siete
municipios de la ZMG y conservé únicamente los
alojamientos cuya coordenada ofuscada cayó dentro del
polígono ZMG. De ese cruce salieron las cuatro columnas
geoestadísticas: cve_mun, nom_mun,
cvegeo_colonia y nom_asen.
Después convertí los precios de USD a MXN. Para que
todas las cifras del tablero fueran comparables entre sí
elegí un tipo de cambio único: el FIX de Banco
de México publicado en el DOF para el día de la
descarga (17.3213 MXN/USD). Apliqué ese
factor a cada columna price_<periodo>_usd
para generar la columna price_<periodo>_mxn.
Lo declaro como cifra comparativa, no financiera: no
contempla comisiones de tarjeta ni casas de cambio.
Luego normalicé la unidad de precio. Las columnas
price_<periodo>_* del CSV traían el
precio total de las dos noches que dura cada ventana de
consulta. Al generar los archivos JSON servidos al
cliente (listings.json,
agregado-colonia.json) dividí entre dos
para que todas las visualizaciones del tablero hablen en
USD/noche y MXN/noche.
El CSV descargable conservó los totales originales por
trazabilidad.
El paso más delicado fue la revisión manual de
outliers, que hice el 24 de mayo de 2026. Entré
uno por uno a los listings más caros del dataset, leí la
ficha en Airbnb y crucé tipo de inmueble, colonia y
reviews. Identifiqué cinco alojamientos con tarifas entre
USD$6,700 y USD$80,700 por noche que no eran consistentes
con sus características —lofts, cuartos solos y
departamentos en barrios populares—. Mi hipótesis es que
el host infló la tarifa para bloquear la fecha sin
cerrar el calendario manualmente. Decidí excluir esos
cinco listings del tablero en todos los periodos
para que no contaminaran las agregaciones, pero los
dejé en el CSV publicable marcados con la columna
outlier_excluido = True, de modo que la
decisión quedara transparente y reversible. La base
visible del tablero pasó así de 10,840 a 10,835
alojamientos.
Ver los cinco IDs excluidos
34513696— Comfortable room in the center of Guadalajara, Zona Centro (GDL). USD$80,742/noche.698748598563310156— Loft Niny, Loma Bonita Ejidal (Zapopan). USD$13,478/noche.24920563— Departamento en Balcones de Santa María (Tlaquepaque). USD$11,452/noche.1588557352832649099— Departamento en Balcones de Santa María (Tlaquepaque). USD$6,746/noche.24783033— Real del Bosque (Zapopan). USD$6,729/noche.
c · Análisis
Para todas las cifras agregadas del tablero —los KPIs, el coroplético, el boxplot, la distribución, la serie temporal y el top de colonias— decidí usar la mediana como estadístico base. La distribución de precios de Airbnb en la ZMG es muy asimétrica y arrastra outliers extremos; el promedio se distorsionaba demasiado y describía mal el alojamiento típico. La mediana, en cambio, es robusta a esos casos.
Después calculé el Sobreprecio Mundial.
Por cada alojamiento que aparecía tanto en la ventana
del Mundial como en septiembre 2026 obtuve el ratio
precio_mundial / precio_septiembre en USD
por noche, y reporté la mediana de esos ratios. Elegí
septiembre como referencia por dos razones: es
temporada normal mexicana sin eventos masivos, y fue la
pasada con mayor cobertura del inventario (más
alojamientos con precio disponible en el cruce). El
cálculo dio una n = 3,483 alojamientos en la
ventana México y n = 3,880 en la ventana España.
Para los agregados por colonia (mediana
del precio, mediana del premium, % de saturación,
conteo de listings) preferí precalcular los valores
server-side y guardarlos en
agregado-colonia.json, en lugar de
recalcularlos sobre 10,835 listings cada vez que el
navegador renderiza el coroplético. Eso bajó
considerablemente el costo del primer render.
d · Visualización
Para construir el tablero elegí trabajar con stack vanilla. Usé D3.js v7 para las gráficas y el coroplético, Leaflet 1.9 para el mapa base y Leaflet.heat para la capa de densidad. Para la capa Clúster sumé Leaflet.markercluster, pero lo cargué con dynamic import: solo se descarga la primera vez que el usuario activa esa capa, no al cargar el sitio.
Como tile base elegí CartoDB Dark Matter
sobre datos de OpenStreetMap, coherente con el tema
oscuro del tablero. El mapa proyecta en Web Mercator
(EPSG:3857) por compatibilidad con los
tile servers públicos; los polígonos INEGI se sirven
en WGS 84 (EPSG:4326).
Para no cargar 3 MB de CSV al navegador, dividí los
datos servidos en varios archivos según uso:
listings.json con los campos numéricos y
espaciales, listings-texto.json con título y
descripción (que se carga lazy, solo al abrir una
ficha), agregado-colonia.json con las
métricas por colonia, colonias.geojson con
los 2,739 polígonos, y meta.json con
fechas, conteos y versiones. El sitio no usa
tracking de usuario; no incluí Google
Analytics, Meta Pixel ni equivalentes.
Fuentes de datos
a · Airbnb
La fuente primaria es Airbnb, vía el
motor de búsqueda público de airbnb.com.mx.
El dataset reproduce lo visible para cualquier usuario
anónimo de la plataforma: sin login, sin cookies
de cuenta, sin credenciales privadas. No se
accede a información de host (correo, teléfono,
ingresos), a datos de huéspedes ni a histórico de
reservas — Airbnb no expone esa información por su
interfaz pública.
Reporte técnico de la extracción: docs/extraccion/pyairbnb-zmg.md.
b · Colonias INEGI
Los polígonos de colonias provienen del Marco Geoestadístico Nacional 2025 del Instituto Nacional de Estadística y Geografía (INEGI). Se filtraron a los siete municipios de la Zona Metropolitana de Guadalajara: Guadalajara (039), Zapopan (120), San Pedro Tlaquepaque (098), Tonalá (101), Tlajomulco de Zúñiga (097), El Salto (070) y Juanacatlán (051). Total: 2,739 colonias.
Tiles base. Las capas visuales del mapa usan CartoDB Dark Matter sobre datos de OpenStreetMap. Atribución obligatoria visible en la esquina inferior del mapa.
Sesgos y limitaciones
Este tablero trabaja con datos públicos de una sola plataforma capturados en un día específico. Como todo trabajo con datos, este ejercicio tiene sesgos y limitaciones. A continuación las describo.
¿Por qué los Ocupados no tienen precio?
pyairbnb consulta el motor de búsqueda de Airbnb
pasándole fechas específicas (check-in
y check-out de cada una de las nueve ventanas). El motor
devuelve los listings que están reservables en
esa ventana, junto con el precio que el host pide en ese
momento. Si el calendario está bloqueado para esas
fechas, Airbnb no devuelve precio:
el precio que sirve la plataforma es el precio
al momento de cotizar una reserva concreta,
no un precio de catálogo permanente. Por eso en el CSV
la columna price_<periodo>_usd queda
vacía cuando disponible_<periodo> es
False. No es un dato perdido: es un dato
que nunca existió desde la API.
Las medianas se calculan solo sobre los Disponibles
Como consecuencia directa, todas las cifras de mediana
publicadas en el tablero se construyen sobre el
subconjunto de listings con precio reservable
para esa ventana. Los Ocupados no entran al cálculo
porque su price es null y los
filtros descartan los nulls antes de calcular el
estadístico. Esto introduce un sesgo de
selección: medimos el precio del subconjunto
que decidió ofrecerse, no del mercado completo. Si los
hosts que retiran oferta son sistemáticamente más caros
o más baratos que los que sí cotizan, la mediana publicada
subestima o sobreestima el "precio real del mercado".
El dato no permite saber en qué dirección va el sesgo.
"Ocupado" no significa "Reservado"
El dato no distingue entre cuatro fenómenos muy distintos que colapsan en la etiqueta Ocupado:
- Reservado. Alguien ya reservó esas fechas. El calendario se cerró por demanda real.
- Bloqueado estratégicamente. El host marcó las fechas como "no disponibles" para retirar oferta — esperar mejor postor, dosificar inventario.
- Bloqueado por uso propio. El host vive ahí esos días, renueva, usa el espacio personalmente.
- Listing nuevo con calendario aún cerrado para esas fechas.
La cifra de 53.9 % de saturación mezcla los cuatro. Si una parte sustantiva es bloqueo estratégico (no reservas), entonces lo que estamos viendo no es solo "Airbnb se llenó por el Mundial" sino hosts retirando oferta especulativamente. Dos lecturas que el dato fuente no permite separar.
¿Cómo trata el tablero la ausencia de precio?
- Filtro de precio del mapa. Los Ocupados se filtran usando
price_baseline_usd(tarifa de agosto 2027) o, si no existe, el primer precio definido del listing en cualquier otra ventana. - Gráficas estadísticas. Los Ocupados quedan fuera del cálculo de mediana, cuartiles y sobreprecio porque su precio es
null. - Coroplético de Ocupación. Cuenta los Ocupados sin importar si están reservados o bloqueados estratégicamente. El mapa muestra qué tan cerrado está el calendario, no qué tan demandado está el barrio.
Coordenadas ofuscadas
Airbnb desplaza cada alojamiento unos 150 metros de su ubicación real por privacidad del host. Por eso el mapa no debe leerse a escala de manzana. Sirve para detectar concentraciones por colonia o por corredor, no para señalar el inmueble exacto.
No se puede medir concentración por host
El campo host_id que entrega
pyairbnb no es fiable: cuando aparece, viene
mezclado con el nombre del listing. Por eso este tablero
no puede medir concentración por anfitrión,
que es probablemente el indicador más relevante para
discutir la financiarización de la vivienda en la ZMG.
La distinción entre listing, vivienda y host queda
definida en §02.
No hay padrón municipal de comparación
Guadalajara y Zapopan no publican un padrón propio de hospedaje no tradicional. No podemos cruzar este dataset con un registro oficial ni distinguir listings con permiso de los que operan en zona gris.
Snapshot de un día
Toda la captura es del 22 de mayo de 2026. No hay serie histórica. Si un host cambió su precio el 23 de mayo, no lo sabemos. Si abrió o cerró su calendario después, tampoco. El tablero es una fotografía, no una película.
Una sola plataforma
El tablero mide únicamente la oferta de Airbnb. Otras plataformas (Booking, VRBO, Expedia, hoteles tradicionales) ofrecen también hospedaje temporal en la ZMG y no aparecen aquí. La cifra "10,835 alojamientos activos" se lee correctamente como "Airbnbs activos", no como "hospedaje no tradicional total".
Tipo de cambio fijo
La conversión USD → MXN (descrita en §03.b) usa un tipo de cambio único del día de la descarga. No contempla fluctuación posterior, comisiones de tarjeta ni casas de cambio. Quien quiera otro tipo de cambio puede recalcular desde la columna USD del CSV.
Datos descargables
El CSV publicado contiene los mismos campos que cualquier
usuario ve al abrir un listing en Airbnb: identificadores
(room_id), título, descripción, coordenadas
ofuscadas por la plataforma (~150 m), precios y
disponibilidad por las nueve ventanas, rating y
fecha_descarga (2026-05-22). 49 columnas
en total (incluyendo outlier_excluido). No
hay datos personales del host ni del huésped: todo lo
que aparece está accesible públicamente en la
plataforma. Uso editorial y académico libre.
Atribución sugerida: Hector Pina.