Skip to contents

Introduction

GTFS feeds do not have a defined scope regarding its coverage of the transportation system. Some can be bounded to one agency, whereas others can aggregate several modes in the same city, or even national wise.

From the simpler to the most complex feeds, some analysis require to narrow the perspective. GTFShift provides some to help in this process.

Filters

Filter by agency

This example uses a national GTFS feed for long distance rail in Germany, retrieved from https://gtfs.de/en/feeds/de_fv/.

# Load GTFS
gtfs = load_feed("https://download.gtfs.de/germany/fv_free/latest.zip")
summary(gtfs)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, attributions, shapes, transfers, ., calendar, calendar_dates, feed_info, stops
#> agencies     Ceske Drahy, Nederlandse Spoorwegen, Dänische Staatsbahnen ... 10 more
#> service      from 2025-06-22 to 2025-07-22
#> uses         stop_times (no frequencies)
#> # routes       97
#> # trips      4453
#> # stop_ids   1560
#> # stop_names  771
#> # shapes     2144

Multimodal feeds aggregate several agencies. GTFShift::filter_by_agency(), allows to filter by the agency id, name, or both. You can filter by the id and/or name of the agency.

# Filter by agency id
gtfs_5 = GTFShift::filter_by_agency(gtfs, id = 5)
summary(gtfs_5)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, attributions, shapes, transfers, ., calendar, calendar_dates, feed_info, stops
#> agencies     Ceske Drahy, Nederlandse Spoorwegen, Dänische Staatsbahnen ... 10 more
#> service      from 2025-06-22 to 2025-07-22
#> uses         stop_times (no frequencies)
#> # routes       64
#> # trips      3557
#> # stop_ids    792
#> # stop_names  376
#> # shapes     2024

# Filter by agency name
gtfs_sncf = GTFShift::filter_by_agency(gtfs, name = "SNCF")
summary(gtfs_sncf)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, attributions, shapes, transfers, ., calendar, calendar_dates, feed_info, stops
#> agencies     Ceske Drahy, Nederlandse Spoorwegen, Dänische Staatsbahnen ... 10 more
#> service      from 2025-06-22 to 2025-07-22
#> uses         stop_times (no frequencies)
#> # routes      2
#> # trips      27
#> # stop_ids    6
#> # stop_names  5
#> # shapes      6

Original GTFS

shape_agency = gtfs$trips |>
  left_join(gtfs$routes, by = "route_id") |>
  left_join(gtfs$agency, by = "agency_id") |>
  select(shape_id, agency_id, agency_name) |>
  distinct() |> 
  mutate(name = paste(agency_id, agency_name, sep = ": "))

