Datos sobre la infraestructura de transporte público de Londres

TRANSPORTE
TRANSPORTE PÚBLICO
LONDRES
Autor/a
Afiliación

Carolina Armella Chavarria

Universidad de Valencia

Fecha de publicación

1 de abril de 2026

Input

Se ha trabajado con un conjunto de datos de transporte público de Londres procedente de Transport for London (TfL) en formato GTFS (General Transit Feed Specification). Este tipo de feed contiene información estructurada sobre paradas, conexiones internas y metadatos del servicio, y resulta especialmente útil para análisis espaciales y de accesibilidad.

Archivo Descripción Uso en el análisis
stops.txt Paradas, estaciones, accesos y plataformas Base principal de las entidades espaciales
pathways.txt Conexiones peatonales entre elementos Enriquecimiento de accesibilidad y conectividad
levels.txt Niveles verticales de estaciones Referencia de estructura interna
feed_info.txt Metadatos del feed Contexto temporal y técnico

La fuente oficial del feed es Transport for London Open Data (https://tfl.gov.uk/info-for/open-data-users/our-open-data). Este dataset es adecuado para estudiar accesibilidad, conectividad y estructura interna de estaciones, pero requiere limpieza y enriquecimiento para su explotación analítica.

Descripción

El objetivo de este trabajo es depurar y enriquecer el feed GTFS para generar un dataset más útil para análisis geoespaciales y de accesibilidad. Para ello, se parte de las paradas de TfL y se incorporan variables derivadas a partir de las conexiones internas entre elementos de una estación.

En concreto, se pretende:

  1. Normalizar el conjunto de paradas y conservar únicamente los registros útiles para análisis espacial.

  2. Transformar la geometría a un sistema proyectado para poder hacer cálculos en metros.

  3. Resumir la conectividad interna a partir de pathways.txt.

  4. Construir indicadores simples de complejidad y accesibilidad de estación.

  5. Exportar el resultado en varios formatos reutilizables para análisis y visualización.

Tratamiento

# Cargar archivos GTFS
stops <- read_csv("../data/2526020003/stops.txt", show_col_types = FALSE)
pathways <- read_csv("../data/2526020003/pathways.txt", show_col_types = FALSE)
levels <- read_csv("../data/2526020003/levels.txt", show_col_types = FALSE)
feed_info <- read_csv("../data/2526020003/feed_info.txt", show_col_types = FALSE)

En primer lugar, se normalizan las coordenadas geográficas convirtiéndolas a formato numérico. Este paso es esencial para evitar errores en etapas posteriores de análisis espacial. Además, se filtran únicamente aquellos registros correspondientes a tipos de localización relevantes (paradas, estaciones, accesos y nodos), descartando otros elementos que no aportan valor al análisis.

stops_clean <- stops %>%
  mutate(
    stop_lat = as.numeric(stop_lat),
    stop_lon = as.numeric(stop_lon)
  ) %>%
  filter(location_type %in% c(0, 1, 2, 3))

En primer lugar, se normalizan las coordenadas geográficas convirtiéndolas a formato numérico. Este paso es esencial para evitar errores en etapas posteriores de análisis espacial. Además, se filtran únicamente aquellos registros correspondientes a tipos de localización relevantes (paradas, estaciones, accesos y nodos), descartando otros elementos que no aportan valor al análisis.

# Convertimos a sf
stops_sf <- stops_clean %>%
  filter(!is.na(stop_lat), !is.na(stop_lon)) %>%
  st_as_sf(coords = c("stop_lon", "stop_lat"), crs = 4326, remove = FALSE)

stops_uk <- stops_sf %>%
  st_transform(27700) %>%
  mutate(
    stop_lat_wgs84 = stop_lat,
    stop_lon_wgs84 = stop_lon
  )

A continuación, se incorpora información adicional sobre la conectividad interna de las estaciones a partir del archivo pathways.txt. Este fichero describe las conexiones peatonales entre distintos elementos del sistema (como accesos, plataformas o niveles), lo que permite enriquecer el dataset original con variables relacionadas con la accesibilidad y la complejidad estructural de las estaciones.

# Agregamos información de conectividad desde pathways
pathways_summary <- pathways %>%
  mutate(
    pathway_mode = as.integer(pathway_mode),
    has_lift_edge = pathway_mode == 5,
    has_ramp_edge = str_detect(str_to_lower(pathway_id), "ramp"),
    has_escalator_edge = str_detect(str_to_lower(pathway_id), "escal")
  ) %>%
  group_by(from_stop_id) %>%
  summarise(
    n_pathways = n(),
    has_lift = any(has_lift_edge, na.rm = TRUE),
    has_ramp = any(has_ramp_edge, na.rm = TRUE),
    has_escalator = any(has_escalator_edge, na.rm = TRUE),
    .groups = "drop"
  )

# Unión con stops
stops_enhanced <- stops_uk %>%
  left_join(pathways_summary, by = c("stop_id" = "from_stop_id")) %>%
  mutate(
    n_pathways = replace_na(n_pathways, 0),
    has_lift = replace_na(has_lift, FALSE),
    has_ramp = replace_na(has_ramp, FALSE),
    has_escalator = replace_na(has_escalator, FALSE)
  )
stops_classified <- stops_enhanced %>%
  mutate(
    service_type = case_when(
      str_detect(str_to_lower(stop_name), "underground") ~ "Underground",
      str_detect(str_to_lower(stop_name), "overground") ~ "Overground",
      str_detect(str_to_lower(stop_name), "dlr") ~ "DLR",
      str_detect(str_to_lower(stop_name), "elizabeth") ~ "Elizabeth Line",
      str_detect(str_to_lower(stop_name), "tram") ~ "Tram",
      str_detect(str_to_lower(stop_name), "rail") ~ "National Rail",
      TRUE ~ "Bus/Other"
    ),
    station_complexity = case_when(
      n_pathways > 50 ~ "High",
      n_pathways > 20 ~ "Medium",
      TRUE ~ "Low"
    ),
    accessibility_score = case_when(
      has_lift & has_ramp ~ 3,
      has_lift | has_ramp ~ 2,
      TRUE ~ 1
    )
  )

En particular, se define una clasificación del tipo de servicio en función del nombre de la parada, lo que permite distinguir entre diferentes modos de transporte (metro, tren, tranvía, etc.). Asimismo, se construyen indicadores sintéticos de complejidad de estación y accesibilidad, que resumen de forma sencilla la estructura interna y las condiciones de uso de cada instalación.

Con el objetivo de evaluar la calidad del dataset resultante, se genera un informe de validación que resume algunos indicadores clave, como el número total de paradas, la disponibilidad de coordenadas o la presencia de información de conectividad. Este paso permite detectar posibles problemas en los datos y asegurar la consistencia del resultado final.

# Comprobar que todas las estaciones tengan coordenadas válidas
validation_report <- stops_classified %>%
  st_drop_geometry() %>%
  summarise(
    total_stops = n(),
    stops_with_coords = sum(!is.na(stop_lat_wgs84) & !is.na(stop_lon_wgs84)),
    stops_with_pathways = sum(n_pathways > 0, na.rm = TRUE),
    stops_with_lift = sum(has_lift, na.rm = TRUE),
    stops_with_ramp = sum(has_ramp, na.rm = TRUE),
    avg_pathways = mean(n_pathways, na.rm = TRUE)
  )

dir.create("output", showWarnings = FALSE, recursive = TRUE)
write_csv(validation_report, "output/validation_report.csv")
files <- c(
  "output/london_transit_enhanced.gpkg",
  "output/london_transit_enhanced.rds",
  "output/london_transit_enhanced.csv",
  "output/london_transit_enhanced.geojson"
)

file.remove(files)
[1] TRUE TRUE TRUE TRUE
# Exportar GeoPackage
st_write(
  stops_classified %>% select(-matches("^buffer_")),
  "output/london_transit_enhanced.gpkg",
  driver = "GPKG",
  overwrite = TRUE
)
Writing layer `london_transit_enhanced' to data source 
  `output/london_transit_enhanced.gpkg' using driver `GPKG'
Writing 4083 features with 19 fields and geometry type Point.
# Exportar RDS
write_rds(
  stops_classified %>% select(-matches("^buffer_")),
  "output/london_transit_enhanced.rds"
)

# Exportar CSV
stops_classified %>%
  st_drop_geometry() %>%
  write_csv("output/london_transit_enhanced.csv")

# Exportar GeoJSON (visualización web)
st_write(
  stops_classified %>% select(-matches("^buffer_")),
  "output/london_transit_enhanced.geojson",
  driver = "GeoJSON",
  overwrite = TRUE
)
Writing layer `london_transit_enhanced' to data source 
  `output/london_transit_enhanced.geojson' using driver `GeoJSON'
Writing 4083 features with 19 fields and geometry type Point.

Output

Se ha obtenido un conjunto de datos limpio y enriquecido con geometría válida, metadatos de servicio, conectividad interna y un índice simple de accesibilidad y complejidad de estación.

Además, se genera un reporte de validación para comprobar cuántas paradas conservan coordenadas y cuánta conectividad aporta el feed.

Los ficheros generados con este procedimiento son:

  • output/validation_report.csv

  • output/london_transit_enhanced.gpkg

  • output/london_transit_enhanced.rds

  • output/london_transit_enhanced.csv

  • output/london_transit_enhanced.geojson

Visualización

1. Mapa de estaciones con plataformas codificadas

london_transit_enhanced <- read_csv("output/london_transit_enhanced.csv")
Rows: 4083 Columns: 19
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (6): stop_id, stop_name, parent_station, level_id, service_type, station...
dbl (7): stop_lat, stop_lon, location_type, stop_lat_wgs84, stop_lon_wgs84, ...
lgl (6): stop_code, stop_desc, platform_code, has_lift, has_ramp, has_escalator

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
london_transit_enhanced %>%
  filter(!is.na(stop_lat), !is.na(stop_lon)) %>%
  leaflet() %>%
  addTiles() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(
    lat = ~stop_lat, 
    lng = ~stop_lon,
    radius = 2.5,
    color = "#2b8cbe",
    fillOpacity = 0.6,
    stroke = TRUE,
    weight = 1,
    opacity = 0.8,
    popup = ~stop_name,
    label = ~stop_name
  ) %>%
  setView(lng = -0.1278, lat = 51.5074, zoom = 10.5) %>%
  addScaleBar(position = "bottomleft") %>%
  addMiniMap(
    tiles = providers$CartoDB.Positron,
    position = "topright",
    width = 200,
    height = 150,
    toggleDisplay = TRUE
  )

Este mapa nos muestra la distribución geográfica completa de 4.583 paradas del sistema de transporte público de Londres extraídas del feed GTFS. Se observa una concentración radial en el centro de Londres con extensiones hacia los boroughs periféricos, evidenciando la cobertura multimodal del sistema.

2. Distribución espacial. Paradas por zona de Londres

london_transit_enhanced %>%
  filter(!is.na(stop_lat), !is.na(stop_lon)) %>%
  mutate(
    zona = case_when(
      stop_lat > 51.58 ~ "Norte",
      between(stop_lat, 51.48, 51.58) ~ "Centro", 
      TRUE ~ "Sur/Sureste"
    )
  ) %>%
  count(zona) %>%
  ggplot(aes(x = reorder(zona, n), y = n, fill = zona)) +
  geom_col(alpha = 0.8, width = 0.7) +
  geom_text(aes(label = n), vjust = -0.3, size = 5, fontface = "bold") +
  coord_flip() +
  labs(title = "Número de paradas por zona geográfica",
       subtitle = "Distribución Norte-Centro-Sur de Londres") +
  scale_fill_brewer(palette = "Set2", guide = "none") +
  theme_minimal() +
  theme(axis.title.y = element_blank())

Este segundo gráfico revela una concentración del 62% de las paradas en la zona Centro (2.690 paradas), frente al 15% en Norte (634) y 17% en Sur/Sureste (759). Esta distribución refleja la centralidad del sistema de transporte londinense

El/los fichero(s) generados con este procedimiento/técnica/metodología se puede descargar de aquí.



Proyecto de Innovación Educativa Emergente (PIEE-2737007)