Skip to content

Evaluation Measures

API Description
common_part_of_commuters Compare Trips or sparse FlowDataFrames with Rust-backed CPC.
common_part_of_links Return the common part of links (CPL) between two flow arrays.
common_part_of_commuters_distance Return the common part of commuters by distance (CPCD).
compare_to Compare a value column between two dataframes, optionally grouped by an existing column. Also available as BaseDataFrame.compare_to().
r_squared Return the coefficient of determination R-squared.
rmse Return the root mean squared error between true and predicted values.
nrmse Return the normalized root mean squared error (RMSE / sum(true)).
information_gain Return the information gain of true over predicted values.
kullback_leibler_divergence Return the Kullback-Leibler divergence between true and predicted values.
max_error Return the maximum signed error max(true_i - pred_i).
mse Return the mean squared error between true and predicted values.
jensen_shannon_divergence Return Jensen-Shannon divergence between two distributions.
wasserstein_distance Return Rust-backed 1D Wasserstein distance between empirical samples.
stvd_emd Compute the spatio-temporal Wasserstein distance between two distributions.

fastmob.measures.evaluation

fastmob.measures.evaluation — generic comparison metrics.

For grouped/dataframe-level comparisons, use BaseDataFrame.compare_to() (inherited by Staypoints, Trips, Triplegs, Locations, TrajDataFrame, FlowDataFrame, Tours) instead of hand-coding a grouping around these primitives.

ComparisonResult dataclass

Result of :func:compare_to: an overall value and, if grouped, a per-group breakdown.

common_part_of_commuters(observed, predicted)

Compare two Trips or two FlowDataFrames through sparse Rust CPC.

common_part_of_commuters_distance(observed, predicted)

Compare distance_km distributions in two Trips using Rust CPCD.

Compare active sparse OD links in two Trips or FlowDataFrames.

compare_to(df1, df2, value_col, *, group_col=None, metric=None)

Compare value_col between two dataframes, optionally grouped by group_col.

metric defaults to :func:wasserstein_distance and accepts any (array, array) -> float callable, e.g. :func:jensen_shannon_divergence. When group_col is given, the comparison runs once per label present in both dataframes (sorted for determinism) and overall is the mean across groups.

information_gain(true, pred)

Return the information gain (KL divergence variant) of true over pred.

\[ \mathrm{IG} = \sum_i \frac{y_i}{N} \log\left(\frac{y_i}{\hat{y}_i}\right) \]

where \(N = \sum_i y_i\).

Parameters:

Name Type Description Default
true

Ground truth target values (must be positive).

required
pred

Estimated target values (must be positive).

required

Returns:

Type Description
float

Information gain value; 0.0 when the distributions are identical.

Examples:

>>> from fastmob import information_gain
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(information_gain(observed, predicted), 3))
0.005

jensen_shannon_divergence(distribution1, distribution2)

Return Jensen-Shannon divergence between two distributions.

Inputs are normalised internally (raw counts and probabilities both work) and must have the same length -- unlike :func:wasserstein_distance, the two arrays are paired by index (one entry per category/bin), not independent samples.

kullback_leibler_divergence(true, pred)

Return the Kullback–Leibler divergence \(D_\mathrm{KL}(\mathrm{true}\,\|\,\mathrm{pred})\).

\[ D_\mathrm{KL}(P \,\|\, Q) = \sum_k p_k \log\left(\frac{p_k}{q_k}\right) \]

Inputs are normalized to probability distributions before evaluation, matching the historical scipy.stats.entropy(true, pred) behavior.

Parameters:

Name Type Description Default
true

Probability distribution P (reference).

required
pred

Probability distribution Q (approximation).

required

Returns:

Type Description
float

KL divergence; 0.0 when the distributions are identical.

Examples:

>>> from fastmob import kullback_leibler_divergence
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(kullback_leibler_divergence(observed, predicted), 3))
0.005

max_error(true, pred)

Return the maximum signed error max(true_i - pred_i).

Parameters:

Name Type Description Default
true

Ground truth target values.

required
pred

Estimated target values.

required

Returns:

Type Description
float

Maximum element-wise difference true - pred.

Examples:

>>> from fastmob import max_error
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(max_error(observed, predicted), 3))
3.0

mse(true, pred)

