Skip to main content

landmix

PyPI version Python versions CI License: MIT

Land-use mix and diversity indices for urban analysis.

landmix computes the land-use mix measures used in planning, transport and walkability research from the amount of each land use in a zone. It runs on the Python standard library alone. The indices are:

  • Entropy: normalised Shannon entropy, with K set to all study categories (Cervero & Kockelman 1997; Frank et al. 2004, 2005) or to the categories present. The Cervero & Kockelman mean-of-hectares form is also included.
  • Concentration: Herfindahl–Hirschman, normalised HHI, Simpson's λ, Gini–Simpson, and Simpson's finite-sample form for counts.
  • Hill numbers: the effective number of land uses for any order q.
  • Two-use balance: the balance index, jobs–housing ratio and jobs–housing balance (Ewing et al. 2002), and Hoek's MXI.
  • Spatial: the Cervero & Kockelman (1997) dissimilarity index on a grid of dominant uses.

You can read inputs from a dict, a CSV file (wide or long layout) or a pandas DataFrame. GeoJSON points and polygons can be aggregated to zone polygons, to a regular grid or to moving-window buffers. This uses pure-Python point-in-polygon, or exact area splitting if shapely is installed. OpenStreetMap tags can be classified into planning land uses. Results are written to CSV, JSON, Markdown or GeoJSON from Python or the landmix command.

pip install landmix              # stdlib only
pip install "landmix[shapely]"   # + exact polygon/zone intersection
pip install "landmix[pandas]"    # + DataFrame in/out

Requires Python 3.9 or newer.


Quick start (Python)

from landmix import entropy_index, hhi, hill_number, mix_table, Balance

USES = ["residential", "commercial", "office", "institutional", "entertainment"]
zone = {"residential": 6000, "commercial": 2500, "office": 1500}   # m2 floor area

entropy_index(zone, USES)                        # 0.583  (K = 5 study categories)
entropy_index(zone, normalization="present")     # 0.853  (K = 3 uses present)
hhi(zone)                                        # 0.445
hill_number(zone, q=1)                           # 2.554 effective land uses

zones = {
    "Centre": {"residential": 12000, "commercial": 9000, "office": 15000, "jobs": 1400, "housing": 300},
    "Suburb": {"residential": 45000, "commercial": 500, "jobs": 40, "housing": 700},
}
rows = mix_table(zones, USES, balances=[Balance("jhb", ["jobs"], ["housing"])])

mix_table returns one dict per zone. The default columns are total, richness, entropy, entropy_present, hhi, gini_simpson, hill1, hill2, dominant_share and dominant, plus <name> and <name>_ratio for each Balance. Categories that are not listed in USES (here jobs, housing) are left out of the mix indices, but a balance can still use them.

GeoJSON: zones, grid, moving windows

from landmix import (load_geojson, aggregate_to_zones, aggregate_to_grid,
                     moving_window, grid_centres, features_bounds,
                     osm_category, dissimilarity_index, mix_table)

pois = load_geojson("osm_pois.geojson")          # projected CRS, metres
zones = load_geojson("neighbourhoods.geojson")

table = aggregate_to_zones(pois, zones, category=osm_category, zone_id="name")
rows = mix_table(table)

grid = aggregate_to_grid(pois, 100, category=osm_category)        # 1 ha cells
di = dissimilarity_index(grid.dominant())                         # Cervero & Kockelman

centres = grid_centres(features_bounds(pois), 200)
windows = moving_window(pois, centres, radius=400, category=osm_category)

# polygons by floor area property, split exactly across zones (needs shapely)
blocks = load_geojson("blocks.geojson")
table = aggregate_to_zones(blocks, zones, category="use", measure="floor_area",
                           method="intersection", zone_id="name")

measure is "count", "area" (polygon area) or the name of a numeric property. category is a property name or a function of the feature properties.

Quick start (command line)

