Flow measures and comparisons

These functions use the weights chosen when the network was built. With the default weight="n_trips", strengths and flow changes are measured in trips. For examples with expected results, see Network examples.

  • node_strengths(): flow sent and received by each zone.

  • compare_networks(): similarity, edge overlap and overall flow changes.

  • edge_changes(): weights and signed changes for individual OD pairs.

  • destination_similarity(): similarity of each origin’s destination profile.

Flow by zone

pyspainmobility.network.metrics.node_strengths(network: SparseMobilityNetwork) → DataFrame[source]

Calculate the flow sent and received by each zone.

Parameters:

network (SparseMobilityNetwork) – Network whose edge weights will be summed. With weight='n_trips', the resulting strengths are measured in trips.

Returns:

One row per node in matrix order, including isolates:

  • node_id: zone identifier.

  • out_strength: sum of outgoing weights (matrix row sum).

  • in_strength: sum of incoming weights (matrix column sum).

  • total_strength: outgoing plus incoming strength for directed networks; incident weight for undirected networks.

Return type:

polars.DataFrame

Notes

A directed self-loop contributes once to each of in- and out-strength.

For undirected networks total_strength counts a self-loop once, as one mobility flow incident on that zone. NetworkX’s weighted graph degree counts an undirected self-loop twice; callers needing that graph-theoretic convention should use the NetworkX adapter explicitly.

Examples

>>> import polars as pl
>>> from pyspainmobility import build_network, node_strengths
>>> od = pl.DataFrame({
...     "id_origin": ["A"], "id_destination": ["B"], "n_trips": [5.0],
... })
>>> node_strengths(build_network(od)).rows()
[('A', 5.0, 0.0, 5.0), ('B', 0.0, 5.0, 5.0)]

Whole-network comparison

pyspainmobility.network.metrics.compare_networks(left: SparseMobilityNetwork, right: SparseMobilityNetwork, *, left_label: object | None = None, right_label: object | None = None) → NetworkComparison[source]

Summarize overlap, similarity and flow changes between two networks.

Parameters:
  • left (SparseMobilityNetwork) – Networks with compatible node universes, zoning metadata, direction, weight field and normalization. Their node order is aligned automatically.

  • right (SparseMobilityNetwork) – Networks with compatible node universes, zoning metadata, direction, weight field and normalization. Their node order is aligned automatically.

  • left_label (object, optional) – Labels recorded in the result, such as the dates being compared.

  • right_label (object, optional) – Labels recorded in the result, such as the dates being compared.

Returns:

Connection counts, edge and weighted Jaccard similarity, cosine similarity, and increases/decreases in flow. audit() returns a serializable dictionary. See the flow-measures reference for definitions.

Return type:

NetworkComparison

Examples

>>> import polars as pl
>>> from pyspainmobility import build_network, compare_networks
>>> od = pl.DataFrame({
...     "id_origin": ["A"], "id_destination": ["B"], "n_trips": [5.0],
... })
>>> first = build_network(od)
>>> second = build_network(od.with_columns(pl.lit(7.0).alias("n_trips")))
>>> result = compare_networks(first, second)
>>> result.flow_increase, result.flow_decrease
(2.0, 0.0)
>>> result.shared_edge_count
1
>>> round(result.weighted_jaccard, 2)
0.71
>>> result.cosine_similarity
1.0
Fields in the comparison result

Field

Definition

left_weight, right_weight

Total logical edge weight in the first and second networks.

left_edge_count, right_edge_count

Number of non-zero connections in each network.

shared_edge_count

Connections present in both networks.

added_edge_count, removed_edge_count

Connections appearing or disappearing in the second network.

edge_jaccard

Shared connection count divided by the union connection count.

edge_turnover

1 - edge_jaccard; the fraction of union connections not shared.

shared_weight, union_weight

Sum of pairwise minimum and maximum edge weights, respectively.

weighted_jaccard

shared_weight / union_weight; accounts for both overlap and magnitude.

