Hestia Distribution
Prior and posterior distributions of crop yield and input use (fertiliser, pesticide, irrigation), per country and per product, for the Hestia platform. Use them to get an expected range for a value, or to score how plausible a reported value is.
What it does
For a (country, product) pair the library produces a (mu, sigma) normal distribution for
each quantity, built in two layers:
-
Prior — the starting belief, derived from FAOSTAT. For yield it is the national yield time series; for inputs it is national input use. The mean is an area-weighted average over the years, and the spread is the year-to-year variation of that national figure, floored by a cross-country coefficient of variation so a country with a flat series is not given an implausibly tight interval. Priors are cheap and exist for every
(country, product)FAO reports. -
Posterior — the prior updated with Hestia's own Cycle data for that pair, via Bayesian MCMC (a small Stan model sampled with cmdstanpy). Where enough real cycles exist the posterior is dominated by them and departs from the prior; where they don't, it stays close to the prior. A posterior is only published if the data can support it: at least
MIN_CYCLEScycles (default 30) and a fit that converged (r̂ ≤ 1.01). Otherwise no posterior is written and the prior stands — so a consumer always has something to fall back to.
The read path (getting a distribution back out) is pure lookup: it reads pre-built files from a local folder and needs no sampler. Only generating posteriors needs cmdstanpy and CmdStan.
Install
pip install hestia_earth.distribution
Reading distributions needs only this. To generate posterior files, also install the sampler:
pip install hestia_earth.distribution[stats]
./install-cmdstan.sh
cmdstanpy drives
CmdStan, which is compiled on the machine
rather than installed from pip — hence the second step. It needs a C++ toolchain
(build-essential on Debian/Ubuntu, the Xcode command line tools on macOS) and takes a few
minutes the first time.
Getting the files
The library reads pre-built distribution files from a local folder, set by
DISTRIBUTION_DATA_FOLDER (default ./data). The files are published at
https://api.hestia.earth/distribution/files as a JSON list of download URLs — the four
FAO prior CSVs and one posterior CSV per country.
Run this once to populate the folder (no credentials needed):
import json
import os
import urllib.request
DATA_FOLDER = os.getenv("DISTRIBUTION_DATA_FOLDER", "./data")
files = json.load(urllib.request.urlopen("https://api.hestia.earth/distribution/files"))
for group, subfolder in [("priorFiles", "prior_files"), ("posteriorFiles", "posterior_files")]:
folder = os.path.join(DATA_FOLDER, subfolder)
os.makedirs(folder, exist_ok=True)
for f in files[group]:
urllib.request.urlretrieve(f["url"], os.path.join(folder, f["name"]))
print(f"downloaded {len(files[group])} {subfolder}")
That lays out <DATA_FOLDER>/prior_files/ and <DATA_FOLDER>/posterior_files/, which is
exactly where the accessors below look. The download URLs are short-lived, so fetch the list
fresh each time rather than saving a URL to reuse. Each file is then read once per process and
cached, so checking many values costs one read per file, not one per lookup.
Getting distribution data
Each quantity has its own module, and every one exposes the same two accessors:
get_prior(...)— the FAO-derived prior, always available where FAO has data.get_post(...)— the posterior, or(None, None)when none was published for that pair. When it is(None, None), fall back toget_prior(...).
Both return a (mu, sigma) tuple.
from hestia_earth.distribution.prior_yield import get_prior as get_prior_yield
from hestia_earth.distribution.posterior_yield import get_post as get_post_yield
get_prior_yield("GADM-GBR", "wheatGrain") # -> (8061.2, 3736.7) FAO prior
get_post_yield("GADM-GBR", "wheatGrain") # -> (7900.4, 1970.5) updated with Hestia cycles
get_post_yield("GADM-GBR", "oatGrain") # -> (None, None) no posterior; use the prior
The other quantities follow the same shape, differing only in what identifies the row:
# Fertiliser: keyed by product AND input. The prior is now crop-specific - derived as
# yield x crop nutrient removal / recovery efficiency, where the removal is the nutrient in the
# harvested product - so a product is required (it was per-input before).
from hestia_earth.distribution.prior_fert import get_prior as get_prior_fert
from hestia_earth.distribution.posterior_fert import get_post as get_post_fert
get_prior_fert("GADM-GBR", "inorganicNitrogenFertiliserUnspecifiedKgN", "wheatGrain") # (mu, sigma)
get_post_fert("GADM-GBR", "wheatGrain", "inorganicNitrogenFertiliserUnspecifiedKgN") # (mu, sigma)
# Pesticide and irrigation: the prior is per country only.
from hestia_earth.distribution.prior_pest import get_prior as get_prior_pest
from hestia_earth.distribution.posterior_pest import get_post as get_post_pest
get_prior_pest("GADM-GBR") # (mu, sigma)
get_post_pest("GADM-GBR", "wheatGrain") # (mu, sigma) or (None, None)
from hestia_earth.distribution.prior_irrigation import get_prior as get_prior_irri
from hestia_earth.distribution.posterior_irrigation import get_post as get_post_irri
get_prior_irri("GADM-GBR") # (mu, sigma)
get_post_irri("GADM-GBR", "wheatGrain") # (mu, sigma) or (None, None)
get_post returns the mean of the posterior ensemble. If you need the full ensemble of draws
rather than its mean, call get_post_ensemble(country_id, product_id) from the same module,
which returns (mu_ensemble, sd_ensemble) as lists.
Validating a value
A (mu, sigma) distribution gives a confidence interval directly: with the usual 95%
interval, a value is an outlier when it falls outside mu ± 1.96 · sigma. Prefer the
posterior and fall back to the prior:
from hestia_earth.distribution.posterior_yield import get_post as get_post_yield
from hestia_earth.distribution.prior_yield import get_prior as get_prior_yield
def yield_interval(country_id, product_id, z=1.96):
mu, sigma = get_post_yield(country_id, product_id)
if mu is None: # no posterior -> use the prior
mu, sigma = get_prior_yield(country_id, product_id)
if mu is None: # no distribution at all
return None
return (max(mu - z * sigma, 0), mu + z * sigma) # yield cannot be negative
yield_interval("GADM-GBR", "wheatGrain") # -> (4038.2, 11762.7)
Joint (multivariate) plausibility
The interval above checks each quantity on its own. To score a combination — e.g. "is 8500 kg/ha of wheat alongside 200 kg N/ha jointly plausible for the UK?" — use the multivariate fit, which accounts for the correlation between yield and input use. It needs no extra and reads the underlying cycle data.
from hestia_earth.distribution.utils.MCMC_mv import calculate_fit_2d, calculate_fit_rows
# candidate follows the column order [Nitrogen (kg N), Grain yield (kg/ha)]
likelihood, _ranges = calculate_fit_2d([200, 8500], "GADM-GBR", "wheatGrain")
# likelihood: the share of observed samples with a lower joint density than the pair.
# Above ~5% is acceptable.
# or against rows you have already selected, e.g. only the cycles complete for fertiliser
likelihood = calculate_fit_rows([200, 8500], rows, ["Nitrogen (kg N)", "Grain yield (kg/ha)"])
Generating distributions
Building prior and posterior files (rather than reading them) is done through each module's
generate_* and update_all_post functions — for example
generate_prior_yield_file(overwrite=True) and
update_all_post(country_id, product_ids=..., overwrite=False) in posterior_yield.
Generating posteriors samples the Stan model and therefore needs the [stats] extra and
CmdStan installed (see Install). Passing product_ids restricts the run to the products
that actually have cycles in a country, which is far cheaper than walking every product.
Configuration reference
| Variable | Purpose | Default |
|---|---|---|
DISTRIBUTION_DATA_FOLDER |
folder the library reads distribution files from | ./data |
DISTRIBUTION_MIN_CYCLES |
minimum cycles before a posterior is published | 30 |
DISTRIBUTION_POST_CACHE_SIZE |
how many countries' posterior files to hold in memory | 8 |
Metadata
Release files for hestia-earth-distribution 0.7.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| hestia_earth_distribution-0.7.3.tar.gz | 42.5 kB | Without provenance | Details for hestia_earth_distribution-0.7.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| hestia_earth_distribution-0.7.3-py3-none-any.whl | Python 3 | none | any | Details for hestia_earth_distribution-0.7.3-py3-none-any.whl |
Total release size: 94.9 kB
Release files / hestia_earth_distribution-0.7.3.tar.gz
| Download URL | hestia_earth_distribution-0.7.3.tar.gz |
|---|---|
| Size | 42.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3d81646d510f74677fb6325c85df413fc1cfa3b1cf3d63d4f14f5b069d006e6a
|
|
BLAKE2b-256 checksum How to use checksums |
2f1340ca08a597da404a3ddb713808b02e1790be42f9d7e4e631482f90deaa68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.16
|
Release files / hestia_earth_distribution-0.7.3-py3-none-any.whl
| Download URL | hestia_earth_distribution-0.7.3-py3-none-any.whl |
|---|---|
| Size | 52.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4bca10e8e8c87bb5fecc2ccffff4699bb83e4da7b2ea2ae0d0686aacf29680aa
|
|
BLAKE2b-256 checksum How to use checksums |
028391bc8af4cba5c03c78679dc413041230b49d8419f68af927ab4a031b7fe9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.16
|
Security for hestia-earth-distribution 0.7.3
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.