# wide CSV: zone,residential,commercial,office,...,jobs,housing
landmix table zones.csv --categories residential,commercial,office,institutional,entertainment \
    --balance jhb=jobs/housing -o mix.csv

# long CSV: zone,category,value
landmix table parcels.csv --layout long --zone-col mahalle --category-col use --value-col m2

# OSM points aggregated to zone polygons, written back as GeoJSON with the indices
landmix geojson pois.geojson --osm --zones zones.geojson --zone-id name -o zones_mix.geojson

# 100 m grid (prints the dissimilarity index), 400 m moving windows every 200 m
landmix geojson landuse.geojson --category-field use --measure area --grid 100 -o grid.geojson
landmix geojson pois.geojson --osm --window 400 --spacing 200 -o windows.csv

landmix indices        # list indices and their sources
landmix osm-mapping    # print the default OSM tag table (JSON) to copy and edit

The output format follows the extension (.csv, .json, .md, .geojson). Without -o, a Markdown table is printed. --balance NAME=X1+X2/Y1@ratio adds a balance column. When the ratio is left out, the study-wide ratio is the target.

API overview

Function Formula / meaning
shannon(a, base=e) H = −Σ pⱼ ln pⱼ
entropy_index(a, categories, k=None, normalization="all"|"present") H / ln K
cervero_kockelman_entropy(cells, categories) mean of cell entropies over developed cells
hhi(a, scale=1), normalized_hhi(a, k) Σ (s·pⱼ)²; (HHI − 1/K)/(1 − 1/K)
simpson(a), gini_simpson(a) λ = Σ pⱼ²; 1 − λ
simpson_unbiased(counts), gini_simpson_unbiased(counts) Σ nⱼ(nⱼ−1) / N(N−1)
hill_number(a, q), hill_profile(a, qs) ᵠD = (Σ pⱼ^q)^(1/(1−q)); q→1: exp H; q=∞: 1/max p
balance_index(x, y, a=1) 1 − |x − a·y| / (x + a·y)
jobs_housing_ratio(J, H), jobs_housing_balance(J, H, J*, H*) J/H; 1 − |J − kH|/(J + kH), k = J*/H*
mixed_use_index_hoek(a, housing) MXI = 100 · housing GFA / total GFA
area_index(a, relevant) share of zone area in purpose-relevant uses (see Limitations)
dissimilarity_index(grid, neighbours=8, edge="valid"|"fixed") (1/K) Σₖ Σₗ Xₗ / 8
richness(a), dominant_share(a), proportions(a) helpers

Here a is a {category: amount} mapping or a sequence of amounts. Pass categories so that categories missing from a zone count as zero and every zone shares the same K. Negative, NaN or infinite amounts raise ValueError. A zone with a total of zero returns NaN. A zone with one land use has entropy 0, HHI 1 and Gini–Simpson 0.

Other modules:

  • landmix.io: read_csv, from_records, from_dataframe, to_dataframe.
  • landmix.geo: load_geojson, point_in_polygon, geometry_area (planar or spherical), geometry_centroid, aggregate_to_zones, aggregate_to_grid → Grid (zone_table(), dominant(), to_geojson()), moving_window, grid_centres, haversine.
  • landmix.categories: classify_osm, OSM_DEFAULT_MAPPING, default_mapping(), recode, and FRANK_CLASSES (the five Frank et al. land uses).
  • landmix.report: mix_table, Balance, summary, to_markdown, to_json, write_csv.

Method notes and sources

Normalised entropy. LUM = −Σⱼ pⱼ ln pⱼ / ln K. Cervero & Kockelman (1997) and Frank et al. (2004, 2005) take K as the number of land-use categories in the study, so a zone reaches 1 only when every study use has an equal share. This is normalization="all". Here a missing category matters, so give the full categories list or k. Dividing by the number of uses present (normalization="present") measures evenness among those uses only. Song, Merlin & Rodriguez (2013) discuss how sensitive the entropy index is to this choice. The original Cervero & Kockelman entropy averages the per-hectare entropy over the actively developed hectares of a tract (cervero_kockelman_entropy).

