(TFL) es un organismo estatutario responsable de la red de transporte público de Londres, gestionando los autobuses, el metro, el Docklands Light Railway, el Overground y las carreteras principales. Su política de 'Datos Abiertos' significa que comparten gran parte de sus datos internos con el público, lo que, según dicen, actualmente impulsa más de 600 aplicaciones para los londinenses.
Una fuente de datos interesante que comparten con el público son los datos de uso de Santander Cycle (también conocida coloquialmente como Boris Bikes). Cada viaje en bicicleta queda registrado. Estos datos se remontan desde 2015 hasta 2025. Los datos están organizados en archivos CSV semanales difíciles de manejar para descargar: https://cycling.data.tfl.gov.uk/#!usage-stats%2F. Cada fila de estos datos es un viaje en bicicleta, y cada viaje en bicicleta comienza en una estación de bicicletas en particular. Esto equivale a 9,2 millones de horas de estación, 800 estaciones de bicicletas y 144 CSV semanales. Vea un ejemplo de los datos a continuación.
| Fecha de inicio | Nombre de la estación de inicio | Fecha de finalización | Nombre de la estación final | Duración | |:—————–|:———————————|:—————–|:————————————|———–:| | 01/10/2016 00:00 | Drury Lane, Covent Garden | 01/10/2016 00:04 | Calle Frith, Soho | 240 | | 01/10/2016 00:00 | Calle Pott, Bethnal Green | 01/10/2016 00:05 | Victoria Park Road, Hackney Central | 300 | | 01/10/2016 00:00 | Plaza Harrington 2, Camden Town | 01/10/2016 00:20 | Baylis Road, Waterloo | 1200 | | 01/10/2016 00:01 | Calle Cantón, Álamo | 01/10/2016 00:14 | Calle Hewison, Viejo Ford | 780 | | 01/10/2016 00:01 | Calle Cephas, Bethnal Green | 01/10/2016 00:11 | Mercado de Brick Lane, Shoreditch | 600 |
Podemos tomar cada fila y agregar estos datos para poder ver las tendencias de estacionalidad a lo largo de algunos años:
Este conjunto de datos ahora nos da una idea del uso de bicicletas en Londres (estos datos no contienen todos los viajes en bicicleta en Londres, pero podemos esperar que el uso de Boris Bike esté relacionado con el uso general de bicicletas). Para un entusiasta de la ciencia de datos causales, la siguiente pregunta natural es: ¿cómo podemos utilizar este conjunto de datos para responder algunas preguntas causales interesantes? ¿Qué eventos ocurren que tienen un gran impacto en los viajes en bicicleta? ¿Cuáles son algunas interrupciones comunes a gran escala que hacen que las personas no puedan tomar el metro? ¿Cómo muestran los trabajadores el valor de su trabajo a sus empleadores reteniéndolo? ¡Huelgas!
En este artículo examinaré el impacto causal de las principales huelgas de metro en el uso de bicicletas en Londres. Las huelgas históricas son algo difíciles de precisar en Internet, pero afortunadamente para mí hay una libertad de información sobre las huelgas, que nos da fechas de huelgas a nivel lineal, entre 2014 y 2018.
Dado que los datos comienzan como una fila para cada viaje en bicicleta en todas las estaciones de bicicletas de Londres, tenemos trabajo que hacer para encontrar un formato que podamos usar. Tenemos 144 CSV semanales que convertimos a parquet para ayudar con las limitaciones de memoria. Luego combinamos todos estos archivos de parquet en un gran marco de datos y los agrupamos por estación de bicicletas y hora.
| id_estación | inicio_viajes | ts | |————-:|:——————–|—–:| | 1 | 2016-01-10 09:00:00 | 4 | | 1 | 2016-01-10 10:00:00 | 1 | | 1 | 2016-01-10 11:00:00 | 2 | | 1 | 2016-01-10 12:00:00 | 2 | | 1 | 2016-01-10 13:00:00 | 2 |
TFL también proporciona coordenadas para cada estación de bicicletas. Unimos las coordenadas a su correspondiente celda H3. H3 es un sistema de cuadrícula hexagonal utilizado por Uber y útil para muchas tareas de análisis espacial. El siguiente gráfico muestra cómo se distribuyen los viajes en bicicleta por Londres.
Ahora podemos agregar los datos del viaje hasta el nivel de días celulares H3 junto con algunos factores de confusión que creemos que también tienen un impacto en el uso de la bicicleta en Londres. Estos incluyen características climáticas y estacionales.
# Procese en fragmentos para evitar picos de memoria chunk_size = 100_000 h3_cells =[]para i en rango(0, len(bf), tamaño_pedazo): trozo = bf.iloc[i:i+tamaño_pedazo] h3_cells.extend([h3.latlng_to_cell(lat, lon, 8) para lat, lon en zip(pedazo["lat"], trozo["lon"])]) print(f" Procesado {min(i+tamaño_pedazo, len(bf)):,} / {len(bf):,}") bf["h3_cell"] = h3_cells # Agregado al día de la celda bf["day"] = pd.to_datetime(bf["trips_start"]).dt.date cell_day = ( bf.groupby(["h3_cell", "day"]) .agg( total_trips = ("ts", "sum"), frac_exposed = ("strike_exposed", "media"), n_estaciones = ("station_id", "nunique"), temperatura_2m = ("temperatura_2m", "media"), precipitación = ("precipitación", "media"), is_weekend = ("es_weekend", "primero"), is_bank_holiday = ("is_bank_holiday", "primero"), is_school_holiday = ("is_school_holiday", "primero"), días_para_la_siguiente_strike = ("days_to_next_strike", "first"), days_since_last_strike= ("days_since_last_strike", "first"), mes = ("mes", "primero"), año = ("año", "primero"), doy = ("doy", "primero"), lat = ("lat", "media"), lon = ("lon", "media"), ) .reset_index() )
Esto significa que cada fila de nuestro conjunto de datos ahora contiene todos los viajes en bicicleta de Santander para cada día y cada celda H3. Tenemos 172 células observadas durante 1.192 días.
También filtramos para que se incluyeran todas las celdas que tenían al menos una parada de tubo dentro de los 500 m; esto es necesario para satisfacer el Supuesto de Positividad. Este supuesto establece que cada unidad debe tener una probabilidad distinta de cero tanto de tratamiento como de control. Si una celda no tiene paradas de metro a menos de 500 m (podemos suponer razonablemente que un viajero que no puede usar el metro debido a las huelgas caminaría 500 m para usar una bicicleta Santander).
día_celda = día_celda[día_celda["n_tube_within_500m"] >= 1].copiar()
Esto nos proporciona un conjunto de datos de días de células con 62 células H3, 66.039 filas y el 98,4% de las células jamás tratadas.
A continuación podemos definir nuestras variables de resultado y tratamiento. Como cada celda tendrá diferentes niveles de uso esperado de bicicletas, creamos nuestra variable de resultado para que sea relativa a la capacidad de cada celda: los viajes totales de cada celda en cada día divididos por el número de estaciones de bicicletas en esa celda. tomamos el registro de manera que nuestro coeficiente nos informe sobre cambios proporcionales en lugar de cambios absolutos y para que se cumplan los supuestos estadísticos de la regresión, y agregamos uno para que los días de celda tranquilos con cero viajes registrados se incluyan en el análisis en lugar de eliminarlos silenciosamente.
[
Y_{i,t} = logleft(1 + frac{text{Total de viajes en bicicleta en la celda } i text{ el día } t}{text{Número de estaciones de bicicletas en la celda } i}right)
]
Podemos calcular la variable de resultado en Python con el siguiente código.
cell_day["y_per_station_log1p"] = np.log1p(cell_day["total_trips"] / cell_day["n_stations"])
Definir la variable de tratamiento para la exposición a los golpes no es tan sencillo. Sabemos qué líneas de metro estuvieron en huelga cada día, pero esta información no se asigna claramente a cada celda, ya que cada línea de metro serpentea a través de Londres. Cuando reflexionamos sobre qué sucede con el uso de bicicletas cuando las líneas de metro no están operativas, es útil decidir primero cuándo las estaciones de bicicletas están "cerca" de las estaciones de metro afectadas por huelgas. Hemos definido que una estación de bicicletas se verá afectada por una huelga si se encuentra a menos de 400 m de una estación de metro que da servicio a una de las líneas en huelga.
Luego definimos una celda h3 para que se vea afectada por la huelga si alguna estación de bicicletas se ve afectada dentro de esa celda h3. Esta es ahora nuestra variable de tratamiento.
[
T_{i,t} =
begin{casos}
1, & text{si la celda } i text{ está expuesta a ataques el día } t \
0, & text{de lo contrario}
end{casos}
]
Para construir esta variable de tratamiento para nuestro conjunto de datos, primero tenemos que crear una columna de huelga efectuada para nuestros datos a nivel de estación. Hacemos esto usando la siguiente función que toma nuestros datos de horas de estación, un marco de datos que nos dice qué líneas estaban en huelga cada día y un marco de datos que nos dice que las estaciones están conectadas a cada línea en huelga.
def adjuntar_strikes_to_base( base: pd.DataFrame, strikes_daily: pd.DataFrame, station_line_map: pd.DataFrame, ) -> pd.DataFrame: """ Adjunte un indicador binario strike_exposed al panel de horas de la estación. Una hora de estación se trata (strike_exposed = 1) si alguna línea de metro que sirve a esa estación está en huelga ese día. La base debe tener columnas: station_id, trips_start (fecha y hora), ts (recuento numérico de viajes). """ df = base.copy() df["date"] = pd.to_datetime(df["trips_start"]).dt.floor("D") station_day_treat = ( strikes_daily .merge(station_line_map[["station_id", "affected_line"]], on="affected_line", how="inner") .drop_duplicates(subset=["station_id", "date"]) .assign(strike_exposed=1) [["station_id", "date", "strike_exposed"]] ) df = df.merge(station_day_treat, on=["station_id", "date"], how="left") df["strike_exposed"] = df["strike_exposed"].fillna(0).astype(int) devuelve df.drop(columns=["fecha"])
Cuando agregamos el marco de datos de hora de estación al nivel de día de celda, tomamos la media de la columna strike_exposed en una nueva columna frac_exposed, y cualquier celda con un frac_exposed positivo se convierte en celdas tratadas.
día_celda["tratado"] = (día_celda["frac_exposed"] > 0).astype(int)
Puede encontrar más detalles sobre la manipulación de datos en https://github.com/stucsk99/tfl_bike_casual/blob/main/01_data_pipeline.ipynb
Ahora que hemos definido nuestras variables de resultado y tratamiento, demos un paso atrás y hablemos de la teoría causal subyacente que sustenta todos los resultados a los que llegaremos en este artículo.
¿Cuál es la pregunta que queremos hacer?
El mecanismo causal que subyace a nuestro análisis es la sustitución. Cuando una línea de metro choca, los viajeros que normalmente viajarían bajo tierra se ven desplazados y deben encontrar una alternativa. Sostenemos que para los viajeros cerca de las principales estaciones de intercambio, las Bicicletas Santander representan la alternativa más accesible: están disponibles sin registro previo, tienen un precio para viajes cortos y están físicamente presentes en las estaciones donde emergen los viajeros desplazados. Esta historia de sustitución es lo que conecta nuestra variable de tratamiento con nuestro resultado a través de una vía causal creíble en lugar de una mera correlación.
Se produce huelga → los viajeros en metro no pueden viajar → esos viajeros buscan alternativas → algunos caminan hasta un muelle cercano de Santander → aumentan los viajes en bicicleta. Cada flecha de esa cadena es un paso en el mecanismo. Sin él, incluso un resultado estadísticamente significativo es sólo una correlación con una historia adjunta. Con él, tienes una razón para creer que el efecto es real.
El mecanismo causal que estamos describiendo puede describirse mediante el siguiente modelo causal estructural.
Debido a que el momento de la huelga está determinado por las negociaciones laborales más que por cualquier cosa relacionada con la demanda de bicicletas, tenemos buenas razones para creer que los días de huelga no son sistemáticamente diferentes de los días sin huelga en formas que afectarían de forma independiente el uso de bicicletas. Una huelga convocada un martes de enero no se convoca porque los martes de enero sean inusualmente buenos o malos para el ciclismo: se convoca porque fracasó una negociación salarial. Esto hace que la comparación contrafactual sea creíble: el uso de bicicletas que observamos en días comparables sin huelga es una aproximación razonable de lo que habría sucedido en los días de huelga si la huelga no hubiera ocurrido.
Ahora que hemos establecido nuestro mecanismo causal, podemos continuar con nuestro análisis causal. Pero antes de hacer eso, repasemos algunos de los componentes importantes de la inferencia causal: el marco de resultados potenciales.
Resultados potenciales
El problema fundamental de la inferencia causal es que no observamos los resultados contrafácticos: nunca sabemos qué habría pasado con el uso de bicicletas en un día de huelga, si esa huelga no hubiera ocurrido. Esto es, por definición, inobservable.
En un mundo ideal, observaríamos ambos resultados potenciales para cada unidad: Yi,t(0)Y_{i,t}(0), que es el resultado potencial si la celda ii no hubiera experimentado un ataque el día tt, y Yi,t(1)Y_{i,t}(1), que es el resultado potencial si hubiera experimentado un ataque. A partir de aquí podemos definir el efecto del tratamiento individual para la célula ii el día tt, que es la diferencia entre los dos resultados potenciales:
[
tau_{i,t} = Y_{i,t}(1) – Y_{i,t}(0)
]
Nos encantaría saber esta cantidad para cada observación, pero como se mencionó anteriormente, solo observamos uno de los dos resultados potenciales. El siguiente paso lógico es promediar este efecto para todas las unidades. Este es el efecto promedio del tratamiento (ATE):
[
ATE = E[Y_{i,t}(1) – Y_{i,t}(0)] = E[tau_{i,t}]
]
Este es el efecto de tratamiento esperado para una unidad seleccionada al azar del total. En nuestro entorno, responde: para un día de celda seleccionado al azar en nuestro panel, ¿cuál es el cambio esperado en el registro de viajes en bicicleta por estación si ese día de celda estuviera expuesto a una huelga?
También podemos definir otro efecto del tratamiento: El Efecto Promedio del Tratamiento sobre los Tratados (ATT):
[
ATT = E[Y_{i,t}(1) – Y_{i,t}(0) | D_i = 1] = E[tau_{i,t} | D_i = 1]
]
Donde Di∈{0,1}D_i in {0, 1} es el indicador de tratamiento. Esto cambia el enfoque hacia las unidades que realmente fueron tratadas. para un día de celda que en realidad estuvo expuesto a un ataque, ¿cuál fue el efecto causal de esa exposición?
Efecto del tratamiento ingenuo
Antes de entrar en cómo estimamos estas cifras utilizando métodos causales sólidos, primero podemos ilustrar qué sale mal cuando estimamos el ATE de manera ingenua. Para hacer esto de la manera más simple posible, podríamos estimar el ATE como la diferencia en las medias muestrales entre las observaciones tratadas y de control. Eso es,
[
tau^{ingenuo} = overline{Y}_{D=1} – overline{Y}_{D=0}
]
print(f"Diferencia ingenua: {np.expm1(cell_day.loc[cell_day['treatment']==1,'y_per_station_log1p'].mean() – cell_day.loc[cell_day['treatment']==0,'y_per_station_log1p'].mean())*100:+.1f}%")
Según nuestros datos, esto da una diferencia ingenua del +5,5%. Las células con algún tipo de exposición a huelgas tienen un registro de viajes en bicicleta por estación sustancialmente mayor que las células sin ellas. Pero ésta no es una estimación causal creíble. Podemos descomponer la diferencia ingenua algebraicamente para ver exactamente qué está estimando:
[
overline{Y}_{D=1} – overline{Y}_{D=0} = underbrace{E[Y_{i,t}(1) – Y_{i,t}(0) | D_i = 1]}_{ATT} + underbrace{E[Y_{i,t}(0) | D_i = 1] – E[Y_{i,t}(0) | D_i = 0] }_{text{sesgo de selección}}
]
El primer término es el TCA, lo que queremos. El segundo término es sesgo de selección: la diferencia en los resultados potenciales de control entre las unidades tratadas y no tratadas. En nuestro caso, este sesgo probablemente sea positivo: las celdas que están expuestas a las huelgas están cerca de las líneas de metro, lo que significa que están en áreas más densas y céntricas de Londres que tienen un mayor uso de bicicletas de referencia independientemente de cualquier huelga. La ingenua estimación combina el efecto de las huelgas con la ventaja preexistente de las células ubicadas en el centro.
Eliminar este sesgo de selección es el trabajo completo de los métodos siguientes.
Datos del panel
Nuestro conjunto de datos tiene una estructura que es particularmente adecuada para abordar el sesgo de selección. Es un panel. Un conjunto de datos de panel observa las mismas unidades repetidamente a lo largo del tiempo. Nuestro panel específico tiene la siguiente estructura
[
{ X_{i,t}, D_{i,t}, Y_{i,t} }
]
Donde i=1,…,Ni = 1, dots, N representa nuestras celdas H3 y t=1,…,Tt = 1 , dots , T representa nuestros días observados en nuestro conjunto de datos. (obtenga el valor real de T y N aquí) Tenemos N x T de observaciones totales.
La idea clave que proporcionan los datos de panel es la siguiente: si observamos la misma celda durante varios días, podemos separar el componente invariante en el tiempo del resultado de esa celda de la variación específica del día. Una célula cerca de la estación Bank siempre estará más ocupada que una célula cerca de Pimlico; esa es una característica permanente de la ubicación de la célula, no algo que cambie con las huelgas. Los métodos de panel nos permiten tener en cuenta esta característica permanente sin tener que medirla directamente.
Podemos utilizar la configuración inherente de los datos del panel para modelar el efecto del tratamiento utilizando un modelo de efectos fijos bidireccional. Esta es una generalización de un método tradicional de Diferencia en Diferencias. Este modelo se configura de la siguiente manera:
[
Y_{i,t} = alpha_{i} + lambda_{t} + tau{D}_{i,t} + beta X_{i,t} + epsilon_{i,t}
]
Donde Yi,tY_{i,t} es nuestra variable de resultado para la celda ii el día tt, αialpha_{i} es el efecto fijo para la celda ii, λtlambda_{t} es el efecto fijo para el día tt, τtau es el efecto causal del tratamiento, Di,t{D}_{i,t} es el indicador de tratamiento, βbeta son los coeficientes para las covariables Xi,tX_{i,t} y ϵi,tepsilon_{i,t} son nuestros errores.
En este modelo, tenemos dos efectos fijos, αialpha_{i} y λtlambda_{t} para cada celda ii y cada día tt, que actúan como variables ficticias para cada celda y día. El efecto fijo de celda contiene todas las características de celda invariantes en el tiempo (todas las características geográficas de la celda ii que no cambian con el tiempo) y el efecto fijo de fecha contiene todas las variaciones invariantes de celda (variación específica del día). Esto equivale a degradar dentro de cada celda y dentro de cada fecha, lo que elimina todas las características de celda invariantes en el tiempo y los shocks comunes a nivel diario.
Simplemente podemos ejecutar este análisis de regresión usando la función ols de la biblioteca statsmodels.formula.api:
twfe = smf.ols( """y_per_station_log1p ~ tratado + temperatura_2m + precipitación + is_weekend + is_bank_holiday + is_school_holiday + días_hasta_la_próxima_huelga + días_since_last_strike + C(h3_cell) + C(date_str)""", data=cell_day, ).fit( cov_type="cluster", cov_kwds={"grupos": día_celda["h3_celda"]},)
Tenga en cuenta que no podemos ejecutar OLS ordinario ya que las observaciones de la celda en diferentes días están correlacionadas. Si ignoramos esta correlación y utilizamos errores estándar MCO, subestimaríamos sistemáticamente la incertidumbre en ϵi,tepsilon_{i,t}, produciendo intervalos de confianza demasiado estrechos y valores p demasiado pequeños. Podemos solucionar esto utilizando la solución estándar de errores de agrupamiento a nivel de celda. Esto permite una correlación arbitraria entre los residuos ϵi,sepsilon_{i,s} y ϵi,tepsilon_{i,t} para la misma celda i en dos fechas cualesquiera tt y ss, manteniendo al mismo tiempo el supuesto de independencia entre celdas.
Resultados
Nuestro método TWFE nos da un aumento del 3,95% en el uso de bicicletas en Santander en los días de huelga, con un valor p de 0,097.
Antes de profundizar en estos resultados, primero nos centramos en algunos cambios que hicimos en nuestros datos para reforzar los mecanismos causales que queremos comprender.
Habiendo establecido que cada celda de nuestro análisis debe tener al menos una estación de metro en un radio de 500 metros (nuestra condición de positividad), aplicamos una restricción más fuerte motivada por el propio mecanismo causal. No todas las estaciones de metro generan el mismo desplazamiento de viajeros cuando entran en huelga. Las 42 estaciones en las que nos centramos son las principales estaciones de intercambio del centro de Londres: Bank, Liverpool Street, King's Cross, Waterloo, Victoria y sus vecinas. Estas son las estaciones donde miles de viajeros convergen cada mañana, donde los muelles de Santander Bike son más densos y donde la sustitución del metro por la bicicleta es más sencilla: un viajero desplazado sale de una estación cerrada y encuentra un estante de bicicletas a unos metros.
En las estaciones más periféricas, incluso donde existe un muelle de Santander cerca, el mecanismo de desplazamiento es más débil. Menos viajeros dependen exclusivamente del metro, y es más probable que la distancia a pie hasta un muelle para bicicletas exceda lo que tolerará un viajero con presión de tiempo. Por lo tanto, restringirse a las 32 celdas dentro de un radio de 800 metros de estas 42 principales estaciones de intercambio es un enfoque deliberado en la población geográfica donde tanto el impacto de la demanda por la huelga como la respuesta de la oferta de la red de bicicletas están suficientemente concentrados para que el efecto de sustitución sea detectable.
# Obtener centroides de todas las celdas únicas en cell_day_clean Unique_cells = cell_day["h3_cell"].unique() cell_centroids = pd.DataFrame([ {"h3_cell": c, "lat": h3.cell_to_latlng(c)[0], "lon": h3.cell_to_latlng(c)[1]} para c en celdas_únicas ]) # Construya un árbol KD sobre las 42 coordenadas de la estación station_coords = np.radians(CENTRAL_42[["lat", "lon"]].values) tree = cKDTree(station_coords) # Consulta el centroide de cada celda cell_coords = np.radians(cell_centroids[["lat", "lon"]].values) radio_rad = 0.8 / 6371.0 # 800m en radianes # Para cada celda, encuentre la distancia a la más cercana de las 42 estaciones más cercana_dist_rad, _ = tree.query(cell_coords, k=1) cell_centroids["dist_to_central_42_km"] = near_dist_rad * 6371.0 cell_centroids["near_central_42"] = near_dist_rad <= radio_rad central_cells = set( cell_centroids.loc[cell_centroids["near_central_42"], "h3_cell"] ) # ── Filtro ────────────────────────── ─────────────────────────── cell_day_central = cell_day_clean[ cell_day["h3_cell"].isin(central_cells) ].copiar()
Los días a 300 días de cualquier huelga tienen características estacionales muy diferentes de los días de huelga y no tienen relevancia causal para la comparación. Incluirlos obliga a que los efectos fijos de fecha abarquen un amplio rango estacional, y los efectos fijos de celda se estiman a partir de un período que no es directamente relevante para la comparación. Al restringir a una ventana local de 45 días alrededor de cada fecha de huelga, podemos crear un experimento más limpio: los días de control se parecen más al contrafactual de los días tratados y se reduce la confusión estacional.
sub = cell_day_central[cell_day_central["days_to_nearest"] <= 45].copiar()
Ahora tenemos 4 versiones diferentes de basefile, cada una con una relación señal-ruido cada vez más potente.
| Versión del archivo base | Filas | Tratamiento % | |——————————————:|:———–|————-:| | Sólo las celdas a menos de 500 m de la parada del metro | 66.039 | 0,82 | | Sólo celdas cercanas a Estaciones Centrales | 34.590 | 0,94 | | Sólo días dentro de los 45 días siguientes a los días de huelga | 16.799 | 1,95 |
El gráfico muestra las diferentes estimaciones de TWFE en las diferentes especificaciones del archivo base. Con la configuración causalmente más poderosa de nuestros datos de panel se logra un efecto de tratamiento estimado del 3,95 % con un valor p de 0,097.
Nuestro valor p está por encima del estándar p=0,05 que se utiliza como estándar. Esto significa que nuestros resultados de un aumento del 3,95% se lograrían aleatoriamente el 9,7% de las veces. Aunque nuestro valor p está por debajo del punto de referencia utilizado estándar, podemos ver que nuestras tres estimaciones son consistentemente positivas, y la amplitud del intervalo de confianza refleja el número limitado de eventos de huelga en los datos de la FOI, no la ausencia de un efecto.
Supuestos de inferencia causal
Antes de dejarnos llevar por estos resultados, debemos detenernos y considerar los supuestos que deben hacerse para que la estimación TWFE tenga una interpretación casual.
Positividad/Superposición requiere que cada unidad tenga una probabilidad distinta de cero de ser tratada. Hemos solucionado este problema asegurándonos de que cada celda del panel tenga al menos una parada de tubo en un radio de 500 m.
Las tendencias paralelas requieren que, en ausencia de huelgas, las células tratadas y de control habrían experimentado la misma tendencia en el uso de la bicicleta. Esto es plausible en nuestro entorno porque el momento de la huelga está determinado por la dinámica de la negociación laboral: la decisión de hacer huelga en una fecha particular está impulsada por los resultados de la negociación entre la dirección de TfL y los sindicatos, no por nada relacionado con la trayectoria subyacente del uso de la bicicleta.
Ninguna anticipación requiere que las células no cambien su comportamiento antes de que se produzca el tratamiento: que el anuncio de una huelga no altere en sí mismo el uso de las bicicletas en los días previos a la huelga. Esto se soluciona parcialmente mediante la inclusión de días_para_el_próximo_ataque como covariable en la especificación controlada, que captura cualquier tendencia sistemática previa al ataque. Observamos que, en el caso de huelgas realmente no anunciadas, el supuesto de no anticipación se cumple automáticamente.
SUTVA (Supuesto de valor de tratamiento unitario estable, Rubin 1980) requiere que los resultados potenciales de una célula no dependan del estado del tratamiento de otras células. Este es el supuesto que es más probable que se viole en nuestro entorno: una huelga desplaza a los viajeros a través de una amplia área geográfica, afectando potencialmente el uso de bicicletas en las celdas más allá de las directamente adyacentes a las líneas de huelga. Las violaciones del SUTVA atenuarán nuestra estimación hacia cero, lo que significa que nuestro +3,95 % debe interpretarse como un límite inferior del efecto real para las células más directamente expuestas.
Comentarios finales
Este artículo se propuso responder a una pregunta sencilla: ¿las huelgas del metro de Londres empujan a los viajeros a utilizar Santander Bikes? La respuesta, basada en un análisis de efectos fijos bidireccional de cuatro años de datos abiertos de TfL, es sí, pero llegar a esa respuesta fue considerablemente menos sencillo de lo que podría sugerir el resultado claro.
Trabajar con datos de la vida real nunca es sencillo. Para obtener los datos del viaje en un formato que me fuera útil para responder las preguntas. Mientras analizaba 144 CSV semanales, tuve que conciliar esquemas de columnas inconsistentes entre las publicaciones de datos, corregir una discrepancia de nombres silenciosa entre los identificadores de líneas de huelga y reconstruir el mapeo espacial entre las estaciones de bicicletas y las paradas de metro varias veces.
Todo esto fue antes de considerar las diferentes suposiciones causales necesarias para construir un argumento creíble. Como tengo experiencia en aprendizaje automático, también dediqué una cantidad considerable de tiempo a investigar metaaprendices (estudiantes S, T y X, que son un conjunto de métodos predictivos de aprendizaje automático para estimar los efectos del tratamiento) para este problema. Esto nos habría dado una visión más rica: el efecto del tratamiento promedio condicional, o CATE, que nos diría cómo varía el efecto del tratamiento en Londres.
Aprendí por las malas que la herramienta no se ajustaba al problema. Los datos de panel con tratamiento binario recurrente y una sólida historia de identificación geográfica requieren una regresión de efectos fijos, no un estimador ML transversal.