Disaggregate country-level FAOSTAT livestock stocks and emissions to a 0.5-degree grid. Each species group uses a tailored spatial proxy:
Ruminants (cattle, buffalo, sheep/goats, equines): LUH2 managed pasture (
pastr) plus rangeland (range), optionally weighted by a static manure-intensity reference (West et al. 2014).Confined animals (pigs, poultry): LUH2 aggregate cropland, reflecting intensive farming co-location with crop production.
Range specialists (camels): LUH2 rangeland only.
Mixed (other animals): 50/50 blend of pasture and cropland.
For each country, year, and species group the function distributes the national total proportionally to cell-level proxy weights:
$$\text{cell} = \frac{w_i}{\sum_{j \in \text{country}} w_j} \times T_{\text{country}}$$
where \(w_i\) is the proxy weight in cell \(i\) (land-use hectares times optional reference-pattern intensity) and \(T\) is the country total (heads or emissions).
Methodology
Livestock spatialization is not covered by LandInG (Ostberg et al. 2023), which focuses on crops only. The approach here extends the LandInG framework by using the same LUH2-based spatial proxies (pasture, rangeland, cropland) for livestock distribution.
Country-level data comes from build_primary_production() (stocks)
and the faostat-emissions-livestock pin (CH4/N2O emissions), with
predecessor redistribution and pre-1961 backfill already applied.
The Zenodo livestock density input (Heinke 2025,
doi:10.5281/zenodo.14946695) provides an alternative calibrated
LSU/ha reference for use with the glw_density parameter.
Data sources and references
| Source | Use |
| FAOSTAT Production_Livestock (FAO 2024) | Country-level heads |
| FAOSTAT Emissions_livestock (FAO 2024) | Enteric CH4, manure CH4/N2O |
| LUH2 v2h (Hurtt et al. 2020) | Time-varying pasture + cropland |
| West et al. (2014) | Static manure-N intensity reference |
| GLW3 (Gilbert et al. 2018) | Species-specific density (optional) |
| Heinke (2025) | Calibrated LSU/ha density (optional) |
| IPCC 2006/2019 | N-excretion rates, emission factors |
Usage
build_gridded_livestock(
livestock_data,
gridded_pasture,
gridded_cropland,
country_grid,
species_proxy = NULL,
manure_pattern = NULL,
glw_density = NULL,
grass_productivity = NULL,
years = NULL,
area_key = c("grid", "polity_area")
)Arguments
- livestock_data
A tibble with country-level livestock data. Required columns:
year: Integer year.area_code: Country code (WHEP polities).species_group: Livestock functional-type name (e.g."cattle","pigs","poultry").heads: Live animal count (number of head). Any additional numeric columns (e.g.enteric_ch4_kt,manure_ch4_kt,manure_n2o_kt,manure_n_mg) are distributed to the grid using the same proportional weights asheads.
- gridded_pasture
A tibble with annual gridded pasture extent. Required columns:
lon,lat: Cell centre coordinates (0.5 degree).year: Integer year.pasture_ha: Managed pasture area in hectares (LUH2pastr).rangeland_ha: Rangeland area in hectares (LUH2range).
- gridded_cropland
A tibble with annual gridded cropland extent. Required columns:
lon,lat: Cell centre coordinates.year: Integer year.cropland_ha: Total cropland area in hectares.
- country_grid
A tibble mapping grid cells to countries. Required columns:
lon,lat: Cell centre coordinates.area_code: Country code.cell_area_frac(orpolity_frac,area_frac,country_frac): This polity compartment's share of the physical cell, a partition summing to 1 over the polities that overlap the cell. Required: a grid carrying no share is refused, because defaulting it to 1 gives a border cell wholly to one polity. Pass 1 only where the polity does own the whole cell. A land fraction (landfrac) is a different quantity and is refused rather than reinterpreted. Optional columns:polycell_id,cell_id: Stable compartment/cell identifiers preserved in outputs when present.yearor validity intervals (valid_from/valid_to,start_year/end_year,from_year/to_year) for historical, time-varying polity overlays. The start bound is inclusive; the end bound is exclusive at a succession and inclusive at the open end, so 2014 selects"RUS-2014-2025"and not"RUS-1991-2014", while 2025 still selects"RUS-2014-2025"because no later interval of that compartment follows it. See polities for the full rule.
- species_proxy
A tibble mapping each
species_groupto its spatial proxy type:"pasture","cropland","rangeland", or"mixed". Required columns:species_group: Group name (must matchlivestock_data).spatial_proxy: One of"pasture","cropland","rangeland", or"mixed". IfNULL, a default mapping is used (see Details).
- manure_pattern
A tibble with static manure-intensity weights (e.g. from West et al. 2014). Optional. Expected columns:
lon,lat: Cell centre coordinates.manure_intensity: Relative intensity (kg N per ha or similar). Values are used multiplicatively with the land-use proxy. IfNULL, land-use weights are used alone.
- glw_density
A tibble with species-specific gridded livestock density from GLW3 (Gilbert et al. 2018). Optional. Expected columns:
lon,lat: Cell centre coordinates.species_group: Must matchlivestock_data.density: Heads per cell (reference year ~2010). If provided, this replaces the LUH2-based proxy for the matching groups, while still being scaled by LUH2 time trends. IfNULL, LUH2 proxies are used for all groups.
- grass_productivity
A tibble with grass productivity per cell (
lon,lat,grass_npp) fromread_lpjml_grass_productivity(). Optional. When provided, it multiplies thepasture/rangeland(grazer) proxy weights so animals follow grass production rather than area alone; cropland/mixed proxies are unaffected. IfNULL, area proxies are used alone.- years
Integer vector of years to spatialize. If
NULL(default), all years present inlivestock_dataare processed. When supplied,livestock_data,gridded_pasture, andgridded_croplandare filtered to this set before processing.- area_key
Which area code the output is keyed on:
"grid"(default, the reporting codeslivestock_dataandcountry_gridare keyed on) or"polity_area"(the polity_area_crosswalk bucket national tables are aggregated on). Seebuild_gridded_landuse()'s Which area code the output is keyed on.
Value
A tibble with gridded livestock data. Columns:
lon,lat: Cell centre coordinates.area_code: WHEP polity code for this cell compartment.polity_area_code,reporting_polity_code,reporting_polity_name,reporting_polity_has_geometry: Polity metadata forarea_code.grid_area_code: Only underarea_key = "polity_area"; the reporting code the engine allocated on.polycell_id,cell_id: Preserved when supplied incountry_grid.year: Integer year.species_group: Livestock functional type.heads: Allocated live animal count.Any additional numeric columns from
livestock_data(e.g.enteric_ch4_kt,manure_ch4_kt).
Which area code the output is keyed on
The chain allocates from a national table keyed on area_code and
into a country_grid keyed the same way, so both sides speak the raw
reporting vocabulary the grid was rasterized in. WHEP's polity-keyed
national tables are aggregated on polity_area_code instead, a bucket
that a reporting code need not equal: 276 Sudan and 277 South Sudan
both fall in bucket 206. Every such output row therefore carries two
territorial keys that disagree, and whether a consumer joins on
area_code or on polity_area_code decides whether Sudan exists in its
result (whep#582).
area_key selects which of the two the output carries. It is not a
fallback: "grid" is the default, reproduces today's codes
bit-for-bit, and warns naming the codes that cannot join;
"polity_area" resolves each code to its bucket through
polity_area_crosswalk before the polity columns are attached, so
area_code and polity_area_code agree in every row. It respects
options(whep.unfold_rest_of_world) (see folded_reporting_areas()),
so the output and the national tables agree about where a Rest-of-World
member's rows belong.
Under "polity_area" the raw reporting code is carried, not
replaced: the output gains grid_area_code, joined with + where two
reporting areas of one bucket meet in a cell and their rows collapse. So
the fold stays recoverable at the join rather than baked into the output,
the shape build_cell_polity() adopted for the same reason (whep#579).
Examples
# Minimal example with toy data
livestock_data <- tibble::tribble(
~year, ~area_code, ~species_group, ~heads,
2000L, 1L, "cattle", 5000
)
gridded_pasture <- tibble::tribble(
~lon, ~lat, ~year, ~pasture_ha, ~rangeland_ha,
0.25, 50.25, 2000L, 600, 200,
0.75, 50.25, 2000L, 400, 100
)
gridded_cropland <- tibble::tribble(
~lon, ~lat, ~year, ~cropland_ha,
0.25, 50.25, 2000L, 800,
0.75, 50.25, 2000L, 500
)
country_grid <- tibble::tribble(
~lon, ~lat, ~area_code, ~cell_area_frac,
0.25, 50.25, 1L, 1,
0.75, 50.25, 1L, 1
)
build_gridded_livestock(
livestock_data, gridded_pasture, gridded_cropland, country_grid
)
#> ℹ Spatializing 1 groups over 1 years
#> # A tibble: 2 × 10
#> year area_code polity_area_code reporting_polity_code reporting_polity_name
#> <int> <int> <int> <chr> <chr>
#> 1 2000 1 1 ARM-1991-2025 Armenia
#> 2 2000 1 1 ARM-1991-2025 Armenia
#> # ℹ 5 more variables: reporting_polity_has_geometry <lgl>, species_group <chr>,
#> # lon <dbl>, lat <dbl>, heads <dbl>
