Detect Habitual Locations
Habitual locations: find each person's anchors, and say which one a trip end is at.
A habitual location is an anchor: a place a person has a standing relationship with — their home, their workplace, their school — as opposed to somewhere they merely happened to stop. Two tables come out of here, because identity and presence have different existence conditions. A habitual location exists whether or not the person went there during the diary period — a reported workplace belonging to someone who teleworked all week is still their workplace — so it cannot live on a table keyed by day::
habitual_locations one row per (person, kind, number)
who / where / how we know
habitual_location_days one row per (location, day) the person was present
how long / how many visits / how the day related
For example::
habitual_locations
person type num is_primary source
1 WORK 1 True reported
1 WORK 2 False observed <- a Tue/Thu office
2 HOME 1 True reported
2 HOME 2 False reported <- a second home
The table is delivered by survey cleaning holding the reported locations —
the survey's home, work and school coordinates, plus any further ones such as
a vendor's second home — built by :func:reported_habitual_locations and
taken as given. The detect_habitual_locations step, after trip linking,
appends the observed ones and writes the day table; it never changes a delivered
row. Tour extraction and everything after only read it.
Observed locations:
- Observed homes, only when
include_observed_homesis on (it has no default): days the respondent said began or ended at home or at their other home, placed where that day's travel began or ended. Away from every reported home, such a place is another home of theirs. Off, the only homes are the reported ones. - Observed workplaces and schools: places the person went for that purpose and stayed long enough. Work-related stops count towards a workplace at their own longer cutoff: their median is under an hour, a meeting or a delivery, but a stay the length of a working day says the person works there.
Homes are never inferred from travel alone. A stay at "another residence" may be a second home or a friend's flat, and only the respondent can say which.
A single buffer (MatchConfig.buffer_meters) is the whole spatial
vocabulary. Stops within it of one another are one place; a place within it of a
reported location is that location, so it is not added again; and a trip end
within it of a location is at that location when its purpose agrees with the
location's kind (:func:matching.match_points). Only an agreeing purpose
counts: an unknown one matches nothing. The exception is a person's own front
door: within at_address_meters of their primary home, any purpose is at that
home, because there the purpose describes what they were doing rather than a
different place. Tour extraction asks the same question through
:func:match_trip_ends, with the same settings.
Modules:
detect_habitual_locations: the pipeline step.habitual_location_configs: the rules.reported: the delivered table, built from the survey's coordinates.episodes: trips as stays.observed: clustering, and the observed locations.numbering: primacy, numbering and identifiers.matching: the one "at a location" test.location_days: the per-day table.
The step
Find observed homes, workplaces and schools, and record presence per day.
Runs after trip linking. The table arrives from survey cleaning holding the
reported locations (see reported_habitual_locations); this appends what
the travel shows, and never changes a delivered row. See the package
docstring for the rules.
Example
- name: detect_habitual_locations
params:
buffer_meters: 300
include_observed_homes: true
Settings
| Key | Type | Default | Meaning |
|---|---|---|---|
at_address_meters |
number, > 0 | 100.0 |
Distance in metres within which a trip end is at the person's own primary home whatever its purpose. At their doorstep the purpose describes what they are doing -- exercising, working from home, dropping someone off -- not a different place, and refusing it welds two tours into one. Further out the purpose has to agree, because that is where the corner shop is. Homes only: a lunch place can be metres from the office door. |
buffer_meters |
number, > 0 | required | Distance in metres within which two points are the same place. It groups stops into one location, decides a found place is a reported one, decides a trip end is at a location, and decides a tour came back to its primary destination. Reported locations are geocoded from what the respondent typed while trip ends are GPS, and people walk about a site, so it is a few hundred metres rather than a building's width. Set it to suit the survey's location noise. |
include_observed_homes |
true / false | required | Whether to add homes found from where days were said to begin or end, beside the reported ones. An analyst's choice with no default: a 'home' answer away from the reported home becomes another home, and tours touching it are graded OTHER_HOME. |
min_dwell_minutes |
number, ≥ 0 | 90.0 |
Minimum length of one stop for it to count as evidence of a workplace or school, unless its purpose has its own entry in min_dwell_minutes_by_purpose. |
min_dwell_minutes_by_purpose |
mapping | built in | Per-purpose overrides of min_dwell_minutes. College is lower because a student may attend one class and leave. Work-related purposes are higher: their median stop is under an hour, a meeting or a delivery, and only a stay the length of a working day says the person works there. |
min_distinct_days |
whole number, ≥ 1 | 1 |
Optional minimum number of distinct travel days an observed location was visited. Defaults to 1 (no filter). |
Delivering the reported locations
Survey cleaning builds the delivered table with this helper.
Reported locations: the survey's own coordinates, as the delivered table.
habitual_locations is delivered by survey cleaning holding only the
reported locations, and :func:reported_habitual_locations is how every project
builds it, so the rows and their numbering are the same whoever delivers them.
reported_habitual_locations
reported_habitual_locations(
households: pl.DataFrame,
persons: pl.DataFrame,
extra: pl.DataFrame | None = None,
buffer_meters: float = DEFAULT_BUFFER_METERS,
) -> pl.DataFrame
Build the delivered habitual_locations table: every reported location.
The survey's home (household), work and school (person) coordinates are
each person's primaries. Any further reported locations — a vendor's
second home — come in extra and follow them, in the order given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
households
|
pl.DataFrame
|
Households with |
required |
persons
|
pl.DataFrame
|
Persons with |
required |
extra
|
pl.DataFrame | None
|
Optional further reported locations with |
None
|
buffer_meters
|
float
|
An extra location this close to its kind's primary is the same place and is dropped. |
DEFAULT_BUFFER_METERS
|
Returns:
| Type | Description |
|---|---|
pl.DataFrame
|
Table conforming to |