Dissimilarity index (Cervero & Kockelman 1997). For each developed hectare, this is the share of its 8 neighbours whose land use differs from the centre's, averaged over hectares. Build the input with aggregate_to_grid(...).dominant(). The published form divides by 8. The default here (edge="valid") divides by the number of developed neighbours that exist, so that cells on the edge of the study area are not penalised. Use edge="fixed" for the published denominator.

HHI and Simpson. HHI = Σ pⱼ² (Hirschman 1964; Rhoades 1993), which is Simpson's λ (Simpson 1949). Gini–Simpson 1 − λ is the probability that two random units of land belong to different uses. For counts of discrete units, Simpson's finite-sample form Σ n(n−1)/N(N−1) avoids the small-sample bias.

Hill numbers (Hill 1973; Jost 2006) express diversity as an effective number of equally common uses: order 0 is richness, order 1 is exp(H) and order 2 is the inverse Simpson index. Simons (2023) argues for them in pedestrian-scale mixed-use analysis.

Balance and jobs–housing. The balance index 1 − |X − aY| / (X + aY) equals 1 when X/Y equals the target ratio a and 0 when one of the uses is absent. Ewing, Pendall & Chen (2002) use the regional jobs-per-resident ratio as the target. mix_table uses the study-wide ratio unless a is given. Cervero (1989) discusses jobs–housing ratios.

MXI (van den Hoek 2008) is the housing share of gross floor area, in percent.

Areas and distances. Coordinates are planar by default, so reproject to a local metric CRS. With lonlat=True, polygon areas use the spherical formula of Chamberlain & Duquette (2007) with the WGS84 equatorial radius, and window distances use the haversine formula with the mean Earth radius (6371008.8 m).

OSM categories. The default table follows the OSM wiki definitions of landuse, amenity, shop, office, leisure, tourism, craft and building. Keys are tried in a fixed priority order, so a shop=* on an apartment building counts as commercial. The wiki defines landuse=commercial as predominantly offices, so it maps to office, and landuse=retail maps to commercial. The table is a judgement call: copy default_mapping(), edit it and pass it to classify_osm.

References

  • Bordoloi, R., Mote, A., Sarkar, P. P., & Mallikarjuna, C. (2013). Quantification of land use diversity in the context of mixed land use. Procedia – Social and Behavioral Sciences, 104, 563–572. https://doi.org/10.1016/j.sbspro.2013.11.150
  • Cervero, R. (1989). Jobs-housing balancing and regional mobility. Journal of the American Planning Association, 55(2), 136–150.
  • Cervero, R., & Kockelman, K. (1997). Travel demand and the 3Ds: Density, diversity, and design. Transportation Research Part D, 2(3), 199–219.
  • Chamberlain, R. G., & Duquette, W. H. (2007). Some algorithms for polygons on a sphere. JPL Publication 07-03.
  • Ewing, R., Pendall, R., & Chen, D. (2002). Measuring Sprawl and Its Impact. Smart Growth America.
  • Frank, L. D., Andresen, M. A., & Schmid, T. L. (2004). Obesity relationships with community design, physical activity, and time spent in cars. American Journal of Preventive Medicine, 27(2), 87–96.
  • Frank, L. D., Schmid, T. L., Sallis, J. F., Chapman, J., & Saelens, B. E. (2005). Linking objectively measured physical activity with objectively measured urban form. American Journal of Preventive Medicine, 28(2S2), 117–125.
  • Hill, M. O. (1973). Diversity and evenness: a unifying notation and its consequences. Ecology, 54(2), 427–432.
  • Hirschman, A. O. (1964). The paternity of an index. American Economic Review, 54(5), 761.
  • Iannillo, A., & Fasolino, I. (2021). Land-use mix and urban sustainability: Benefits and indicators analysis. Sustainability, 13(23), 13460. https://doi.org/10.3390/su132313460 (used as a published test case)
  • Jost, L. (2006). Entropy and diversity. Oikos, 113(2), 363–375.
  • Rhoades, S. A. (1993). The Herfindahl-Hirschman index. Federal Reserve Bulletin, 79, 188–189.
  • Shannon, C. E. (1948). A mathematical theory of communication. Bell System Technical Journal, 27(3), 379–423.
  • Simons, G. D. (2023). The cityseer Python package for pedestrian-scale network-based urban analysis. Environment and Planning B, 50(5), 1328–1344.
  • Simpson, E. H. (1949). Measurement of diversity. Nature, 163, 688.
  • Song, Y., Merlin, L., & Rodriguez, D. (2013). Comparing measures of urban land use mix. Computers, Environment and Urban Systems, 42, 1–13.
  • van den Hoek, J. W. (2008). The MXI (Mixed-use Index) as tool for urban planning and analysis. In Corporations and Cities, TU Delft.

