Skip to main content

odflows

PyPI version Python versions CI License: MIT

Origin-destination flow toolkit for mobility and commuting analysis.

odflows covers the usual steps of an OD study in one small package that needs only numpy:

  • Read OD tables in long form (origin,destination,flow) or as a square matrix, and zone centroids from GeoJSON (points, or area centroids of polygons) or CSV.
  • Desire lines as GeoJSON LineStrings: directed, net or gross; straight, curved or offset, so both directions of a two-way pair stay visible.
  • Change zone systems: aggregate and disaggregate with weighted lookup tables, including area weights computed from two polygon layers (optional shapely).
  • Net and gross flows, symmetric part, asymmetry index, intrazonal handling.
  • Filter flows: minimum size, top-k, share of origin / destination / total, cumulative share, dominant (largest outgoing) flows.
  • Cluster and bundle flows with the Tao & Thill flow distance: agglomerative (single / complete / average linkage) and DBSCAN, in pure numpy.
  • Indicators: in/out degree and strength, supply- and demand-side self-containment, commuting job ratio, net balance.
  • Functional regions (commuting zones) with Intramax (Masser & Brown 1975), optionally contiguity-constrained.
  • Export to GeoJSON, flowmap.blue (locations + flows sheets) and a kepler.gl arc-layer CSV.
  • A command-line tool, odflows.
pip install odflows            # numpy only
pip install "odflows[pandas]"  # + DataFrame helpers
pip install "odflows[geo]"     # + shapely, for area-weighted lookups from polygons

Requires Python 3.9 or newer.

Quick start

from odflows import (
    ODMatrix, read_od_csv, centroids_from_geojson, desire_lines, write_geojson,
    zone_summary, intramax, region_summary, filter_top_k,
)

od = read_od_csv("commute.csv")                 # origin,destination,flow
zones = centroids_from_geojson("zones.geojson", id_field="zone")

for row in zone_summary(od):                     # self-containment, job ratio, ...
    print(row["zone"], round(row["supply_sc"], 3), round(row["job_ratio"], 2))

lines = desire_lines(filter_top_k(od, 20), zones, style="curved")
write_geojson(lines, "desire_lines.geojson")

hierarchy = intramax(od)                         # full Intramax hierarchy
membership = hierarchy.membership(n_regions=4)   # cut it at 4 regions
print(region_summary(od, membership))

A runnable example with synthetic data is in examples/quickstart.py.

Matrices can also be built in memory:

od = ODMatrix.from_long([("A", "B", 120), ("B", "A", 40), ("A", "A", 300)])
od = ODMatrix(["A", "B"], [[300, 120], [40, 0]])  # rows = origins, columns = destinations
od.net().to_long()   # [('A', 'B', 80.0)]

Command line

odflows summary commute.csv -o zones.csv              # per-zone indicators
odflows summary commute.csv --json                    # matrix statistics
odflows lines commute.csv --centroids zones.geojson --id-field zone \
        --style curved --min-flow 50 -o lines.geojson
odflows filter commute.csv --min-share 0.1 --basis origin -o main_flows.csv
odflows transform commute.csv net -o net.csv          # net | gross | symmetric | ...
odflows aggregate commute.csv --lookup lookup.csv -o districts.csv
odflows regions commute.csv -n 5 -o regions.csv --summary regions_sc.csv
odflows cluster commute.csv --centroids zones.geojson --id-field zone \
        --n-clusters 8 -o bundles.geojson
odflows export commute.csv --centroids zones.geojson --id-field zone \
        --format flowmap -o mymap                       # mymap-locations.csv, mymap-flows.csv

Every command accepts --matrix for square-matrix input and --origin-col, --dest-col, --flow-col for other column names. Centroids may be a GeoJSON file or a CSV (--id-field, --x-col, --y-col).

API overview

