Travel time and accessibility estimates require a departure time, and
they can differ significantly across departure times because public
transport service levels vary across the day (Stepniak et al. 2019). Even leaving at
10:00am instead of 10:04am can change results
considerably, depending on when vehicles arrive and how well transfers
are coordinated. This relates to the modifiable temporal unit problem
(MTUP) (Pereira 2019; Levinson and et al. 2020).
The uncertainty is greater when GTFS feeds have a
frequencies.txt table, because exact vehicle departure
times are unknown (Conway et al. 2018; Stewart
and Byrd 2022).
A common strategy is to compute estimates for multiple departure times over a time window and take the average or median. Repeating the routing for each departure is slow, though.
In r5r, functions such as
travel_time_matrix() and accessibility() have
a time_window parameter that does this in a single call.
This vignette shows how to use it and interpret the results.
time_window works and how to interpret the
results.With time_window, R5 computes an estimate for
a departure every minute from departure_datetime until the
end of the window. If the GTFS feeds have a frequencies.txt
table, draws_per_minute sets the number of Monte Carlo
draws per minute (default 5, i.e. 300 draws in a 60-minute window);
without frequencies, departures are deterministic and
draws_per_minute does not affect results. For the effect of
the number of draws on result stability, see Stewart et al (2022).
This holds for the default routing algorithm (FastRaptor). When a
fare_structure is passed to
travel_time_matrix(), accessibility() or
travel_time_surface(), always in
pareto_frontier(), and in
detailed_itineraries() with
shortest_path = FALSE, R5 uses the McRaptor
algorithm instead. McRaptor does not route a departure every minute: it
routes time_window * draws_per_minute departure times
sampled at random within the window (consecutive departures are, on
average, 1 / draws_per_minute minutes apart, each origin
gets its own departure times, and the same ones in every call with the
same origin and seed). So with McRaptor,
draws_per_minute affects results even if the GTFS feeds
have no frequencies.txt table.
pareto_frontier() and detailed_itineraries()
fix it at 1.
The result is not a single estimate but a distribution reflecting the
uncertainty within the window. The percentiles parameter
selects which percentiles to return. For example, a 25th-percentile
travel time of 15 minutes from A to B means that 25% of trips departing
within the window are shorter than 15 minutes.
time_window.build_network()We use the São Paulo (Brazil) sample data included in
r5r.
# increase Java memory
options(java.parameters = "-Xmx2G")
# load libraries
library(r5r)
library(sf)
library(data.table)
library(ggplot2)
library(dplyr)
# build a routable transport network with r5r
data_path <- system.file("extdata/spo", package = "r5r")
r5r_network <- build_network(data_path)
# routing inputs
mode <- c('walk', 'transit')
max_walk_time <- 30 # minutes
max_trip_duration <- 90 # minutes
# load origin/destination points
points <- fread(file.path(data_path, "spo_hexgrid.csv"))
# departure datetime
departure_datetime = as.POSIXct("13-05-2019 14:00:00",
format = "%d-%m-%Y %H:%M:%S")time_window.Number of schools reachable from each location within 45 minutes
(decay_function = "step", cutoffs = 45), for
departures between 2pm and 3pm (time_window = 60):
# estimate accessibility
acc <- r5r::accessibility(
r5r_network,
origins = points,
destinations = points,
opportunities_colnames = 'schools',
mode = mode,
max_walk_time = max_walk_time,
decay_function = "step",
cutoffs = 45,
departure_datetime = departure_datetime,
progress = FALSE,
time_window = 60,
percentiles = c(10, 20, 50, 70, 80)
)
head(acc, n = 10)The output is in long format, one row per origin and percentile, so the first 5 rows refer to the same origin. The 10th percentile counts the schools reachable in at least 10% of departures, the 50th those reachable in at least half of them; so accessibility never increases as the percentile rises. An accessibility of 0 means no school is reachable within 45 minutes.
The plot shows, for each origin (sorted by median), the range of accessibility between the 10th and 80th percentiles, with the median as a dot:
# summarize
df <- acc[, .(min_acc = min(accessibility),
median = accessibility[which(percentile == 50)],
max_acc = max(accessibility)), by = id]
# plot
ggplot(data=df) +
geom_linerange(color='gray', alpha=.5, aes(x = reorder(id, median) ,
y=median, ymin=min_acc, ymax=max_acc)) +
geom_point(color='#0570b0', size=.5, aes(x = reorder(id, median), y=median)) +
labs(y='N. of schools accessible\nby public transport', x='Origins sorted by accessibility',
title="Accessibility uncertainty between 2pm and 3pm",
subtitle = 'Upper limit 10% and lower limit 80% of the times') +
theme_classic() +
theme(axis.text.x=element_blank(),
axis.ticks.x=element_blank())time_window.All-to-all travel times for departures between 2pm and 3pm:
# estimate travel time matrix
ttm <- travel_time_matrix(
r5r_network,
origins = points,
destinations = points,
mode = mode,
max_walk_time = max_walk_time,
max_trip_duration = max_trip_duration,
departure_datetime = departure_datetime,
progress = TRUE,
time_window = 60,
percentiles = c(10, 20, 50, 70, 80)
)
head(ttm, n = 10)Each travel_time_pXX column gives the travel time within
which XX% of the trips departing between 2pm and 3pm are completed:
travel_time_p10 for the fastest 10%,
travel_time_p50 the median, and so on. An NA
means fewer than XX% of the trips arrive within
max_trip_duration (90 minutes). A wider
time_window can include departures with worse service, so
it can produce more NAs in the higher percentiles.
time_window.With time_window,
expanded_travel_time_matrix() returns the fastest route
departing at each minute of the window, instead of percentiles. It can
be very memory intensive for large data sets and time windows.
time_window.detailed_itineraries() returns itineraries rather than
travel time or accessibility percentiles. With time_window,
it returns the fastest itinerary: the shortest travel time measured from
its own departure, so it may depart and arrive later than others (use
time_window = 1 for the earliest arrival). With
shortest_path = FALSE, it also returns sub-optimal
alternatives.
With the default shortest_path = TRUE and no
fare_structure, it routes one departure at each minute of
time_window, as travel_time_matrix() does with
schedule-based GTFS; ties between equally fast itineraries go to the
earliest departure.
With shortest_path = FALSE or a
fare_structure, it uses the McRaptor algorithm with one
draw per minute (see Section 2): consecutive departures are 30 to 90
seconds apart, so a 10-minute time_window simulates about
10 departures. Each departure is routed separately, and with the default
suboptimal_minutes = 0 only the itineraries arriving
earliest are kept for each departure, even if a slower one has fewer
transfers.
obs. detailed_itineraries() cannot route public
transport trips on frequency-based GTFS feeds; use
gtfstools::frequencies_to_stop_times() to create a suitable
feed.
Stop the network and run Java’s garbage collector to free the memory it used:
If you have any suggestions or want to report an error, please visit the package GitHub page.