Testing

The test suite checks every index against hand-computed values. Examples include shares (0.5, 0.25, 0.25), checkerboard grids for the dissimilarity index, and an exact graticule cell for the spherical area. Edge cases covered are single categories, all-zero zones, missing categories and invalid amounts. The suite also reproduces the published two-decimal values of Iannillo & Fasolino (2021, Table 6) for Shannon–Wiener, entropy and HHI.

pip install -e ".[dev,pandas,shapely]"
python -m pytest

Limitations

  • Bordoloi et al. (2013). The paper proposes modified dissimilarity and entropy indices, a per-trip-purpose Area Index and a Mix type Index. The full text could not be consulted for this release, so only area_index is provided. It implements the paper's description: the share of zone land in uses relevant to one trip purpose. It is not verified against the paper's equation. The Mix type Index is not implemented.
  • The dissimilarity index uses the dominant use of each cell. How edge cells and undeveloped (None) neighbours are treated is a documented choice (edge=).
  • With method="centroid", a polygon goes whole to the zone or cell containing its centroid, and the centroid of a strongly concave polygon can fall outside it. Use method="intersection" (shapely) for exact area splitting.
  • A point exactly on a shared zone edge goes to the zone on its right or upper side, the same rule the grid uses.
  • Moving windows are straight-line (Euclidean or haversine) buffers on feature representative points. Network buffers are out of scope.
  • The grid is built in coordinate units. Grids in longitude/latitude have cells of unequal area.
  • The OSM category table is not a standard and should be reviewed for each study.

License

MIT © 2026 Yusuf Eminoğlu. See LICENSE. To cite landmix, see CITATION.cff.

Academic citation

@software{eminoglu_landmix,
  author  = {Eminoğlu, Yusuf},
  title   = {landmix},
  year    = {2026},
  version = {0.1.0},
  url     = {https://github.com/YusufEminoglu/landmix_sdk}
}

See also CITATION.cff.

Metadata

Release files for landmix 0.1.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 landmix 0.1.0
File Size Uploaded Provenance Actions
landmix-0.1.0.tar.gz 52.2 kB With provenance Details for landmix-0.1.0.tar.gz

Built distribution (wheel)

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

Total release size: 90.5 kB

Release files / landmix-0.1.0.tar.gz

Download URL landmix-0.1.0.tar.gz
Size 52.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f95178116d4a72d65dd72072876fd0cd054447a04b49f9a72c67350bd342a9d1
BLAKE2b-256 checksum
How to use checksums
ba760d71142dfbbecb469e1ede0e28e1fe957bf12e3e7a6e179b4c338edda626
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 / landmix-0.1.0-py3-none-any.whl

Download URL landmix-0.1.0-py3-none-any.whl
Size 38.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
864b1b9f46e595d4889c5327fdf190ba0168440a2fafbd1d95f33004c1ef6130
BLAKE2b-256 checksum
How to use checksums
1dc2e35b3a7f0dd5c35e00518755e4f4750a5333ec01e961d4f0ae86c7de882a
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 landmix 0.1.0

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 landmix-0.1.0.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.0 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