cosine_similarity

Cosine of the vectors of edge weights; compares their relative pattern.

flow_increase, flow_decrease

Sums of positive and negative weight changes, reported as positive totals.

Both Jaccard measures and cosine similarity are 1 when both networks are empty, and 0 when only one is empty. Undirected connections are counted once, including each self-loop. Comparisons align node order and reject incompatible node universes, zoning metadata, directions, weight fields or normalizations.

class pyspainmobility.network.metrics.NetworkComparison(node_index: NodeIndex, weight: str, weight_normalization: str | None, left_label: str | None, right_label: str | None, left_edge_count: int, right_edge_count: int, shared_edge_count: int, added_edge_count: int, removed_edge_count: int, edge_jaccard: float, weighted_jaccard: float, cosine_similarity: float, left_weight: float, right_weight: float, shared_weight: float, union_weight: float, flow_increase: float, flow_decrease: float)[source]

Summary of a pairwise comparison on one aligned node index.

flow_increase and flow_decrease measure the total positive and negative changes in edge weights. They include changes on persistent edges as well as edges that appeared or disappeared.

property edge_turnover: float

Fraction of the union edge set not shared by both networks.

audit() → dict[str, object][source]

Return a serialisable record of all comparison values.

Changes by connection

pyspainmobility.network.metrics.edge_changes(left: SparseMobilityNetwork, right: SparseMobilityNetwork) → DataFrame[source]

Inspect weight changes for individual origin-destination connections.

Parameters:
  • left (SparseMobilityNetwork) – Networks with compatible node universes, zoning metadata, direction, weight field and normalization. Their node order is aligned automatically.

  • right (SparseMobilityNetwork) – Networks with compatible node universes, zoning metadata, direction, weight field and normalization. Their node order is aligned automatically.

Returns:

One row for each connection present in either network, sorted by IDs:

  • id_origin, id_destination: endpoint zone IDs.

  • left_weight, right_weight: weights, with absent edges set to zero.

  • delta_weight: right_weight - left_weight.

  • change: 'added', 'removed', 'increased', 'decreased' or 'unchanged'.

Unchanged connections are included. Undirected pairs appear once; zero-to-zero pairs are omitted.

Return type:

polars.DataFrame

Examples

>>> import polars as pl
>>> from pyspainmobility import build_network, edge_changes
>>> od = pl.DataFrame({
...     "id_origin": ["A"], "id_destination": ["B"], "n_trips": [5.0],
... })
>>> first = build_network(od)
>>> second = build_network(od.with_columns(pl.lit(7.0).alias("n_trips")))
>>> edge_changes(first, second).select("delta_weight", "change").rows()
[(2.0, 'increased')]

Destination profiles by origin

pyspainmobility.network.metrics.destination_similarity(left: SparseMobilityNetwork, right: SparseMobilityNetwork) → DataFrame[source]

Return per-origin cosine similarity between destination flow profiles.

A node with no outgoing flow in either network has similarity 1.0; a node that is inactive in only one network has similarity 0.0.

Parameters:
  • left (SparseMobilityNetwork) – Networks with compatible node universes, zoning metadata, direction, weight field and normalization. Their node order is aligned automatically.

  • right (SparseMobilityNetwork) – Networks with compatible node universes, zoning metadata, direction, weight field and normalization. Their node order is aligned automatically.

Returns:

One row per origin in the first network’s node order:

  • node_id: origin zone identifier.

  • destination_cosine_similarity: cosine of the two outgoing weight profiles, accounting for the empty-profile conventions above.

Return type:

polars.DataFrame

Examples

>>> import polars as pl
>>> from pyspainmobility import build_network, destination_similarity
>>> od = pl.DataFrame({
...     "id_origin": ["A"], "id_destination": ["B"], "n_trips": [5.0],
... })
>>> network = build_network(od)
>>> destination_similarity(network, network).rows()
[('A', 1.0), ('B', 1.0)]