Tablero  ·  Documentación

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.

Columnas del CSV que entrega directamente el extractor pyairbnb.
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.

Columnas que se añaden al cruzar con el GeoJSON de INEGI.
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.

Columnas que se calculan en el procesamiento.
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
  • 34513696Comfortable room in the center of Guadalajara, Zona Centro (GDL). USD$80,742/noche.
  • 698748598563310156Loft 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.
  • 24783033Real 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.