processing.link_trips
processing.link_trips.link
Trip Linker Module.
MODE_TYPE_TO_ACCESS_EGRESS
module-attribute
MODE_TYPE_TO_ACCESS_EGRESS = {
ModeType.WALK.value: AccessEgressMode.WALK.value,
ModeType.BIKE.value: AccessEgressMode.BICYCLE.value,
ModeType.BIKESHARE.value: AccessEgressMode.BICYCLE.value,
ModeType.SCOOTERSHARE.value: AccessEgressMode.MICROMOBILITY.value,
ModeType.TAXI.value: AccessEgressMode.TNC.value,
ModeType.TNC.value: AccessEgressMode.TNC.value,
ModeType.CAR.value: AccessEgressMode.CAR_HOUSEHOLD.value,
ModeType.CARSHARE.value: AccessEgressMode.CAR_OTHER.value,
ModeType.SCHOOL_BUS.value: AccessEgressMode.TRANSFER_BUS.value,
ModeType.SHUTTLE.value: AccessEgressMode.TRANSFER_BUS.value,
ModeType.FERRY.value: AccessEgressMode.TRANSFER_OTHER.value,
ModeType.TRANSIT.value: AccessEgressMode.TRANSFER_OTHER.value,
ModeType.LONG_DISTANCE.value: AccessEgressMode.TRANSFER_OTHER.value,
ModeType.OTHER.value: AccessEgressMode.OTHER.value,
ModeType.MISSING.value: AccessEgressMode.MISSING.value,
}
ModeType to AccessEgressMode mapping for transit access/egress.
Maps travel mode types to access/egress mode categories used in transit trip analysis. This mapping is used when aggregating linked trips to classify non-transit segments as access or egress modes for transit journeys.
link_trips
link_trips(
unlinked_trips: pl.DataFrame,
change_mode_enum: str,
transit_mode_enums: list[str],
max_dwell_time: float = 120,
dwell_buffer_distance: float = 100,
*,
split_on_occupancy: bool
) -> dict[str, pl.DataFrame]
Link unlinked trip segments into complete journey records.
Detects mode changes and aggregates trip chains by validating spatial and temporal continuity across consecutive trips.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unlinked_trips
|
pl.DataFrame
|
Individual trip records with person_id, day_id, depart_time, arrive_time, o/d locations, o/d purposes, mode_type. |
required |
change_mode_enum
|
str
|
Enum label indicating a mode change purpose. |
required |
transit_mode_enums
|
list[str]
|
List of enum labels that count as transit modes. |
required |
max_dwell_time
|
float
|
Maximum time gap between trips to link them, in minutes (default: 120). |
120
|
dwell_buffer_distance
|
float
|
Maximum spatial distance between trips to link, in meters (default: 100). |
100
|
split_on_occupancy
|
bool
|
Refuse to link two segments whose reported party size differs. A change of occupancy at a stop is evidence that somebody was picked up or dropped off, which makes the stop an activity rather than a transfer. Required, and deliberately without a default: either answer changes what a linked trip is, so the choice belongs to whoever configured the run rather than to this signature. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, pl.DataFrame]
|
Dictionary containing: - unlinked_trips: Original trips with added linked_trip_id column - linked_trips: Aggregated journey records with combined attributes |
Algorithm
Phase 1: Link Trip IDs
- Sort unlinked trips by person, day, and departure time
- For each person-day sequence:
- If previous trip's destination purpose is change_mode_enum, continue current linked trip
- Validate spatial/temporal continuity:
- Time gap between trips ≤ max_dwell_time minutes
- Distance between previous destination and current origin ≤ dwell_buffer_distance meters
- With split_on_occupancy, the reported party size is unchanged
- Otherwise, start a new linked trip
- Assign globally unique linked_trip_id: the day_id followed by a two-digit sequence number
Phase 2: Aggregate Linked Trips
- Group unlinked trips by linked_trip_id
- For each linked trip, aggregate:
- Origin/Destination: First trip's origin, last trip's destination
- Timing: First depart_time, last arrive_time
- Distance: Sum of all trip distances
- Duration: Sum of all trip durations (including dwell time)
- Purposes: First origin purpose, last destination purpose
- Mode Logic:
- If any segment uses a transit mode → the mode of the longest-duration transit segment
- Otherwise, the mode of the longest-duration segment
- Transit Details: Count boarding/alighting, aggregate access/egress modes
- Driver/Passenger: Aggregate from component trips
Notes
- Links trips when travelers make intermediate stops for mode changes or transfers
- Preserves full trip detail in unlinked_trips while creating journey-level linked_trips
- Transit detection ensures multi-modal journeys classified correctly
- Access/egress mode mapping converts trip modes to transit-specific codes
- Spatial/temporal thresholds prevent false linkages across separate journeys
link_trip_ids
link_trip_ids(
unlinked_trips: pl.DataFrame,
change_mode_enum: str,
max_dwell_time: float = 120,
dwell_buffer_distance: float = 100,
*,
split_on_occupancy: bool
) -> pl.DataFrame
Link trips based on purpose and mode in time sequence.
Logic: For each person's day of trips: - Sort trips by departure time - If previous trip's destination purpose is 'change_mode', continue the linked trip; else start a new linked trip. - Assign linked trip IDs accordingly. Linked trip IDs are made globally unique by combining day_id and local linked trip index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unlinked_trips
|
pl.DataFrame
|
DataFrame containing trip data |
required |
change_mode_enum
|
str
|
Enum label indicating a mode change |
required |
max_dwell_time
|
float
|
Maximum time gap between trips to link them (minutes) |
120
|
dwell_buffer_distance
|
float
|
Maximum distance between trips to link (meters) |
100
|
split_on_occupancy
|
bool
|
Also break the link where the reported party size
changes between segments. Required; see |
required |
Returns:
| Type | Description |
|---|---|
pl.DataFrame
|
DataFrame with linked_trip_id column added |
Raises:
| Type | Description |
|---|---|
ValueError
|
split_on_occupancy is set but the trips carry no num_travelers column to compare. |
aggregate_linked_trips
aggregate_linked_trips(
unlinked_trips: pl.DataFrame, transit_mode_enums: list[str]
) -> pl.DataFrame
Aggregate linked trips into single records, summarizing key info.
Logic: For each linked trip: - keep the first trips depart_ and o_ fields - keep the last trips arrive_ and d_ fields - Mode is based on hierarchy. Simple case: If: transit is involved in any trip segment, use transit mode. Else: Use mode of longest duration trip segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unlinked_trips
|
pl.DataFrame
|
DataFrame with linked_trip_id column |
required |
transit_mode_enums
|
list[str]
|
List of mode enums that count as transit |
required |
Returns:
| Type | Description |
|---|---|
pl.DataFrame
|
Aggregated DataFrame with one row per linked trip |