Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/source/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,20 @@ Result metrics use represented-person weights, so a `sample_size` of 1000 does n

Use this for French survey-based behaviour patterns from EMP 2018-2019.

```python
survey = mobility.EMPMobilitySurvey(
correct_zero_durations=True,
)
```

Main argument:

- `correct_zero_durations`: estimate short durations for activities reported
as zero between consecutive trips. The default is `False`.

Use the constructor argument shown above. Mobility applies the correction
while preparing and caching the survey.

Project-specific survey objects can also be passed to `PopulationGroupDayTrips`, but the parser and documentation should live in the project repository.

## Activities
Expand Down
22 changes: 22 additions & 0 deletions docs/source/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@ Survey transfer is a modelling assumption. National surveys provide detailed beh

Some survey codes still appear in model inputs or outputs. The [survey codes page](survey_codes.md) lists the main French codes for socio-professional categories, motives, and trip modes.

### Zero-Duration Activities

Some survey diaries give the same time for one trip's arrival and the next
trip's departure. This produces an activity with a reported duration of zero,
even though the person probably stopped briefly. For example, an arrival at
08:30 followed by a departure at 08:30 records a zero-minute activity.

You can ask Mobility to correct these records when it prepares the survey:

```python
survey = mobility.EMPMobilitySurvey(correct_zero_durations=True)
```

The correction is disabled by default. When enabled, Mobility estimates short
activity durations from the same survey and adjusts the two surrounding trip
times. It does not add, remove, or reorder trips. The activity after the final
trip is unchanged because the diary does not record when it ends.

Enabling this option changes the survey-derived activity-duration
distribution. Record the setting in the study assumptions and check the
resulting durations against suitable evidence.

### ENTD 2007-2008

The national transport and travel survey, `Enquete Nationale Transports Deplacements`, was run in 2007 and 2008. INSEE describes it as a survey about household trips, use of individual and collective transport modes, and the vehicle fleet owned by households.
Expand Down
15 changes: 15 additions & 0 deletions docs/source/research_method.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ Mobility first builds daily activity-motive sequences for population groups.

These sequences depend on survey behaviour and population characteristics such as socio-professional category, household car ownership, household size, and urban setting. Each sequence contains activity steps and expected activity-time needs.

If a survey enables `correct_zero_durations`, Mobility estimates plausible
short durations for activities whose preceding arrival equals their following
departure. This optional preprocessing changes only the two surrounding
clocks; it does not add, remove, or reorder trips.

Timing resolution is inferred for each diary. A reported zero from a
one-minute diary is corrected to one minute. For diaries whose clocks all lie
on five-minute boundaries, Mobility fits an interval-censored Gamma duration
distribution. In other words, a duration reported as 10 minutes is treated as
an observation within the range that could round to 10 minutes, rather than
as an exact measurement. Motives with enough observations are fitted
separately; sparse motives use the survey-wide fit. The correction is limited
by the travel time available on the two surrounding trips so neither trip
becomes negative. See [Data Sources](data.md) for the user-facing assumption.

The model then searches for destinations and mode sequences that can make the daily plan feasible under the current costs and opportunity constraints.

## Opportunity Capacity
Expand Down
7 changes: 7 additions & 0 deletions mobility/surveys/france/emp.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,23 @@ class EMPMobilitySurvey(MobilitySurvey):
def __init__(
self,
parameters: MobilitySurveyParameters | None = None,
*,
correct_zero_durations: bool | None = None,
):
"""Initialize EMP mobility survey with optional parameter overrides.

Args:
parameters: Optional pre-built survey parameters model.
correct_zero_durations: Correct activities recorded with zero
duration. Disabled by default.
"""
parameters = self.prepare_parameters(
parameters=parameters,
parameters_cls=MobilitySurveyParameters,
explicit_args={
"survey_name": "fr-EMP-2019",
"country": "fr",
"correct_zero_durations": correct_zero_durations,
},
owner_name="EMPMobilitySurvey",
)
Expand All @@ -66,6 +71,8 @@ def create_and_get_asset(self) -> dict[str, pd.DataFrame]:

self.download_survey_data(dataset_path)
self.parse_survey_data(dataset_path)
if self.inputs["parameters"].correct_zero_durations:
self.correct_zero_durations()

return self.get_cached_asset()

Expand Down
13 changes: 13 additions & 0 deletions mobility/surveys/france/entd.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,34 @@ class ENTDMobilitySurvey(MobilitySurvey):
def __init__(
self,
parameters: MobilitySurveyParameters | None = None,
*,
correct_zero_durations: bool | None = None,
):
"""Initialize ENTD mobility survey with optional parameter overrides.

Args:
parameters: Optional pre-built survey parameters model.
correct_zero_durations: Unsupported until the ENTD parser exposes
the survey's trip clock times. Passing ``True`` raises
``ValueError``.
"""
parameters = self.prepare_parameters(
parameters=parameters,
parameters_cls=MobilitySurveyParameters,
explicit_args={
"survey_name": "fr-ENTD-2008",
"country": "fr",
"correct_zero_durations": correct_zero_durations,
},
owner_name="ENTDMobilitySurvey",
)
if parameters.correct_zero_durations:
# ENTD contains trip times, but this parser does not load and
# standardize them yet.
raise ValueError(
"ENTDMobilitySurvey cannot correct zero-duration activities "
"until its parser exposes the survey's trip clock times."
)
super().__init__(parameters=parameters)


Expand Down
39 changes: 39 additions & 0 deletions mobility/surveys/mobility_survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from typing import Annotated, Any
from pydantic import BaseModel, ConfigDict, Field
from mobility.runtime.assets.file_asset import FileAsset
from mobility.surveys.zero_duration_activities import (
_correct_zero_duration_activities,
)


class MobilitySurvey(FileAsset):
Expand Down Expand Up @@ -81,6 +84,31 @@ def get_cached_asset(self) -> dict[str, pd.DataFrame]:
"""
return {k: pd.read_parquet(path) for k, path in self.cache_path.items()}

def correct_zero_durations(self) -> None:
"""Correct and persist zero-duration activities in this survey.

This runs once at survey creation, after the national parser has
written its standardized tables. The fit uses the survey's own trip
diaries and day weights; downstream consumers therefore receive the
same corrected ``short_trips`` table without owning correction logic.
"""
short_trips = pd.read_parquet(self.cache_path["short_trips"])
days_trip = pd.read_parquet(self.cache_path["days_trip"])
day_weights = (
days_trip.reset_index()[["day_id", "pondki"]]
.drop_duplicates("day_id")
)
weighted_trips = short_trips.reset_index().merge(
day_weights,
on="day_id",
how="left",
validate="many_to_one",
)
corrected = _correct_zero_duration_activities(weighted_trips)
corrected.drop(columns="pondki", inplace=True)
corrected.set_index("day_id", inplace=True)
corrected.to_parquet(self.cache_path["short_trips"])

class MobilitySurveyParameters(BaseModel):
"""Parameters used to configure a mobility survey asset."""

Expand All @@ -101,3 +129,14 @@ class MobilitySurveyParameters(BaseModel):
description="ISO-like country code used to map surveys to population inputs.",
),
]

correct_zero_durations: Annotated[
bool,
Field(
title="Correct zero-duration activities",
description=(
"Fit plausible short activity durations from this survey and "
"correct equal consecutive arrival and departure times."
),
),
] = False
Loading
Loading