Area Functions / classes
Container ODMatrix (from_long, to_long, outflows, inflows, intrazonal, without_intrazonal, net, gross, symmetric, transpose, asymmetry_index, subset)
Input / output read_od_csv, read_matrix_csv, write_od_csv, write_matrix_csv, centroids_from_geojson, read_centroids_csv, geometry_centroid, write_geojson
Desire lines desire_lines, line_coordinates, haversine_km
Zone systems ZoneLookup (from_records, from_mapping, from_csv, from_geojson), reaggregate, aggregate
Filtering filter_min_flow, filter_top_k, filter_share, filter_cumulative, dominant_flows
Indicators degree, self_containment, job_ratio, net_balance, zone_summary, matrix_summary
Clustering FlowSet, flow_distance_matrix, flow_neighbour_counts, hierarchical_clusters, flow_dbscan, bundle_flows
Regions intramax, IntramaxResult, region_summary
Visualisation export write_flowmap_blue, write_kepler_csv
pandas from_dataframe, to_dataframe

All filters and transformations return a new ODMatrix, so they chain: filter_top_k(filter_share(od, 0.1), 50).without_intrazonal().

Methods and sources

Conventions. T[i, j] is the flow from zone i to zone j. For commuting, rows are places of residence and columns places of work, so O_i = Σ_j T_ij are resident workers and D_j = Σ_i T_ij are jobs; both include intrazonal flows T_ii.

Net and gross flows. Gross G_ij = T_ij + T_ji; net N_ij = max(T_ij − T_ji, 0). The asymmetry index is Σ_{i<j} |T_ij − T_ji| / Σ_{i<j} (T_ij + T_ji).

Desire lines. Curved lines are quadratic Bézier curves whose control point is the midpoint moved to the right of the direction of travel by bend × length; offset lines are the segment shifted to the right by the same amount. A→B and B→A therefore fall on opposite sides. Lengths are haversine kilometres for longitude/latitude centroids (mean Earth radius 6371.0088 km) and planar units otherwise.

Polygon centroids use the shoelace (area-weighted) formula on the exterior ring minus holes, treating coordinates as planar.

Zone re-aggregation. With a row-normalised lookup W (W[s, t] = share of source zone s in target zone t), T' = Wᵀ T W. Flows are split assuming origins and destinations are spread within each source zone in proportion to the weights - areal weighting (Goodchild & Lam 1980). Different origin and destination lookups (e.g. population vs. employment weights) are supported; totals are preserved.

Filtering. filter_share(basis="origin") keeps T_ij / O_i ≥ s; dominant_flows keeps each origin's largest outgoing flow, the first step of nodal region analysis (Nystuen & Dacey 1961).

Flow distance. Tao & Thill (2016) define, for flows i and j with origin distance d_O and destination distance d_D, FD = sqrt(α d_O² + β d_D²) with α + β = 2, and the length-normalised dissimilarity FDS = sqrt((α d_O² + β d_D²) / (L_i L_j)). Coordinates must be projected. flow_neighbour_counts gives the local counting term of their flow K-function (no edge correction or significance test).

Clustering. Agglomerative clustering uses the Lance-Williams (1967) update for single, complete and average (UPGMA) linkage; DBSCAN follows Ester et al. (1996), with an option to count flow volume instead of flow numbers. bundle_flows replaces every cluster by the flow from its volume-weighted mean origin to its volume-weighted mean destination.

Self-containment. Supply-side SC_s = T_ii / O_i (share of resident workers who work locally) and demand-side SC_d = T_ii / D_i (share of local jobs filled by residents), as used for local labour market areas (Goodman 1970; Casado-Díaz 2000). The commuting job ratio is D_i / O_i.

Intramax. Hierarchical aggregation (Masser & Brown 1975) that repeatedly merges the pair of current regions maximising T_ij / (O_i D_j) + T_ji / (O_j D_i) on the aggregated matrix; merged flows become intrazonal. The full hierarchy is stored and can be cut at any number of regions. An optional contiguity constraint only merges adjacent regions (Drobne & Lakner 2016). This is Intramax, not a modularity / Louvain method.

Export formats. flowmap.blue reads a Google Sheet with a locations sheet (id,name,lat,lon) and a flows sheet (origin,dest,count); write_flowmap_blue writes one CSV per sheet. write_kepler_csv writes origin_lat/origin_lng and dest_lat/dest_lng column pairs, which kepler.gl detects as point fields for its arc and line layers.