shapes_sf = tidytransit::shapes_as_sf(gtfs$shapes) |>
  left_join(shape_agency, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Agency")

GTFS filtered for agency id = 5

shape_agency = gtfs_5$trips |>
  left_join(gtfs_5$routes, by = "route_id") |>
  left_join(gtfs_5$agency, by = "agency_id") |>
  select(shape_id, agency_id, agency_name) |>
  distinct() |> 
  mutate(name = paste(agency_id, agency_name, sep = ": "))

shapes_sf = tidytransit::shapes_as_sf(gtfs_5$shapes) |>
  left_join(shape_agency, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name = "Agency")

GTFS filtered for agency name = SNCF

shape_agency = gtfs_sncf$trips |>
  left_join(gtfs_sncf$routes, by = "route_id") |>
  left_join(gtfs_sncf$agency, by = "agency_id") |>
  select(shape_id, agency_id, agency_name) |>
  distinct()|> 
  mutate(name = paste(agency_id, agency_name, sep = ": "))

shapes_sf = tidytransit::shapes_as_sf(gtfs_sncf$shapes) |>
  left_join(shape_agency, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Agency")

Filter by mode

This example uses a metropolitan wide GTFS feed for Los Angeles rail services, retrieved from https://developer.metro.net/gtfs-schedule-data/.

# Load GTFS
gtfs = GTFShift::load_feed("https://gitlab.com/LACMTA/gtfs_rail/raw/master/gtfs_rail.zip", create_transfers=FALSE)
summary(gtfs)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency       Metro - Los Angeles
#> service      from 2025-06-27 to 2025-07-11
#> uses         stop_times (no frequencies)
#> # routes        6
#> # trips      7664
#> # stop_ids    440
#> # stop_names  341
#> # shapes       14

Multimodal feeds aggregate several modes. GTFShift::filter_by_mode() allows to restrict the feed to some modes only. Refer to routes.txt ‘route_type’ parameter on GTFS documentation for more details on the modes id that should be used as parameters.

# Filter by mode tram
gtfs_tram = GTFShift::filter_by_modes(gtfs, modes = list(0))
summary(gtfs_tram)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency       Metro - Los Angeles
#> service      from 2025-06-27 to 2025-07-11
#> uses         stop_times (no frequencies)
#> # routes        4
#> # trips      6176
#> # stop_ids     91
#> # stop_names   91
#> # shapes        8

Original GTFS

shape_route = gtfs$trips |>
  left_join(gtfs$routes, by = "route_id") |>
  select(shape_id, route_id, route_type) |>
  distinct() |> 
  mutate(route_type = as.character(route_type))

shapes_sf = tidytransit::shapes_as_sf(gtfs$shapes) |>
  left_join(shape_route, by = "shape_id") |>
  filter(!is.na(route_type))
mapview::mapview(shapes_sf, zcol = "route_type", legend = TRUE, layer.name="Route type")

GTFS filtered for mode tram (0)

shape_route = gtfs_tram$trips |>
  left_join(gtfs_tram$routes, by = "route_id") |>
  select(shape_id, route_id, route_type) |>
  distinct() |> 
  mutate(route_type = as.character(route_type))

shapes_sf = tidytransit::shapes_as_sf(gtfs_tram$shapes) |>
  left_join(shape_route, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "route_type", legend = TRUE, layer.name="Route type")

Filter by route_name

This article uses a GTFS feed from the library GTFS database for Portugal as an example. Refer to the vignette(“download”) for more details.

# Get GTFS from library GTFS database for Portugal
data = read.csv(system.file("extdata", "gtfs_sources_pt.csv", package = "GTFShift"))
gtfs = GTFShift::load_feed(data[data$ID=="faro",]$URL, create_transfers=FALSE)
summary(gtfs)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency       Próximo - Transportes Urbanos de Faro
#> service      from 2023-01-01 to 2025-12-31
#> uses         stop_times (no frequencies)
#> # routes      14
#> # trips      674
#> # stop_ids   187
#> # stop_names 126
#> # shapes      29

GTFS feeds aggregate several routes. GTFShift::filter_by_route_name(), allows to filter the feed for specific routes, given a partial or total match with the short or the long name.

# Filter by short_name with exact match
gtfs_1 = GTFShift::filter_by_route_name(
  gtfs,
  values = list("1"),
  short_name = TRUE,
  exact_match = TRUE
)
summary(gtfs_1)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency       Próximo - Transportes Urbanos de Faro
#> service      from 2023-01-02 to 2025-12-31
#> uses         stop_times (no frequencies)
#> # routes      1
#> # trips      58
#> # stop_ids   23
#> # stop_names 19
#> # shapes      1

# Filter by long_name with partial match
gtfs_terminal = GTFShift::filter_by_route_name(
  gtfs,
  values = list("Terminal", "Rodoviário"),
  short_name = FALSE,
  exact_match = FALSE
)
summary(gtfs_terminal)
#> tidygtfs object
#> files        agency, routes, stop_times, trips, fare_attributes, fare_rules, shapes, calendar, calendar_dates, feed_info, stops
#> agency       Próximo - Transportes Urbanos de Faro
#> service      from 2023-01-01 to 2025-12-31
#> uses         stop_times (no frequencies)
#> # routes       6
#> # trips      406
#> # stop_ids   103
#> # stop_names  72
#> # shapes      15

Original GTFS

route_name = gtfs$trips |>
  left_join(gtfs$routes, by = "route_id") |>
  select(shape_id, route_id, route_short_name, route_long_name) |>
  distinct() |> 
  mutate(name = paste(route_short_name, route_long_name, sep = ": "))

shapes_sf = tidytransit::shapes_as_sf(gtfs$shapes) |>
  left_join(route_name, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Route name")

GTFS filtered for route short name 1 (exact match)

route_name = gtfs_1$trips |>
  left_join(gtfs_1$routes, by = "route_id") |>
  select(shape_id, route_id, route_short_name, route_long_name) |>
  distinct() |> 
  mutate(name = paste(route_short_name, route_long_name, sep = ": "))

shapes_sf = tidytransit::shapes_as_sf(gtfs_1$shapes) |>
  left_join(route_name, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Route name")

GTFS filtered for route long name Terminal, Rodoviário (partial match)

route_name = gtfs_terminal$trips |>
  left_join(gtfs_terminal$routes, by = "route_id") |>
  select(shape_id, route_id, route_short_name, route_long_name) |>
  distinct() |> 
  mutate(name = paste(route_short_name, route_long_name, sep = ": "))

shapes_sf = tidytransit::shapes_as_sf(gtfs_terminal$shapes) |>
  left_join(route_name, by = "shape_id")
mapview::mapview(shapes_sf, zcol = "name", legend = TRUE, layer.name="Route name")