Skip to main content

moveq-core

Pure NumPy algorithms for measuring transport equity.

PyPI Python versions License

This is the calculation engine of the moveq stack. It has no I/O: you pass NumPy arrays (or, with the optional frames extra, a pandas DataFrame) and get numbers and small result objects back. There is no database, GIS library, or network client.

Most applications should install the umbrella package moveq, which re-exports this API. Install moveq-core directly only when you want the metrics without the catalogue registry.

What each metric is for

Transport analysis usually has a service variable per area (trips, departures, coverage) and a population weight. Sometimes there is also a socioeconomic rank (deprivation index, income band).

Population-weighted Gini

Gini answers: how unevenly is this service spread across people?

Areas are sorted from lowest to highest service. Cumulative population share is plotted against cumulative service share (a Lorenz curve). Gini is 1 minus twice the area under that curve, using trapezoidal integration.

  • 0 — every person faces the same service level (including the all-zero convention: nothing to distribute)
  • toward 1 — almost all service sits with a small share of the population

Gini does not know whether the poorly served people are deprived. It is a statement about the service distribution only.

Palma ratio

Palma answers: how does the best-served tenth compare with the worst-served two-fifths?

It is the population-weighted mean service of the top 10% divided by that of the bottom 40% (areas on the boundary of those shares are split proportionally). Equal service, including all-zero service, gives 1. If the bottom 40% have zero service while the top 10% do not, the function returns inf rather than dividing by zero.

Palma is more sensitive to the tails than Gini, which is why the two are reported together.

Wagstaff Concentration Index

The concentration index answers: does service rise or fall as deprivation falls?

Areas are ordered by rank (1 = most deprived by convention in this library). Units that tie on rank share the group's midpoint fractional rank, so the answer does not depend on input order. Unpopulated units are ignored. A population-weighted covariance between service and fractional rank is scaled by the mean service. For non-negative service the result is in [-1, 1]:

Sign Meaning
CI > 0 outcome concentrated among more-advantaged observations (advantage-concentrated)
CI < 0 outcome concentrated among less-advantaged observations (disadvantage-concentrated)
CI = 0 no systematic gradient with rank

This is the metric to use when the policy question is about socioeconomic targeting, not only about overall unevenness.

Composite score with missing-term renormalisation

compute_score builds a 0–100 weighted index from named terms in [0, 1]. The important behaviour is what happens when a term is None:

  1. that term is dropped, not treated as zero
  2. remaining design weights are renormalised to sum to 1
  3. the result records weight_used vs design_weight, the dropped ids, and a note

If every term is missing, score is None. Design weights must be finite and strictly positive; non-finite term values are rejected rather than clipped. Callers keep control of which indicators exist; the library refuses to invent a zero.

Optional DataFrame helpers (moveq_core.frames)

With pip install "moveq-core[frames]" (or "moveq[frames]"):

  • vulnerability index — min-max scale several deprivation-like columns to 0–100 and take the row-wise mean
  • multiply deprived — flag areas that sit in the worst tertile on at least k factors at once

These are convenience summaries, not a substitute for a national IMD.

Formulae: methodology.

Installation

Requires Python 3.10+. Runtime dependency: numpy>=1.24.

pip install moveq-core
pip install "moveq-core[frames]"   # pandas helpers

Quickstart

import numpy as np
from moveq_core import (
    compute_gini,
    compute_palma_ratio,
    compute_concentration_index,
    gini_result,
    compute_score,
)

service = np.array([10.0, 20.0, 5.0, 50.0, 8.0])
population = np.array([1000, 800, 1200, 300, 900])
deprivation_rank = np.array([1, 3, 2, 5, 4])  # 1 = most deprived

gini = compute_gini(service, population)
palma = compute_palma_ratio(service, population)
ci = compute_concentration_index(
    service, deprivation_rank, population, rank_direction="higher_is_advantaged"
)
gini_audit = gini_result(service, population)  # EquityResult: value, method, n_areas, …

score_res = compute_score(
    terms={"coverage": 0.75, "evening": 0.50, "night": None},
    weights={"coverage": 0.50, "evening": 0.30, "night": 0.20},
)
print(score_res.score, score_res.dropped, score_res.note)

Documentation

License

BSD 3-Clause.

Metadata

Release files for moveq-core 0.2.0

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

Source distribution (sdist)

Source distribution for moveq-core 0.2.0
File Size Uploaded
moveq_core-0.2.0.tar.gz 23.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for moveq-core 0.2.0
File Interpreter ABI Platform
moveq_core-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.2 kB

Release files / moveq_core-0.2.0.tar.gz

Download URL moveq_core-0.2.0.tar.gz
Size 23.1 kB
Tags Source
SHA-256 checksum
How to use checksums
d53bafd1dce17e4ee516b0c1cc2e9d10e80683e73d59ed7dfe0f45f4e86823de
BLAKE2b-256 checksum
How to use checksums
584c8e0b1382557f772ecd47db4e277e22cb1fa3d3556a9b68476f3f77ae6eac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 5, 2026.

Transparency log

Release files / moveq_core-0.2.0-py3-none-any.whl

Download URL moveq_core-0.2.0-py3-none-any.whl
Size 13.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ddaa30125331ee9c264c7f256539f84d6c2eba59063bc264d746539a9e78ad29
BLAKE2b-256 checksum
How to use checksums
95839ea62c88faa8e5f61158a73c27e623f2c3510ce8609d05e308aa5c2580e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

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