Assemble Frequency-Based Realized GTFS Feeds (One per Reliability Quantile)
Source:R/frequencies.R
rt2s_frequencies.RdCollapses many observed runs into compact, frequency-based schedules: one
representative trip per (route, direction, window) with a
frequencies.txt headway and a representative stop pattern, emitted at
several reliability quantiles. The typical output is three feeds -
structural (free-flow, p05), median (p50), and reliable
(p95). By default each applies its quantile to both travel time and
headway, so the reliable feed is slower with longer headways than the
structural one; pass a list to quantiles to decouple the two (see
below).
Usage
rt2s_frequencies(
events = NULL,
windows,
quantiles = c(structural = 0.05, median = 0.5, reliable = 0.95),
agency = NULL,
stops = NULL,
route_type = 3L,
service_id = "SVC1",
service_dates = NULL,
exact_times = 0L,
feed_lang = "en",
feed_contact_email = NULL,
feed_contact_url = NULL,
strict = FALSE,
max_headway_secs = 3L * 3600L,
headway_method = c("trip_start", "passage"),
reference_stops = NULL,
min_revisit_gap_s = 600L,
baseline = NULL,
pattern_source = c("observed", "baseline"),
scaling = NULL,
scaling_missing = c("error", "drop"),
headways = NULL,
headway_groups = NULL,
route_key = c("route_id", "route_short_name"),
extra_trips = NULL,
strict_within_window = FALSE,
closed_last = FALSE
)Arguments
- events
Observed stop events (see observed-stop-events). Restrict them to the service dates that form one service pattern before calling (e.g. weekdays only) - no day-type filtering is imposed here.
May be
NULLwhenheadway_groupsis supplied, in which case no headway analytics run at all and every headway must come fromheadways.NULLwithoutheadway_groupsis an error: there would be no candidate headway group.- windows
Named list of
c(start, end)time strings defining the frequency windows, passed tort2s_time_window(overnight windows such asc("22:00", "26:00")are supported). The window name"other"is reserved for unassigned service times. Whenstrict_within_window = TRUE, configured windows must not overlap. Required: a frequency-based feed needs defined windows. Trips outside every window are not emitted.- quantiles
Reliability quantiles in
[0, 1]; one feed is produced per entry, named by its name. Two spellings are accepted:a named numeric vector - one probability per scenario, applied to both travel time and headway. Default
c(structural = 0.05, median = 0.5, reliable = 0.95).a named list whose elements are either a single probability (coupled, as above) or a numeric named
traveland/orheadway, which decouples the two. A side that is omitted inherits the side that is given. This is what expresses a free-flow scenario at typical frequency, e.g.list(structural = c(travel = 0.05, headway = 0.50), median = 0.50, reliable = 0.95)- a p05 running time at the median headway, not a p05 headway.
quantilesis the single source of scenario identity in every mode: its names define which feeds are emitted, andscaling/headwaysmay only refer to those names. Underpattern_source = "baseline"thetravelside is inert (the pattern comes frombaselinescaled byscaling), so a scenario may give onlyc(headway = ...).- agency, stops, route_type, feed_lang, feed_contact_email, feed_contact_url, strict
As in
rt2s_scaffold- agency metadata, stop coordinates, route type, feed language/contacts, and the strict publish gate. Missing agency or stop coordinates are recorded as publish blockers on every returned feed (or error understrict).- service_id
Identifier for the single synthesized service; its
calendar.txtrow is active on the weekdays present in the resolved service dates, over their range. Default"SVC1".- service_dates
Optional
Datevector giving the days this feed describes. It replaces the dates that would otherwise be read fromevents$service_date, and is reduced by the same rule: weekday flags plus the minimum and maximum date. Required wheneventsisNULL;rt2s_baseline_service_datesis the way to inherit the baseline's own days.Dates inside
[min, max]whose weekday is served but which are absent from this vector are written tocalendar_dates.txtwithexception_type = 2, so a span with holes in it is not overstated as uninterrupted service. A date set with no holes emits nocalendar_dates.txtat all.Supplying
headway_groupswithoutservice_dateswarns when the supplied groups widen the feed past whateventscovers: the calendar then still describes only the observed span.- exact_times
frequencies.exact_times:0(default, frequency-based) or1(schedule-based).- max_headway_secs
Passed to
rt2s_obs_headways; gaps above it are treated as between-service breaks. Default 10800 (3 h).- headway_method
How to estimate headways, passed to
rt2s_obs_headwaysas itsmethod."trip_start"(default) requires one usabletrip_refper run;"passage"measures intervals at one direction-unique reference stop per route-direction. This changes only the frequency headway; representative travel-time patterns still come fromrt2s_obs_travel_timesand need events whosetrip_refvalues make stop offsets meaningful.The argument is deliberately not named
methodhere, even though that is whatrt2s_obs_headwayscalls it. The two live at different altitudes: inside a 20-argument assembler that also chooses apattern_sourceand ascaling_missingpolicy, a baremethoddoes not say method of what. It is not an oversight to be tidied up.- reference_stops, min_revisit_gap_s
Passed to
rt2s_obs_headwayswhenheadway_method = "passage". Explicit values are ignored with a warning whenheadway_method = "trip_start".- baseline
Optional planned static GTFS feed to anchor stop patterns on: a gtfsio/gtfstools-style object or a path to a GTFS zip. Required with
pattern_source = "baseline"and rejected without it.Its
agencyandstopsbecome the defaults for those arguments (an explicit value still wins) and itsroutesrows are inherited so the emittedroute_typestays the operator's own rather than the scaffold default.calendarandshapesare deliberately not inherited: the calendar describes planned service while this feed describes the observed span, and the representative trips reference no baseline shape.Identity contract: the
route_refofeventsand ofheadway_groupsmust carry the same identifier as the baseline'strips.route_id(orroutes.route_short_nameunderroute_key), anddirection_idmust agree. A completely disjoint key set is an error; candidate keys with no baseline pattern are dropped with a warning, and baseline routes that are in no candidate headway group are reported and skipped rather than given an invented headway.- pattern_source
Where the representative stop pattern comes from.
"observed"(default) reconstructs it fromeventsviart2s_obs_travel_times."baseline"anchors on the published pattern frombaseline(seert2s_baseline_patterns) and scales it byscaling, so every scenario emits the same stops in the same order. See the section above for which regime fits.- scaling
Running-time ratios, required with
pattern_source = "baseline". A data.frame with one row perroute_ref,direction_id,window,scenarioand aratiocolumn: the factor by which that scenario stretches the planned stop-to-stop offsets (1 keeps them unchanged, 1.2 is 20\ Ratios must be finite and strictly positive; they are not clamped, so any plausibility bounds belong to whatever estimated them.scenariovalues must be names ofquantiles.- scaling_missing
What to do when
scalinghas no ratio for an emitted headway group/scenario pair."error"(default) reports the offending pairs."drop"removes those trips from every scenario, with a warning - never from just the scenario that lacks a ratio, which would leave the feeds with different trip sets.- headways
Optional per-group headway override: a data.frame keyed
route_ref,direction_id,window,scenariowith a positiveheadway_secs. It supersedes the quantile-derived headway for exactly the headway groups it lists; unlisted groups keep their observed value. This is how a scenario whose frequency does not come from observations at all - a planned "scheduled" feed, viart2s_baseline_headways- is expressed without a second way of naming scenarios. Rows naming a headway group that is not emitted are ignored with a warning.A group with no resolvable headway in some scenario - no observed quantile and no override - is dropped from every scenario with
drop_reason = "no_within_window_headway"or"no_headway", exactly asscaling_missing = "drop"behaves and for the same shared-trip-set reason. Useextra_tripsto carry service that cannot be written as a repeating headway.- headway_groups
Optional candidate headway groups, supplied rather than derived: a data.frame keyed
route_ref,direction_id,window- thescaling/headwayskey minusscenario, because candidacy is a property of the group and not of the scenario. Valid only withpattern_source = "baseline"; passing it under"observed"is an error, since an observed pattern can only be reconstructed for a group that has events.Candidacy becomes the events-derived groups union these, so a group with no observed runs is still emitted when
scalinggives it a ratio andheadwaysgives it a headway. A supplied group with no baseline stop pattern is not an error: it warns and appears inrt2s_resolved_gridwithdrop_reason = "no_stop_pattern", which is what makes a caller's drop funnel able to see it.- route_key
Which baseline column supplies route identity, passed to
rt2s_baseline_patterns. Baseline mode only.- extra_trips
Optional individually-timed trips to add to the emitted feeds, as a named list keyed by scenario name - the same names as
quantiles, which is the single source of scenario identity, so a misspelled scenario is an error rather than a silent no-op. Each element is alist(trips=, stop_times=):trips: requiredtrip_idandroute_id; optionaldirection_idandservice_id. An absentservice_idis stamped with this feed's single synthesized service; a different one is an error.stop_times: requiredtrip_id,arrival_time,departure_time,stop_id,stop_sequence. Times are absolute clock strings ("HH:MM:SS", hours >= 24 allowed for trips running past midnight), not offsets from00:00:00as the generated frequency trips use, and must be non-decreasing along the trip.
Any other element of the list is ignored, so a builder that also returns provenance can be passed through unchanged.
These trips get no
frequencies.txtrow. That is what makes them exact-time trips: per the GTFS specification only trips listed infrequencies.txtare frequency-based, and the rest are read fromstop_timesas scheduled times. A feed may mix the two, so this is a standard-compliant feed, not a workaround - it is how a headway group that cannot be expressed as a repeating headway is carried. Since a group with no resolvable headway is now a drop, this is the only way to carry such service.Their stops and routes are added to
stops.txtandroutes.txt, but every referencedstop_idmust already be known (fromstopsor an emitted pattern) and everyroute_idmust be an emitted or baseline route: a dangling reference is an invalid feed and is rejected rather than filled in. No extratrip_idmay collide with a generatedroute_direction_windowid.No cross-scenario invariant is imposed. A scenario may supply more, fewer or no extra trips than another, because the exact-time evidence for a headway group legitimately differs by scenario. The shared-trip-set guarantee below is scoped to the generated frequency trips, which is where
scaling_missingenforces it.Extra trips are not rows of
rt2s_resolved_grid: the grid is one row per candidate(route, direction, window)headway group and extra trips are not groups. Reconciling a feed'strips.txttherefore meansc(grid[emitted == TRUE]$trip_id, <the ids you supplied>); the caller supplies the extra trips, so it already owns those ids.- strict_within_window
Passed to
rt2s_obs_headwayswhen estimating headways from events. Logical; defaultFALSE. WhenTRUE, configured windows must be pairwise non-overlapping.- closed_last
Logical; when
TRUEthe last window in list order is closed on its end, so an event exactly on it is inside that window rather than unassigned. Passed tort2s_obs_headways. Seert2s_time_window.
Value
A named list of gtfsio-convention feed objects, one per quantile
(e.g. $structural, $median, $reliable); write each
with gtfsio::export_gtfs(). Each carries publishable /
publish_blockers attributes (see rt2s_publishable).
All scenario feeds share one generated trip set by construction:
trip_id is route_direction_window and is resolved once, before
any scenario is built, so a contrast between two feeds is a contrast in
service levels only. Trips added through extra_trips are outside that
guarantee by design and may differ per scenario.
The list carries a resolved_grid attribute: one row per candidate
(route, direction, window) headway group and scenario, recording the
ratio and headway actually applied and, for groups that never reached the
feed, why. Read it with rt2s_resolved_grid. Groups dropped for
want of a stop pattern, a ratio or a headway are present and flagged rather
than absent, so the grid reconciles against a caller's own drop accounting.
With strict_within_window = TRUE, an observed group with fewer than
two starts inside a window is retained with
drop_reason = "no_within_window_headway".
Details
The representative stop_times are offsets from a 00:00:00 trip start
(exact_times = 0 frequency semantics: only relative offsets matter),
clamped non-decreasing. Spec-required surrounding files (agency, routes,
stops, calendar, feed_info) and the publish gate are built exactly as
rt2s_scaffold builds them.
Reconstructed versus anchored stop patterns
This function reconstructs a representative stop pattern from the
observations themselves, via rt2s_obs_travel_times and the canonical
cross-trip order of rt2s_obs_stop_order. That is the right regime
when no usable published pattern exists - GPS-only data, an operator with no
static feed, or a network whose published patterns do not match what runs.
It is the wrong regime when a published pattern does exist and the
analysis rests on the network being identical across scenarios. Because the
reconstructed stop set is derived from what was observed, two feeds built
from different observations can differ in their stops and stop order, and a
scheduled-versus-observed contrast then confounds "service got slower" with
"the network changed". For that design, anchor on the published
pattern instead and vary only service levels: see
rt2s_baseline_patterns and pattern_source = "baseline".
Where the candidate headway groups come from
A headway group is one (route_ref, direction_id, window) - the
object frequencies.txt describes, and what EN 12896 (Transmodel) calls
a headway journey group. One representative trip is emitted per group.
By default the candidate groups are derived from events: a group with
no observed runs is not a candidate. Under
pattern_source = "baseline" that is the wrong gate, because the pattern
comes from baseline, the ratio from scaling and the headway from
headways, so events contributes nothing to such a group's output.
headway_groups names those groups directly; candidacy then becomes the
events-derived groups union the supplied ones, and events may
be NULL entirely.
See also
rt2s_resolved_grid for the resolved grid,
rt2s_baseline_service_dates for service_dates.