Comparing transit accessibility with the r5r package
Our transit system here in Madison (WI) will see a big change in June: On June 12, the flip will be switched and the Transit Network Redesign goes into effect. Last week, Metro released GTFS data for the new network. Not only can riders now use Transit or Google Maps to explore how they get from A to B in the new system; it also allows accessibility analyses that compare the network before and after the redesign.
Accessibility analysis is a big topic in planning, and in addition to proprietary tools, there is an open source ecosystem of software products around transit analysis. For this article I use r5r
, “an R package for rapid realistic routing on multimodal transport networks (walk, bike, public transport and car). It provides a simple and friendly interface to R5, the Rapid Realistic Routing on Real-world and Reimagined networks, the routing engine developed independently by Conveyal.”
The minimum requirements for using r5r
are a file with OpenStreetMap road network data and a GTFS file for the transit data. The former I downloaded from Protomaps, and the latter is available directly from Metro or via OpenMobilityData.
One key promise of the redesign was that it would decrease travel times. r5r
allows us to investigate this. I created a list of points-of-interest (POIs) in different parts of town and saved their coordinated in a Google Spreadsheet. Obviously one could choose other POIs—I picked a mix of locations that are important to me, locations that are on the various edges of town, and some that have come up in public debates during the redesign process. It would be easy to add additional POIs and re-run the analysis.
#read poi coordinates
poi <- googlesheets4::read_sheet("https://docs.google.com/spreadsheets/d/1TLN77Pks1W5dl-kx55XCi55-bK--EhJ_DeHhrexGR5M/edit?usp=sharing")
poi |> gt()
id | lat | lon |
---|---|---|
State Capitol | 43.07475 | -89.38416 |
UW Hospital | 43.07645 | -89.43103 |
Sequoya Library | 43.05378 | -89.45037 |
Henry Vilas Zoo | 43.05808 | -89.41039 |
Mickey’s Tavern | 43.08790 | -89.35847 |
Willy St North | 43.12771 | -89.36300 |
Walmart South Madison | 43.04289 | -89.35068 |
East Towne Mall | 43.12427 | -89.30629 |
West High | 43.06860 | -89.42617 |
DMV West | 43.07894 | -89.52885 |
Catholic Multicultural Center | 43.04605 | -89.39332 |
Another choice is about the time of the comparison: Transit frequencies vary a lot by time of day, with more trips during rush hour, fewer trips during the rest of the day, and no service at all between approximately midnight and 6 am. And less service on weekends. For this analysis I randomly picked a Wednesday afternoon, at 2 pm, right after when the redesign goes into effect and one week before. Not peak hour, but also not low-frequency late night or weekend service. One great feature of r5r
is that it is possible to analyze departures within time windows. This addresses a limitation of analyzing only a single time point: If we were to choose only 2:00 pm for our analysis, the particular schedules would have a huge impact on the result. For example, if the stop closest to the origin has a bus that comes every half hour, it matters a lot for the total trip time if the bus comes at 1:59 or at 2:01 pm. With a 1:59 departure, we’d just have missed the bus and would only get to start our trip at 2:29 pm. Whereas with a 2:01 departure, there is basically no waiting time, cutting 28 minutes from the trip. r5r
allows analysis of departure times for every minute within a given time window. So it will return trip times for 2:00, 2:01, 2:02, …, 2:30 departures and then we can average those times to reduce the bias of choosing only a single time. These averages, then, represent a scenario of spontaneous, unplanned trips: I know where I want to go, I don’t know anything about bus schedules, and I want to leave right now. Again, other choices for analysis are possible.
Here are the results from the analysis in a table. You can sort and filter the table and explore the results. Overall trip times have indeed decreased for many origin/destination pairs. Walk times are more mixed: There are many origin/destination pairs where walk time has increased. This was one of the criticisms about the redesign: For people with limited mobility, these increased walk times could be a problem. But then there are also routes where walk time has decreased. The bottom line: It’s difficult to draw conclusions from the analysis of a set of 11 locations. r5r
provides additional analysis capabilities, and in a future post I may return to this topic.
Sys.setenv(JAVA_HOME="c:/Program Files/OpenJDK/jdk-11/")
options(java.parameters = "-Xmx2G")
rJava::.jinit()
#rJava::.jcall("java.lang.System", "S", "getProperty", "java.version")
# Indicate the path where OSM and GTFS data are stored
r5r_core <- setup_r5(data_path = "data/")
mode <- c("WALK", "TRANSIT")
max_walk_time <- 30 # minutes
max_trip_duration <- 150 # minutes
departure_datetime <- as.POSIXct("12-06-2023 14:00:00",
format = "%d-%m-%Y %H:%M:%S")
# # extract OSM network
# street_net <- street_network_to_sf(r5r_core)
#
# # extract public transport network
# transit_net <- r5r::transit_network_to_sf(r5r_core)
#compare trips before and after network redesign
departure_datetime_before <- as.POSIXct("05-06-2023 14:00:00",
format = "%d-%m-%Y %H:%M:%S")
departure_datetime_after <- as.POSIXct("12-06-2023 14:00:00",
format = "%d-%m-%Y %H:%M:%S")
# calculate a travel time matrix
ttm_before <- expanded_travel_time_matrix(r5r_core = r5r_core,
origins = poi,
destinations = poi,
mode = mode,
departure_datetime = departure_datetime_before,
max_walk_time = max_walk_time,
max_trip_duration = max_trip_duration,
time_window = 30,
breakdown = TRUE)
ttm_before <- ttm_before |>
filter(from_id != to_id) |>
mutate(origin_destination = paste0(from_id, " to ", to_id)) |>
summarize(mean_traveltime_before = mean(total_time),
mean_walk_time_before = mean(access_time + egress_time),
mean_transfer_time_before = mean(transfer_time),
.by = origin_destination)
ttm_after <- expanded_travel_time_matrix(r5r_core = r5r_core,
origins = poi,
destinations = poi,
mode = mode,
departure_datetime = departure_datetime_after,
max_walk_time = max_walk_time,
max_trip_duration = max_trip_duration,
time_window = 30,
breakdown = TRUE)
ttm_after <- ttm_after |>
filter(from_id != to_id) |>
mutate(origin_destination = paste0(from_id, " to ", to_id)) |>
group_by(origin_destination) |>
summarize(from_id,
to_id,
mean_traveltime_after = mean(total_time),
mean_walk_time_after = mean(access_time + egress_time),
mean_transfer_time_after = mean(transfer_time)) |>
distinct(origin_destination, .keep_all = TRUE)
before_after <- ttm_before |>
left_join(ttm_after, by = c("origin_destination")) |>
mutate(change_total = (mean_traveltime_after-mean_traveltime_before)/abs(mean_traveltime_before),
change_walk = (mean_walk_time_after-mean_walk_time_before)/abs(mean_walk_time_before),
change_transfer = (mean_transfer_time_after - mean_transfer_time_before) / abs(mean_transfer_time_before)) |>
select(-change_transfer)
before_after |>
select(from_id,
to_id,
mean_traveltime_before,
mean_traveltime_after,
change_total,
mean_walk_time_before,
mean_walk_time_after,
change_walk) |>
reactable(
searchable = FALSE,
defaultSorted = "from_id",
columns = list(
from_id = colDef(name = "From",
filterable = TRUE),
to_id = colDef(name = "To",
filterable = TRUE),
change_total = colDef(format = colFormat(percent = TRUE, digits = 1),
name = "change total time"),
change_walk = colDef(format = colFormat(percent = TRUE, digits = 1),
name = "change walk time"),
mean_traveltime_after = colDef(format = colFormat(digits = 0),
name = "total time after"),
mean_traveltime_before = colDef(format = colFormat(digits = 0),
name = "total time before"),
mean_walk_time_after = colDef(format = colFormat(digits = 0),
name = "walk time after"),
mean_walk_time_before = colDef(format = colFormat(digits = 0),
name = "walk time before")
)
)
Text and figures are licensed under Creative Commons Attribution CC BY-SA 4.0. The figures that have been reused from other sources don't fall under this license and can be recognized by a note in their caption: "Figure from ...".
For attribution, please cite this work as
Kliems (2023, April 24). Harald Kliems: Before and after the Metro Network Redesign. Retrieved from https://haraldkliems.netlify.app/posts/before-and-after-the-metro-network-redesign/
BibTeX citation
@misc{kliems2023before, author = {Kliems, Harald}, title = {Harald Kliems: Before and after the Metro Network Redesign}, url = {https://haraldkliems.netlify.app/posts/before-and-after-the-metro-network-redesign/}, year = {2023} }