References

  • Casado-Díaz, J. M. (2000). Local labour market areas in Spain: a case study. Regional Studies, 34(9), 843-856.
  • Drobne, S., & Lakner, M. (2016). Use of constraints in the hierarchical aggregation procedure Intramax. Business Systems Research, 7(2), 5-22.
  • Ester, M., Kriegel, H.-P., Sander, J., & Xu, X. (1996). A density-based algorithm for discovering clusters in large spatial databases with noise. Proceedings of KDD-96, 226-231.
  • Goodchild, M. F., & Lam, N. S.-N. (1980). Areal interpolation: a variant of the traditional spatial problem. Geo-Processing, 1, 297-312.
  • Goodman, J. F. B. (1970). The definition and analysis of local labour markets: some empirical problems. British Journal of Industrial Relations, 8(2), 179-196.
  • Lance, G. N., & Williams, W. T. (1967). A general theory of classificatory sorting strategies: 1. Hierarchical systems. The Computer Journal, 9(4), 373-380.
  • Masser, I., & Brown, P. J. B. (1975). Hierarchical aggregation procedures for interaction data. Environment and Planning A, 7(5), 509-523.
  • Nystuen, J. D., & Dacey, M. F. (1961). A graph theory interpretation of nodal regions. Papers of the Regional Science Association, 7, 29-42.
  • Tao, R., & Thill, J.-C. (2016). Spatial cluster detection in spatial flow data. Geographical Analysis, 48(4), 355-372.

Limitations

  • Centroids, Bézier offsets, polygon centroids and the flow distance are planar. Use a projected CRS for clustering and for exact curve shapes; desire_lines guesses longitude/latitude from coordinate ranges (override with geographic=), which can misfire for small projected coordinates.
  • Re-aggregation assumes uniform spreading within source zones (areal weighting). Use population or employment weights where you have them.
  • Intramax and agglomerative clustering are O(n³); fine for hundreds of zones and a few thousand flows, not for full national matrices. Pairs with no interaction are never merged, so Intramax may stop before reaching the requested number of regions.
  • Flow statistics stop at the flow distance, dissimilarity and neighbour counts: the flow K-function with edge correction, and significance testing, are not implemented.
  • No travel-to-work-area (TTWA) thresholds are built in; check region self-containment against the criteria of your own study.

Academic citation

If you use odflows in research, please cite it:

@software{eminoglu_odflows,
  author  = {Eminoğlu, Yusuf},
  title   = {odflows},
  year    = {2026},
  version = {0.1.1},
  url     = {https://github.com/YusufEminoglu/odflows_sdk}
}

See also CITATION.cff.

License

MIT © 2026 Yusuf Eminoğlu. Contact: geospacephilo@gmail.com

Metadata

Release files for odflows 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odflows 0.1.1
File Size Uploaded Provenance Actions
odflows-0.1.1.tar.gz 51.8 kB With provenance Details for odflows-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for odflows 0.1.1
File Interpreter ABI Platform Actions
odflows-0.1.1-py3-none-any.whl Python 3 none any Details for odflows-0.1.1-py3-none-any.whl

Total release size: 90.8 kB

Release files / odflows-0.1.1.tar.gz

Download URL odflows-0.1.1.tar.gz
Size 51.8 kB
Tags Source
SHA-256 checksum
How to use checksums
3f89ead34d74e1799cab12d358fc2be4fbd3349f62b54398f3572c1e87ff627d
BLAKE2b-256 checksum
How to use checksums
024609d401d62570693efb0cff5299e7ff017df6a1a60201d9bf551b6ed72687
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.

Transparency log

Release files / odflows-0.1.1-py3-none-any.whl

Download URL odflows-0.1.1-py3-none-any.whl
Size 38.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7230a817d0eb33227604493682593a9b3245b3c1774235bb51d887e242c0c42f
BLAKE2b-256 checksum
How to use checksums
9425ad87b38a69680864cbfd1e2f6ed925860c37c46a1911f1dcf8488a5f5384
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.

Transparency log

Security for odflows 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for odflows-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page