Return the mean squared error between true and predicted values.

Parameters:

Name Type Description Default
true

Ground truth target values.

required
pred

Estimated target values.

required

Returns:

Type Description
float

Non-negative MSE; 0.0 is the best possible value.

Examples:

>>> from fastmob import mse
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(mse(observed, predicted), 3))
6.5

nrmse(true, pred)

Return the normalized root mean squared error (RMSE / sum(true)).

Parameters:

Name Type Description Default
true

Ground truth target values.

required
pred

Estimated target values.

required

Returns:

Type Description
float

Non-negative NRMSE; 0.0 is the best possible value.

Examples:

>>> from fastmob import nrmse
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(nrmse(observed, predicted), 3))
0.025

r_squared(true, pred)

Return the coefficient of determination R².

\[ R^2 = 1 - \frac{SS_\mathrm{res}}{SS_\mathrm{tot}} \]

where

\[ SS_\mathrm{tot} = \sum_i (y_i - \bar{y})^2 \]

Parameters:

Name Type Description Default
true

Ground truth target values.

required
pred

Estimated target values.

required

Returns:

Type Description
float

R² score. Best possible value is 1.0; can be negative.

Examples:

>>> from fastmob import r_squared
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(r_squared(observed, predicted), 3))
0.948

rmse(true, pred)

Return the root mean squared error between true and predicted values.

Parameters:

Name Type Description Default
true

Ground truth target values.

required
pred

Estimated target values.

required

Returns:

Type Description
float

Non-negative RMSE; 0.0 is the best possible value.

Examples:

>>> from fastmob import rmse
>>> observed = [10, 20, 30, 40]
>>> predicted = [12, 18, 33, 37]
>>> print(round(rmse(observed, predicted), 3))
2.55

stvd_emd(dist_a, dist_b, alpha=10.0, cyclical_period=1440.0, *, time_col=None, weight_col=None, lat_col=None, lng_col=None)

Compute the spatio-temporal Wasserstein distance between two distributions.

Each input is a DataFrame representing a spatial-temporal distribution with columns for a time bin (HH:MM string), a weight/volume value, and a WGS84 latitude/longitude centroid — the exact shape produced by :func:fastmob.measures.collective.build_stvd.

Distances are computed as an entropy-regularised (Sinkhorn) Earth Mover's Distance over an explicit cost matrix combining the great-circle (Haversine) distance between centroids with a cyclical-time term: two points at times t1/t2 contribute 2 * r * sin(d_theta / 2) metres, where r = alpha * cyclical_period / (2*pi), theta = 2*pi*(t / cyclical_period), and d_theta is the shortest angular difference between the two times.

Parameters:

Name Type Description Default
dist_a Any

Any Narwhals-compatible eager DataFrame (pandas, polars, …).

required
dist_b Any

Any Narwhals-compatible eager DataFrame (pandas, polars, …).

required
alpha float

Space-time tradeoff: 1 minute equals alpha metres. Default 10.0.

10.0
cyclical_period float

Wrap-around period in minutes. Default 1440 (one day).

1440.0
time_col str | None

Explicit time column name. Auto-detected when None.

None
weight_col str | None

Explicit weight column name. Auto-detected when None.

None
lat_col str | None

Explicit latitude/longitude column names. Auto-detected when None.

None
lng_col str | None

Explicit latitude/longitude column names. Auto-detected when None.

None

Returns:

Type Description
float

Spatio-temporal Wasserstein distance in metres.

Examples:

>>> import pandas as pd
>>> from fastmob.measures.evaluation import stvd_emd
>>> dist_a = pd.DataFrame(
...     {
...         "time_bin": ["08:00", "08:10"],
...         "mean_volume": [2.0, 1.0],
...         "center_lat": [0.0, 0.0],
...         "center_lng": [0.0, 0.001],
...     }
... )
>>> dist_b = pd.DataFrame(
...     {
...         "time_bin": ["08:00", "08:10"],
...         "mean_volume": [1.0, 2.0],
...         "center_lat": [0.0, 0.0],
...         "center_lng": [0.0, 0.002],
...     }
... )
>>> print(round(stvd_emd(dist_a, dist_b), 3))
74.156

wasserstein_distance(values1, values2)

Return Rust-backed 1D Wasserstein distance between empirical samples.