Skip to contents

Combines biological nitrogen fixation (calculate_bnf()), residue/root N recycling (calculate_npp_carbon_nitrogen()), livestock manure (build_livestock_nutrient_flows()), atmospheric deposition (build_n_deposition()), human-population N (build_human_n()), soil organic-matter mineralization (build_carbon_balance()'s son_change_kgn_ha) and synthetic fertiliser (a country total spatialized to crops and cells via spatialize_country_n_to_crops()) into one long-format tibble of nitrogen inputs to agricultural land.

fert_type values: "bnf", "recycling", "manure_solid", "manure_liquid", "excreta", "deposition", "human", "som_mineralization", "synthetic" and "accum_loss". The last is a documented gap (perennial-crop standing-biomass N accumulation from the source implementation's nitrogen balance): its source computation was not available for this task, so it is never emitted, only reserved in the vocabulary.

Terms that are fundamentally per-cell or per-land-use rather than per-crop are allocated over the agricultural land support, either supplied as data$ag_land_support or derived by build_ag_land_support() from the gridded inputs already present. Deposition uses both cropland and grassland support. "human", "som_mineralization", and manure already assigned upstream to Cropland but lacking a crop use only local cropland support, so manure is not reassigned to grassland after the manure engine's capacity allocation. Forest and natural land are outside that support and therefore outside the agricultural balance. Grassland is represented by CBS 3000; no intensive/extensive class is inferred.

Usage

build_n_inputs(
  years = NULL,
  resolution = c("grid", "polity"),
  synthetic_method = NULL,
  method_unsupported = NULL,
  unattributed_method = NULL,
  polity_validity = c("keep", "flag", "drop"),
  data = list(),
  example = FALSE,
  manure_method = NULL
)

Arguments

years

Optional integer vector of calendar years to keep. NULL keeps every year the assembled inputs cover.

resolution

"grid" (default, per cell/crop/year/fert_type) or "polity" (summed to area_code/item_cbs_code/year/fert_type).

synthetic_method

Synthetic-N crop allocation method, "coello" or "area_share". When NULL (default), uses data$synthetic_method %||% "coello" for backwards compatibility.

method_unsupported

What happens to non-item nitrogen (deposition, human, soil-organic-matter mineralization, unattributed manure) whose own cell-year carries no allocation support at all. The support is the one unattributed_method selects: cropland under the default "cropland_area", cropland plus grassland under "agricultural_area", so the second argument decides both which rows are stranded and where they are rescued to (whep#1191). "abort" (the default, and the behaviour before this argument existed) refuses to continue and names the streams and the mass, so a coverage gap cannot be lost silently. "reallocate" spreads each such row over its own polity-year's support in proportion to area, which conserves mass and is the same rule spatialize_country_n_to_crops() already applies to a polity-crop with no crop-pattern cell; it still aborts when the polity-year has no support anywhere. "reallocate_drop" places the same rows the same way and then discards that irreducible remainder, warning with its mass, so a global build is not refused over a few polities with population and no agricultural land. "drop" reallocates nothing and discards every such row. Whichever is chosen is stamped on every output row as method_unsupported. May also be supplied as data$method_unsupported, which is how build_nitrogen_balance() forwards it.

unattributed_method

Where nitrogen that reached agricultural land but no single crop goes – manure the engine placed on Cropland or landed by transport without a crop, plus deposition, human N and SOM mineralization, all of which carry item_cbs_code = NA. "cropland_area" (default) spreads it over the cell-year's cropland support in proportion to each crop's hectares; "agricultural_area" spreads it over cropland and grassland support (CBS 3000), which is the land the manure engine's own transport sizing already counts as available room; "exclude" drops it, the pre-whep#532 behaviour, retained as a measurable sensitivity and not as a fallback – it warns with the excluded tonnage by stream. The allocating methods conserve mass or abort; "exclude" does not conserve it. When NULL (default), uses data$unattributed_method %||% "cropland_area".

This choice also governs method_unsupported (whep#1191): both run on one allocation support. Under "agricultural_area" a cell holding only grassland is no longer a stranded cell – its nitrogen lands on its own grassland and method_unsupported never sees it – and a "reallocate"/"reallocate_drop" rescue spreads over the polity-year's cropland and grassland rather than its cropland alone. Under "exclude" nothing reaches the stranded step, so method_unsupported is inert apart from its stamp.

polity_validity

What to do with a row whose (area_code, year) resolves to a polity that did not exist in that year (the cell-polity crosswalk has no year dimension, so an early-20th-century cell is labelled with its present-day territory). "keep" (default) keeps every row, which is the historical behaviour, and warns naming the rows, years and area codes involved. "flag" keeps them and adds the per-row logical reporting_polity_out_of_span, marking exactly which rows are stand-ins. "drop" removes them. All three warn; only "drop" changes the numbers. See polity_coverage_gaps(), which reports the same rows for an already-built table.

data

Named list of pre-loaded, caller-supplied upstream inputs. Each of the following is required for its corresponding fert_type to be emitted (a missing one skips that source rather than erroring, since callers may only want a subset). Supplying one and getting no nitrogen back is a different matter and is refused with a whep_absent_input error, because a stream that arrived empty is subtracted from the balance without changing anything the mass check can see – that check compares the assembled rows against themselves (#1034). The one exception is carbon_balance, which warns instead: its stream keeps only son_change_kgn_ha > 0, so contributing nothing is an observation there as well as a symptom.

  • bnf_input: calculate_bnf()'s required input tibble (lon, lat, area_code, year, item_prod_code, crop_npp_n_t, product_n_t, weed_npp_n_t, land_use, legumes_seeded, seeded_cover_crop_share, area_ha). The "bnf" term's n_input_t is the bnf_t column of calculate_bnf()'s output.

  • npp_n_input: calculate_npp_carbon_nitrogen()'s required input tibble (lon, lat, area_code, year, item_prod_code, item_cbs_code, product_dm_t, residue_dm_t, root_dm_t, optionally residue_soil_dm_t). The "recycling" term's n_input_t is root_n_t plus residue_soil_n_t, the residue N left on the field, from calculate_npp_carbon_nitrogen()'s output. When the input does not carry residue_soil_dm_t, calculate_residue_destinies() is applied to it first, with residue_destiny_method, residue_destiny_recovery and residue_bedding_fraction (see build_nitrogen_balance(); the same arguments split the residue removal outputs there) and the polity's region_krausmann and region_un_sub taken from area_code when the input does not carry them. An input that already carries residue_soil_dm_t is recycled on the caller's destinies, while build_nitrogen_balance() books its residue removals from residue_destiny_input with the arguments above: the soil-returned and the removed residue add up to the gross residue only when both come from the package's own destiny split.

  • recycling_residue_basis: "residue_soil_returned" (default) or "total_residue", which recycles the gross residue N (residue_n_t), removed residue included. Recorded in method_recycling_n.

  • livestock_intake: build_livestock_nutrient_flows()'s intake argument (the redistribute_feed() realised-intake contract), plus gridded (its land-surface layer) and resolution/methods (forwarded as-is). The manure terms sum applied_n from the $applied table of build_livestock_nutrient_flows()'s output, keyed by its territory, sub_territory, crop, land_use, manure_type and year; manure_type selects the "excreta", "manure_solid" or "manure_liquid" term. Under manure_method = "faostat" it supplies only the solid:liquid split.

  • manure, livestock_spatial: the manure_method = "faostat" inputs, read only under that method. manure is the faostat-emissions-livestock pin, scoped by the caller to the years wanted, as fertilizer is. livestock_spatial is a list of the surfaces build_gridded_livestock() spreads pasture manure over: gridded_pasture, gridded_cropland and manure_pattern (the spatialize-* pins its production callers read), plus an optional species_proxy overriding the mapping's proxies; needed only when cell_polity is supplied.

  • nhx, noy, cell_polity: build_n_deposition()'s inputs. From its output the "deposition" term reads deposition_kgn_ha (the whole-cell rate), deposition_n_t and area_category (only to form the in-scope share of each polycell's mass, see deposition_scope) and method_deposition; n_input_t is deposition_kgn_ha times that share times the support's area_ha, divided by 1000.

  • deposition_scope: which of a polycell's territory the "deposition" term is credited with. "territory" (default) is land plus inland water plus ice: nitrogen deposited on a lake or a glacier still drives indirect N2O and still reaches the eutrophication pathway, so the impact terms have to account for it. "land" is the terrestrial surface alone, for purposes that want it, and it needs a cell_polity carrying land_area_ha, inland_water_ha and ice_area_ha; asking for it without them aborts rather than silently returning the whole territory. Recorded in method_deposition_scope.

  • ag_land_support: agricultural physical land support keyed by lon, lat, area_code, year, item_cbs_code, with land_use ("cropland" or "grassland") and positive area_ha. Optional: when absent it is derived natively by build_ag_land_support() from cell_polity, type_cropland and crop_patterns (plus states or grassland_ha for the grassland side). Supply it to override that derivation with a better land surface. Cropland rows identify crop CBS items; all pasture/rangeland rows use CBS 3000.

  • grassland_source, gridded_pasture, grassland_ha, states: forwarded to build_ag_land_support() when the support is derived. grassland_source selects its grassland argument ("gridded_pasture" default, "luh2", or "none" for cropland-only support).

  • total_population, urban_population, cropland_ha, cell_polity: build_human_n()'s inputs. Which population is read is set by human_n_population_basis. The "human" term's n_input_t is the human_n_t column of build_human_n()'s output. The term was called "urban" before it was renamed.

  • human_n_population_basis: build_human_n()'s population_basis, "total" (default: total_population, from build_total_population_grid(), with the per-inhabitant rate) or "urban" (urban_population with the per-urban-inhabitant rate). Supplying only the other basis's population aborts. Recorded in method_human_population and method_human_kgn_cap.

  • human_n_method_residual: build_human_n()'s method_residual ("nearest" default, "polity", "keep" or "drop"), for human N the transport step leaves on a source cell with no cropland. Only what that rule leaves stranded reaches method_unsupported.

  • human_n_method_local_residual: build_human_n()'s method_local_residual ("room_cap" default or "uncapped"), for the residual a source cell WITH cropland gets back: capped at the cell's own room, the excess placed by human_n_method_residual, or kept whole.

  • carbon_balance: build_carbon_balance()'s "grid"-resolution output (lon, lat, area_code, land_use, year, area_ha, son_change_kgn_ha); this driver requires it supplied directly, it is never computed here. The "som_mineralization" term keeps the cropland rows with son_change_kgn_ha > 0 and takes son_change_kgn_ha times area_ha, divided by 1000, as n_input_t.

  • primary_prod, fertilizer, crop_patterns, type_cropland, cell_polity: the synthetic-fertiliser assembly (country total from fertilizer, the faostat-fertilizer-nutrients pin, split to crops by the chosen crop-share method, then to cells by crop_patterns/type_cropland). From fertilizer it reads the raw FAOSTAT columns Element ("Agricultural Use"), Item ("Nutrient nitrogen N (total)"), Year, Area Code and Value; Value becomes the country total synthetic_n_t, passed on as the n_t column of spatialize_country_n_to_crops()'s country_totals, whose output n_t is the "synthetic" term's n_input_t.

  • synthetic_method: how the synthetic-N country total is split across crops, "coello" (default; Coello 2025 rate-weighted, FAOSTAT- conserving) or "area_share" (harvested-area shares only).

  • coello_rates: crop-specific synthetic-N rate table shaped like coello_synthetic_n (year, area_code, item_cbs_code, kg_n_ha); defaults to whep::coello_synthetic_n. Used only when synthetic_method = "coello".

  • method_unsupported: the method_unsupported argument, supplied on data instead, so that build_nitrogen_balance() can select it by forwarding its own data list.

  • gridded, resolution, methods: forwarded to build_livestock_nutrient_flows(). resolution is the manure engine's own axis, not this function's: it defaults to "subnational" at resolution = "grid" (cell-level nitrogen needs cell-level manure) and to "national" otherwise. A value supplied here is always honoured.

  • residue_destiny_input, residue_destiny_method, residue_bedding_fraction: the residue-destiny split build_nitrogen_balance() documents. The straw it beds is passed to build_livestock_nutrient_flows() as bedding, one supply per year x territory that add_manure_bedding() spreads over the housed litter-using streams, so the bedding N the balance books as leaving the field returns in the manure input. Nothing is bedded when the input is absent or the split beds nothing.

example

If TRUE, return a small fixture instead of assembling real data. Defaults to FALSE.

manure_method

Where the three manure terms come from. "livestock_intake" (default) is WHEP's manure engine, build_livestock_nutrient_flows() over the realised feed intake. "faostat" reads them instead from the FAOSTAT livestock-emissions domain (data$manure, the faostat-emissions-livestock pin, source "FAO TIER 1", kg N): "manure_solid" and "manure_liquid" from "Manure applied to soils (N content)" for "All Animals", split with the engine's own solid share per country-year and spread to crops by harvested-area share and to cells by spatialize_country_n_to_crops(); "excreta" from the per-species "Manure left on pasture (N content)", mapped to species groups by inst/extdata/livestock_mapping.csv and spread to cells by build_gridded_livestock() with that mapping's spatial proxies. These are national statistics spread onto cells, a cross-check on the engine rather than the default. A country-year the engine gives no solid:liquid split for is booked wholly as solid, with a warning. Recorded in method_manure. When NULL (default), uses data$manure_method %||% "livestock_intake".

Value

A tibble. At resolution = "grid": lon, lat, area_code, item_cbs_code, year, fert_type, n_input_t, method_recycling_n, method_synthetic, method_deposition, method_deposition_scope, method_human_population, method_human_kgn_cap, method_unsupported, method_manure, excreta_cpp_share, method_excreta_species, method_unattributed. At resolution = "polity": area_code, item_cbs_code, year, fert_type, method_recycling_n, method_synthetic, method_deposition, method_deposition_scope, method_human_population, method_human_kgn_cap, method_unsupported, method_manure, method_unattributed, n_input_t (summed over cells), excreta_cpp_share (N-weighted over cells), method_excreta_species. method_manure records the source of the three manure terms ("livestock_intake", "faostat", or "faostat_all_solid" on applied manure booked as solid for want of an engine split) and is NA for every other fert_type. excreta_cpp_share is the share of an "excreta" row's N deposited by cattle, poultry and pigs, the animals of the IPCC 2019 EF3PRP,CPP factor (Vol 4, Ch 11, Table 11.1 (Updated), p. 11.13); sheep and other animals (EF3PRP,SO) deposit the rest. method_excreta_species records where the share came from ("engine_livestock_category", "faostat_species_group") or why the row has none ("fallback_label_no_species" for manure the engine labelled excreta only for want of a manure-type share, "no_excreta_n" for a row with no N). Both are NA for every other fert_type. method_recycling_n records which residue basis the "recycling" term used: "residue_soil_returned" (residue N net of removal for feed, bedding and burning) or "total_residue" (the gross residue N, only on request through data$recycling_residue_basis); it is NA for every other fert_type. method_synthetic records the synthetic crop-split basis ("coello" or "area_share") on "synthetic" rows and is NA for every other fert_type. method_deposition records which deposition product the "deposition" term's field came from, read off the supplied nhx/noy by build_n_deposition() ("hani" for read_n_deposition()'s own rows, "supplied" for an injected field carrying no tag of its own), so a corrected field stays visible here. method_deposition_scope records which of the polycell's territory the "deposition" term was credited with ("territory" or "land"). Both are NA for every other fert_type. method_human_population and method_human_kgn_cap record the "human" term's population basis and the denominator of its per-capita rate (see build_human_n()); both are NA for every other fert_type. method_unsupported records the rule applied to non-item nitrogen with no allocation support in its own cell, and is the same on every row; read it together with method_unattributed, which fixed that support. method_unattributed records the unattributed_method the whole assembly ran under and is stamped on every row, not only on the reallocated ones: under "exclude" no reallocated row survives to carry it, and a choice that removes nitrogen has to stay readable from the table. Both grains also carry the polity columns below, plus reporting_polity_out_of_span when polity_validity = "flag".

Details

polity_validity is forwarded to every builder this function calls that offers it – build_ag_land_support(), build_n_deposition(), build_human_n() and spatialize_country_n_to_crops() – and then applied to the assembled output, so one choice governs the whole assembly instead of each builder deciding on its own key space (whep#727). Under "drop" the support table loses those rows too, so a non-item input (deposition, human, SOM mineralization) whose own rows were supplied directly and therefore not dropped can find no cropland support left to allocate over; that aborts in the mass check rather than silently losing nitrogen.

Polity columns

Every area-keyed output carries the polity its area_code resolves to in that row's year:

  • polity_area_code: The numeric key rows are AGGREGATED on, for the matrix workflows. It is a bucket, not an identity: use reporting_polity_code to say which territory a row belongs to.

  • reporting_polity_code: The polity itself, e.g. ESP-1846-1914. It is year-aware, so the same area_code resolves to different polities in different years, which is the point of the crosswalk.

  • reporting_polity_name: Its name. It can differ from the area's own name where the area folds into an aggregate.

  • reporting_polity_has_geometry: Whether the polity has a polygon in the WHEP polity database, for callers that need to map or intersect it. FALSE is a documented gap upstream, not an error.

Rows whose area_code resolves to no polity keep the columns with NA rather than being dropped, so a gap is visible instead of silent.

Rows before the back-cast anchor year resolve to the polity live in that anchor year rather than to the polity live in the row's own year, because WHEP's pre-anchor series are back-cast onto the anchor-year territory. See add_polity_code() for the reasoning. Where that polity is not live in the row's own year – 41.5% of the pre-1961 (area, year) cells – add_polity_code() says so as mapping_status == "backcast_anchor", and polity_coverage_gaps() reports it as gap_kind == "backcast_anchor". These columns do not say so either way.

A row whose year no mapped period covers is resolved to the NEAREST period of the same area instead, so reporting_polity_code can name a polity that did not exist in that row's year – FAOSTAT bucket 206 "Sudan (former)" keeps reporting after SUD-1956-2011 ends, and its post-2011 rows carry that code. These columns do not say so: add_polity_code() reports such a row as mapping_status == "out_of_span", and that column is dropped here so that adding it does not change the schema of every area-keyed output at once. polity_coverage_gaps() reports the stand-in rows of a built table, and options(whep.polity_mapping_status = "flag") (or "status") carries the signal on the outputs themselves. Both are opt-in; the default is no extra column.

Examples

build_n_inputs(example = TRUE)
#> # A tibble: 9 × 22
#>    year area_code polity_area_code reporting_polity_code reporting_polity_name
#>   <int>     <int>            <int> <chr>                 <chr>                
#> 1  2020         1                1 ARM-1991-2025         Armenia              
#> 2  2020         1                1 ARM-1991-2025         Armenia              
#> 3  2020         1                1 ARM-1991-2025         Armenia              
#> 4  2020         1                1 ARM-1991-2025         Armenia              
#> 5  2020         1                1 ARM-1991-2025         Armenia              
#> 6  2020         1                1 ARM-1991-2025         Armenia              
#> 7  2020         1                1 ARM-1991-2025         Armenia              
#> 8  2020         1                1 ARM-1991-2025         Armenia              
#> 9  2020         1                1 ARM-1991-2025         Armenia              
#> # ℹ 17 more variables: reporting_polity_has_geometry <lgl>, lon <dbl>,
#> #   lat <dbl>, item_cbs_code <int>, fert_type <chr>, n_input_t <dbl>,
#> #   method_recycling_n <chr>, method_synthetic <chr>, method_deposition <chr>,
#> #   method_deposition_scope <chr>, method_human_population <chr>,
#> #   method_human_kgn_cap <chr>, method_unsupported <chr>, method_manure <chr>,
#> #   excreta_cpp_share <dbl>, method_excreta_species <chr>,
#> #   method_unattributed <chr>