Preprocessing
Clean, segment, thin and summarise trajectories, detect stops and places, and convert positions to H3 cells or origin-destination flows. Every function accepts any eager dataframe and returns the same backend. For a worked example on real GPS data, see Clean noisy GPS data and find stops; for the ideas behind the choices, see Cleaning and segmentation.
Clean and reduce
| API | Description | Example |
|---|---|---|
filter |
Filter trajectory noise by removing outlier points using a named algorithm. | Remove implausible jumps |
compress |
Compress trajectory by collapsing nearby points into single representative points. | Reduce the number of points |
simplify |
Simplify a trajectory by dropping points using a named algorithm. | Reduce the number of points |
segment |
Partition a trajectory into segments using a named algorithm. | Split each trace at recording gaps |
Stops and places
| API | Description | Example |
|---|---|---|
stay_locations |
Detect stay locations (stops) in trajectory data. | Detect stops |
cluster |
Cluster stop locations with H3 cells and connected components. | Group stops into locations |
identify_locations |
Label each user's recurring Locations as "home", "work", or "other". |
Functional forms |
create_activity_flag |
Flag each staypoint as a genuine "activity" (vs. a transient stop) by dwell time. | Functional forms |
Trips and travel modes
| API | Description | Example |
|---|---|---|
trajectory_to_trips |
Build a fastmob.Trips row for each consecutive ping pair. |
Common part of commuters and links |
trajectory_to_od |
Build a long-format Origin-Destination matrix directly from a raw trajectory. | From check-ins to flows |
predict_transport_mode |
Classify each tripleg's transport mode from its average speed. | Functional forms |
calculate_modal_split |
Aggregate a mode-labeled Triplegs table into a modal-split table. |
Functional forms |
H3 cells
| API | Description | Example |
|---|---|---|
latlng_to_h3 |
Convert each row's (lat, lng) to an H3 cell index, Rust-accelerated. | Homes and visits per place |
h3_to_latlng |
Convert each H3 cell to its geographic center coordinates. | Common part of commuters and links |
Phone records
| API | Description | Example |
|---|---|---|
cdr_to_trips_df |
Derive a CDR trips dataframe from cdr_to_visitation_df output. |
Mobile-phone records |
cdr_to_visitation_df |
Convert raw CDR records into a NetMob-like visitation dataframe. | Mobile-phone records |
filter(traj, max_speed_kmh=500.0, include_loops=False, speed_kmh=5.0, max_loop=6, ratio_max=0.25, *, datetime_col=None, lat_col=None, lng_col=None, uid_col=None, is_sorted=False, method='speed', **method_kwargs)
Filter trajectory noise by removing outlier points using a named algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
max_speed_kmh
|
float
|
Parameters for the default |
500.0
|
include_loops
|
bool
|
Parameters for the default |
False
|
speed_kmh
|
float
|
Parameters for the default |
5.0
|
max_loop
|
int
|
Parameters for the default |
6
|
ratio_max
|
float
|
Parameters for the default |
0.25
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
is_sorted
|
Whether the trajectory is already sorted by user and time, with nulls
and NaNs removed. Setting this to |
False
|
|
method
|
str
|
Name of the outlier-detection algorithm to run. One of |
'speed'
|
**method_kwargs
|
Any
|
Method-specific parameters for
|
{}
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Filtered trajectory in the same backend as input. |
Examples:
>>> import pandas as pd
>>> import fastmob
>>> url = fastmob.data.BRIGHTKITE_SAMPLE
>>> df = pd.read_csv(
... url,
... sep="\t",
... header=0,
... nrows=5000,
... names=["uid", "datetime", "lat", "lng", "location id"],
... )
>>> df["datetime"] = pd.to_datetime(df["datetime"], errors="coerce")
>>> df["location_id"] = df["location id"].astype("string")
>>> df = df.dropna(subset=["uid", "datetime", "lat", "lng"])[
... ["uid", "datetime", "lat", "lng", "location_id"]
... ]
>>> print(df.head().to_string(index=False))
uid datetime lat lng location_id
0 2010-10-16 06:02:04+00:00 39.891383 -105.070814 7a0f88982aa015062b95e3b4843f9ca2
0 2010-10-16 03:48:54+00:00 39.891077 -105.068532 dd7cd3d264c2d063832db506fba8bf79
0 2010-10-14 18:25:51+00:00 39.750469 -104.999073 9848afcc62e500a01cf6fbf24b797732f8963683
0 2010-10-14 00:21:47+00:00 39.752713 -104.996337 2ef143e12038c870038df53e0478cefc
0 2010-10-13 23:31:51+00:00 39.752508 -104.996637 424eb3dd143292f9e013efa00486c907
>>> from fastmob.preprocessing import filter
>>> filtered = filter(df, max_speed_kmh=500.0)
>>> print(len(filtered))
4898
>>> print(filtered.head().to_string(index=False))
uid datetime lat lng location_id
0 2009-05-25 20:56:10+00:00 37.774929 -122.419415 ee81ef22a22411ddb5e97f082c799f59
0 2009-05-25 21:35:28+00:00 37.600747 -122.382376 248b82709e6c11ddbf68003048c0801e
0 2009-05-25 21:37:44+00:00 37.600747 -122.382376 248b82709e6c11ddbf68003048c0801e
0 2009-05-25 21:42:47+00:00 37.600747 -122.382376 248b82709e6c11ddbf68003048c0801e
0 2009-05-25 22:13:23+00:00 37.615223 -122.389979 be2f1e669cc111dd9a50003048c0801e
References
- [Z2015] Zheng, Y. (2015) Trajectory data mining: an overview. ACM Transactions on Intelligent Systems and Technology 6(3), https://dl.acm.org/citation.cfm?id=2743025
- [PT2020] Pedrido, M.O. (2020). Hampel filter. GitHub repository, https://github.com/MichaelisTrofficus/hampel_filter
- [CKMSS2019] Custers, B., van de Kerkhof, M., Meulemans, W., Speckmann, B., & Staals, F. (2019). Maximum physically consistent trajectories. SIGSPATIAL 2019.
Worked example: Remove implausible jumps
compress(traj, spatial_radius_km=0.2, *, datetime_col=None, lat_col=None, lng_col=None, uid_col=None)
Compress trajectory by collapsing nearby points into single representative points.
All points within spatial_radius_km from an initial point are collapsed into one point with median lat/lng and the initial point's timestamp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
spatial_radius_km
|
float
|
Minimum distance (km) between consecutive output points. |
0.2
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Compressed trajectory in the same backend as input. |
Examples:
>>> import pandas as pd
>>> import fastmob
>>> url = fastmob.data.BRIGHTKITE_SAMPLE
>>> df = pd.read_csv(
... url,
... sep="\t",
... header=0,
... nrows=5000,
... names=["uid", "datetime", "lat", "lng", "location id"],
... )
>>> df["datetime"] = pd.to_datetime(df["datetime"], errors="coerce")
>>> df["location_id"] = df["location id"].astype("string")
>>> df = df.dropna(subset=["uid", "datetime", "lat", "lng"])[
... ["uid", "datetime", "lat", "lng", "location_id"]
... ]
>>> print(df.head().to_string(index=False))
uid datetime lat lng location_id
0 2010-10-16 06:02:04+00:00 39.891383 -105.070814 7a0f88982aa015062b95e3b4843f9ca2
0 2010-10-16 03:48:54+00:00 39.891077 -105.068532 dd7cd3d264c2d063832db506fba8bf79
0 2010-10-14 18:25:51+00:00 39.750469 -104.999073 9848afcc62e500a01cf6fbf24b797732f8963683
0 2010-10-14 00:21:47+00:00 39.752713 -104.996337 2ef143e12038c870038df53e0478cefc
0 2010-10-13 23:31:51+00:00 39.752508 -104.996637 424eb3dd143292f9e013efa00486c907
>>> from fastmob.preprocessing import compress
>>> compressed = compress(df, spatial_radius_km=0.2)
>>> print(len(compressed))
3173
>>> print(compressed.head().to_string(index=False))
uid datetime lat lng location_id
0 2009-05-25 20:56:10+00:00 37.774929 -122.419415 ee81ef22a22411ddb5e97f082c799f59
0 2009-05-25 21:35:28+00:00 37.600747 -122.382376 248b82709e6c11ddbf68003048c0801e
0 2009-05-25 22:13:23+00:00 37.615223 -122.389979 be2f1e669cc111dd9a50003048c0801e
0 2009-05-26 02:21:12+00:00 39.878664 -104.682105 e12721ce84e911dd8019003048c0801e
0 2009-05-26 04:59:44+00:00 39.739154 -104.984703 ee8b1d0ea22411ddb074dbd65f1665cf
References
- [Z2015] Zheng, Y. (2015) Trajectory data mining: an overview. ACM Transactions on Intelligent Systems and Technology 6(3), https://dl.acm.org/citation.cfm?id=2743025
Worked example: Reduce the number of points
simplify(traj, method='douglas_peucker', *, datetime_col=None, lat_col=None, lng_col=None, uid_col=None, is_sorted=False, **method_kwargs)
Simplify a trajectory by dropping points using a named algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
method
|
str
|
Name of the simplification algorithm to run. One of
|
'douglas_peucker'
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
is_sorted
|
bool
|
Whether the trajectory is already sorted by user and time. Setting this to True can speed up processing but may lead to incorrect results if the data is not properly preprocessed. |
False
|
**method_kwargs
|
Any
|
Method-specific parameters, forwarded to the matching
|
{}
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Simplified trajectory (row subset) in the same backend as input. |
Examples:
>>> import pandas as pd
>>> import fastmob
>>> url = fastmob.data.BRIGHTKITE_SAMPLE
>>> df = pd.read_csv(
... url,
... sep="\t",
... header=0,
... nrows=5000,
... names=["uid", "datetime", "lat", "lng", "location id"],
... )
>>> df["datetime"] = pd.to_datetime(df["datetime"], errors="coerce")
>>> df = df.dropna(subset=["uid", "datetime", "lat", "lng"])[
... ["uid", "datetime", "lat", "lng"]
... ]
>>> from fastmob.preprocessing import simplify
>>> simplified = simplify(df, method="douglas_peucker", epsilon_km=0.1)
>>> len(simplified) <= len(df)
True
References
- [DP1973] Douglas, D., & Peucker, T. (1973). Algorithms for the reduction of the number of points required to represent a digitized line or its caricature. The Canadian Cartographer 10(2), 112-122.
- [MdB2004] Meratnia, N., & de By, R.A. (2004). Spatiotemporal compression techniques for moving point objects. EDBT 2004.
- [CC1996] Chan, W. S., & Chin, F. (1996). Approximation of polygonal curves with minimum number of line segments or minimum error. International Journal of Computational Geometry & Applications, 6(1).
- [II1988] Imai, H., & Iri, M. (1988). Polygonal approximations of a curve — formulations and algorithms. Computational Morphology.
Worked example: Reduce the number of points
segment(traj, method='value_change', *, datetime_col=None, lat_col=None, lng_col=None, uid_col=None, is_sorted=False, **method_kwargs)
Partition a trajectory into segments using a named algorithm.
Unlike simplify/filter (which drop rows), segment never changes
row count: it adds an integer segment_id column aligned 1:1 with the
input, restarting at 0 for each user. Every shipped method is ported
from MovingPandas' trajectory_splitter.py module, adapted to
fastmob's hard row-preservation contract — where MovingPandas' splitters
drop short/parked/gap rows entirely (e.g. a min_length cutoff, or
every row strictly inside a detected stop), fastmob folds those rows
forward into a neighboring segment (or, for stop, gives them their
own bracketed segment) instead of dropping them. This is a deliberate,
documented semantic difference, not a bug — see each method's Rust
kernel docstring for the exact adaptation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
method
|
str
|
Name of the segmentation algorithm to run. One of
|
'value_change'
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
is_sorted
|
bool
|
Whether the trajectory is already sorted by user and time. Setting this to True can speed up processing but may lead to incorrect results if the data is not properly preprocessed. |
False
|
**method_kwargs
|
Any
|
Method-specific parameters, forwarded to the matching
|
{}
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Input trajectory with an added |
Examples:
>>> import pandas as pd
>>> import fastmob
>>> url = fastmob.data.BRIGHTKITE_SAMPLE
>>> df = pd.read_csv(
... url,
... sep="\t",
... header=0,
... nrows=5000,
... names=["uid", "datetime", "lat", "lng", "location id"],
... )
>>> df["datetime"] = pd.to_datetime(df["datetime"], errors="coerce")
>>> df = df.dropna(subset=["uid", "datetime", "lat", "lng"])[
... ["uid", "datetime", "lat", "lng"]
... ]
>>> from fastmob.preprocessing import segment
>>> segmented = segment(df, method="observation_gap", gap_s=3600.0)
>>> len(segmented) == len(df)
True
>>> "segment_id" in segmented.columns
True
References
- [GJH2020] Graser, A., Jankowski, P., & Heistermann, M. (2020). MovingPandas. https://github.com/movingpandas/movingpandas
Worked example: Split each trace at recording gaps
stay_locations(traj, minutes_for_a_stop=20.0, spatial_radius_km=0.2, leaving_time=True, no_data_for_minutes=1000000000000.0, min_speed_kmh=None, include_last=True, *, datetime_col=None, lat_col=None, lng_col=None, uid_col=None)
Detect stay locations (stops) in trajectory data.
A stop is detected when the individual stays within spatial_radius_km for at least minutes_for_a_stop minutes. Stop coordinates are median lat/lng.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
minutes_for_a_stop
|
float
|
Minimum duration (minutes) to qualify as a stop. |
20.0
|
spatial_radius_km
|
float
|
Radius (km) within which points are grouped into a stop. |
0.2
|
leaving_time
|
bool
|
If True, add a 'leaving_datetime' column with the departure time. |
True
|
no_data_for_minutes
|
float
|
Gap threshold (minutes) above which data is treated as missing. |
1000000000000.0
|
min_speed_kmh
|
float | None
|
If set, trim trailing high-speed points from the end of each stop. |
None
|
include_last
|
bool
|
Whether to close and emit the still-open stay at the end of a user's
track, i.e. one the user hadn't "stepped out of" yet when tracking
ended. Matches trackintel's |
True
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Stop locations in the same backend as input. Schema: [uid_col, lat_col, lng_col, datetime_col, (leaving_datetime)] |
Examples:
>>> import pandas as pd
>>> import fastmob
>>> url = fastmob.data.BRIGHTKITE_SAMPLE
>>> df = pd.read_csv(
... url,
... sep="\t",
... header=0,
... nrows=5000,
... names=["uid", "datetime", "lat", "lng", "location id"],
... )
>>> df["datetime"] = pd.to_datetime(df["datetime"], errors="coerce")
>>> df["location_id"] = df["location id"].astype("string")
>>> df = df.dropna(subset=["uid", "datetime", "lat", "lng"])[
... ["uid", "datetime", "lat", "lng", "location_id"]
... ]
>>> print(df.head().to_string(index=False))
uid datetime lat lng location_id
0 2010-10-16 06:02:04+00:00 39.891383 -105.070814 7a0f88982aa015062b95e3b4843f9ca2
0 2010-10-16 03:48:54+00:00 39.891077 -105.068532 dd7cd3d264c2d063832db506fba8bf79
0 2010-10-14 18:25:51+00:00 39.750469 -104.999073 9848afcc62e500a01cf6fbf24b797732f8963683
0 2010-10-14 00:21:47+00:00 39.752713 -104.996337 2ef143e12038c870038df53e0478cefc
0 2010-10-13 23:31:51+00:00 39.752508 -104.996637 424eb3dd143292f9e013efa00486c907
>>> from fastmob.preprocessing import stay_locations
>>> stops = stay_locations(df, spatial_radius_km=0.2, minutes_for_a_stop=20.0)
>>> print(len(stops))
3029
>>> print(stops.head().to_string(index=False))
lat lng datetime uid leaving_datetime
37.774929 -122.419415 2009-05-25 20:56:10 0 2009-05-25 21:35:28
37.600747 -122.382376 2009-05-25 21:35:28 0 2009-05-25 22:13:23
37.615223 -122.389979 2009-05-25 22:13:23 0 2009-05-26 02:21:12
39.878664 -104.682105 2009-05-26 02:21:12 0 2009-05-26 04:59:44
39.739154 -104.984703 2009-05-26 04:59:44 0 2009-05-26 16:43:59
References
- [RT2004] Ramaswamy, H. & Toyama, K. (2004) Project Lachesis: parsing and modeling location histories. In International Conference on Geographic Information Science, 106-124, http://kentarotoyama.com/papers/Hariharan_2004_Project_Lachesis.pdf
- [Z2015] Zheng, Y. (2015) Trajectory data mining: an overview. ACM Transactions on Intelligent Systems and Technology 6(3), https://dl.acm.org/citation.cfm?id=2743025
Worked example: Detect stops
cluster(traj, cluster_radius_km=0.1, min_samples=1, *, h3_resolution=None, datetime_col=None, lat_col=None, lng_col=None, uid_col=None, n_jobs=None)
Cluster stop locations with H3 cells and connected components.
Points are assigned to an H3 cell. Cells containing fewer than
min_samples points are noise; active cells touching at an edge are
joined into a cluster. cluster_radius_km selects a calibrated grid
resolution for compatibility with the previous DBSCAN API. Pass
h3_resolution (0--15) to control the grid explicitly; it takes
precedence over cluster_radius_km. n_jobs is accepted for API
compatibility and has no effect.
Worked example: Group stops into locations
identify_locations(locations, staypoints, method='freq', night_start_hour=22, night_end_hour=6, work_start_hour=8, work_end_hour=18)
Label each user's recurring Locations as "home", "work", or "other".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
locations
|
Locations
|
Locations previously generated from |
required |
staypoints
|
Staypoints
|
Must carry a |
required |
method
|
str
|
Only |
'freq'
|
night_start_hour
|
int
|
Hour-of-day boundaries (0-23) used by the |
22
|
night_end_hour
|
int
|
Hour-of-day boundaries (0-23) used by the |
6
|
work_start_hour
|
int
|
Hour-of-day boundaries (0-23) used by the |
8
|
work_end_hour
|
int
|
Hour-of-day boundaries (0-23) used by the |
18
|
Returns:
| Type | Description |
|---|---|
Locations
|
A copy of |
References
- Martin, F. et al. (2023) trackintel:
location_identifier(method="FREQ"). - transbigdata:
mobile_identify_home/mobile_identify_work.
Worked example: Functional forms
create_activity_flag(staypoints, method='time_threshold', time_threshold_min=15.0)
Flag each staypoint as a genuine "activity" (vs. a transient stop) by dwell time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
staypoints
|
Staypoints or dataframe
|
Must have |
required |
method
|
str
|
Only |
'time_threshold'
|
time_threshold_min
|
float
|
Minimum dwell time, in minutes, to count as an activity.
Default |
15.0
|
Returns:
| Type | Description |
|---|---|
Staypoints or dataframe
|
Input with an added boolean |
References
- Martin, F. et al. (2023) trackintel:
create_activity_flag.
Worked example: Functional forms
trajectory_to_trips(traj, resolution=9, *, uid_col=None, datetime_col=None, lat_col=None, lng_col=None, drop_self_loops=True, presorted=False)
Build a fastmob.Trips row for each consecutive ping pair.
Each user's fixes are sorted chronologically, tessellated to H3, then
paired with that user's next fix. Same-cell pairs are dropped by default,
matching trajectory_to_od. The returned Trips contain
origin_location_id and destination_location_id for direct sparse
CPC comparisons, alongside trip IDs and the pair's start/finish times.
This represents a consecutive-fix movement pair, not the activity-to- activity trips built from triplegs and staypoints.
presorted=True skips the internal chronological sort, trusting that
rows are already ordered by [uid_col, datetime_col] (or just
datetime_col when there is no uid_col) -- see
trajectory_to_od for the full caveat.
Worked example: Common part of commuters and links
trajectory_to_od(traj, resolution=9, *, uid_col=None, datetime_col=None, lat_col=None, lng_col=None, drop_self_loops=True, presorted=False)
Build a long-format Origin-Destination matrix directly from a raw trajectory.
Tessellates each point into an H3 cell (via fastmob.preprocessing.latlng_to_h3),
sorts each user chronologically, and pairs each point's cell with the next
point's cell (per user) to derive one trip per consecutive fix. Pairing and
counting are fused into a single Rust pass (see od_edge_counts_presorted)
rather than materializing every paired trip before counting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
resolution
|
int
|
H3 resolution, 0-15. |
9
|
uid_col
|
str | None
|
Explicit column name overrides; auto-detected when None. When
|
None
|
datetime_col
|
str | None
|
Explicit column name overrides; auto-detected when None. When
|
None
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. When
|
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. When
|
None
|
drop_self_loops
|
bool
|
When True (default), consecutive fixes that land in the same cell
are not counted as a trip -- matches how "did the user actually move
somewhere" trip derivation is normally defined. Set False to also
count same-cell "stayed put" pairs (as |
True
|
presorted
|
bool
|
When True, skip the internal chronological sort and trust that rows
are already ordered by |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Long-format OD counts with columns |
Examples:
>>> import pandas as pd
>>> from fastmob.preprocessing import trajectory_to_od
>>> traj = pd.DataFrame(
... {
... "uid": ["u1", "u1", "u1"],
... "datetime": pd.to_datetime(["2020-01-01 00:00", "2020-01-01 01:00", "2020-01-01 02:00"]),
... "lat": [37.7793, 37.6007, 37.6007],
... "lng": [-122.4194, -122.3824, -122.3824],
... }
... )
>>> result = trajectory_to_od(traj, resolution=7)
>>> len(result)
1
Worked example: From check-ins to flows
predict_transport_mode(triplegs, method='simple-coarse', categories=None, *, speed_col='mean_speed_kmh')
Classify each tripleg's transport mode from its average speed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
triplegs
|
Triplegs or dataframe
|
A |
required |
method
|
str
|
Only |
'simple-coarse'
|
categories
|
dict[float, str]
|
Mapping of upper-bound speed threshold (km/h) to mode label,
evaluated in ascending order (the last key should be |
None
|
speed_col
|
str
|
Column holding each row's average speed, km/h. Default
|
'mean_speed_kmh'
|
Returns:
| Type | Description |
|---|---|
Triplegs or dataframe
|
Input with an added |
References
- Martin, F. et al. (2023) trackintel:
predict_transport_mode.
Worked example: Functional forms
calculate_modal_split(triplegs, freq=None, metric='count', per_user=False, normalize=False)
Aggregate a mode-labeled Triplegs table into a modal-split table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
triplegs
|
Triplegs or dataframe
|
Must already have a |
required |
freq
|
str
|
Time-bucket the split by |
None
|
metric
|
str
|
|
'count'
|
per_user
|
bool
|
When True, split per user as well (requires a |
False
|
normalize
|
bool
|
When True, normalize each row-group (excluding |
False
|
Returns:
| Type | Description |
|---|---|
dataframe
|
One row per |
Worked example: Functional forms
latlng_to_h3(traj, resolution=9, *, lat_col=None, lng_col=None, output_col='h3_cell')
Convert each row's (lat, lng) to an H3 cell index, Rust-accelerated.
Rows with a non-finite or out-of-range coordinate (or a real Arrow null,
for Polars/PyArrow input) get a null cell rather than failing the whole
batch: NaN/inf coordinates in a NumPy-backed frame map to the reserved
sentinel 2**64 - 1 (not a valid H3 index), while an Arrow-backed frame
gets a genuine null slot in the output column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Trajectory dataframe; any Narwhals-compatible eager backend. |
required |
resolution
|
int
|
H3 resolution, 0-15. |
9
|
lat_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
lng_col
|
str | None
|
Explicit column name overrides; auto-detected when None. |
None
|
output_col
|
str
|
Name of the new column holding the H3 cell index. |
'h3_cell'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
Worked example: Homes and visits per place
h3_to_latlng(traj, *, h3_col='h3_cell', lat_col='center_lat', lng_col='center_lng')
Convert each H3 cell to its geographic center coordinates.
Null or invalid H3 cells produce null latitude and longitude values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traj
|
Any
|
Dataframe containing an H3 cell column; any Narwhals-compatible eager backend. |
required |
h3_col
|
str
|
Name of the input column containing native unsigned integer H3 cell indexes. |
'h3_cell'
|
lat_col
|
str
|
Names of the output columns containing the cell-center latitude and longitude. |
'center_lat'
|
lng_col
|
str
|
Names of the output columns containing the cell-center latitude and longitude. |
'center_lng'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Examples:
Worked example: Common part of commuters and links
cdr_to_trips_df(visitation_df, user_id_column='user_id', avg_speed_kmh=50.0, circuity=1.2)
Derive a CDR trips dataframe from cdr_to_visitation_df output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
visitation_df
|
Any
|
Visitation dataframe produced by |
required |
user_id_column
|
str
|
Column containing user IDs. |
'user_id'
|
avg_speed_kmh
|
float
|
Assumed average travel speed used to estimate trip durations. |
50.0
|
circuity
|
float
|
Multiplier applied to straight-line travel distance. |
1.2
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
NetMob-like trips dataframe in the caller's original backend. |
Examples:
>>> import pandas as pd
>>> from fastmob.preprocessing import cdr_to_trips_df, cdr_to_visitation_df
>>> cdr = pd.DataFrame(
... {
... "user_id": ["u1", "u1", "u1", "u1"],
... "timestamp": pd.to_datetime(
... ["2020-01-01 08:00", "2020-01-01 08:15", "2020-01-01 09:00", "2020-01-01 10:00"]
... ),
... "venueId": ["home", "home", "work", "home"],
... "lat": [0.0, 0.0, 0.01, 0.0],
... "long": [0.0, 0.0, 0.01, 0.0],
... }
... )
>>> visits = cdr_to_visitation_df(cdr)
>>> result = cdr_to_trips_df(visits)
>>> preview = result[["user_id", "trip_number", "origin_area", "destination_area", "duration_minutes"]]
>>> print(preview.round({"duration_minutes": 2}).to_string(index=False))
user_id trip_number origin_area destination_area duration_minutes
u1 1 home work 2.26
u1 2 work home 2.26
Worked example: Mobile-phone records
cdr_to_visitation_df(trajectory_df, user_id_column='user_id', timestamp_column='timestamp', venue_column='venueId', lat_column='lat', lon_column='long', avg_speed_kmh=50.0, circuity=1.2)
Convert raw CDR records into a NetMob-like visitation dataframe.
Consecutive records at the same venue for the same user are collapsed into one stay. The stay end time is estimated from approximate travel time to the next stay, clamped to the observed gap between venue changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trajectory_df
|
Any
|
Raw CDR-like records in any Narwhals-compatible eager dataframe. |
required |
user_id_column
|
str
|
Column containing user IDs. |
'user_id'
|
timestamp_column
|
str
|
Column containing observation timestamps. |
'timestamp'
|
venue_column
|
str
|
Column containing venue or cell-tower IDs. |
'venueId'
|
lat_column
|
str
|
Latitude column. |
'lat'
|
lon_column
|
str
|
Longitude column. |
'long'
|
avg_speed_kmh
|
float
|
Assumed average travel speed used to estimate stay end times. |
50.0
|
circuity
|
float
|
Multiplier applied to straight-line travel distance. |
1.2
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
NetMob-like visitation dataframe in the caller's original backend. |
Examples:
>>> import pandas as pd
>>> from fastmob.preprocessing import cdr_to_visitation_df
>>> cdr = pd.DataFrame(
... {
... "user_id": ["u1", "u1", "u1", "u1"],
... "timestamp": pd.to_datetime(
... ["2020-01-01 08:00", "2020-01-01 08:15", "2020-01-01 09:00", "2020-01-01 10:00"]
... ),
... "venueId": ["home", "home", "work", "home"],
... "lat": [0.0, 0.0, 0.01, 0.0],
... "long": [0.0, 0.0, 0.01, 0.0],
... }
... )
>>> result = cdr_to_visitation_df(cdr)
>>> preview = result[["user_id", "area", "start_timestamp", "end_timestamp", "duration_minutes"]]
>>> print(preview.round({"duration_minutes": 2}).to_string(index=False))
user_id area start_timestamp end_timestamp duration_minutes
u1 home 2020-01-01 08:00:00 2020-01-01 08:57:44.132898 57.74
u1 work 2020-01-01 09:00:00 2020-01-01 09:57:44.132898 57.74
u1 home 2020-01-01 10:00:00 NaT NaN
Worked example: Mobile-phone records