
Assemble gridded nitrogen inputs from every WHEP N-input source.
Source:R/n_balance_inputs.R
build_n_inputs.RdCombines 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.
Arguments
- years
Optional integer vector of calendar years to keep.
NULLkeeps every year the assembled inputs cover.- resolution
"grid"(default, per cell/crop/year/fert_type) or"polity"(summed toarea_code/item_cbs_code/year/fert_type).- synthetic_method
Synthetic-N crop allocation method,
"coello"or"area_share". WhenNULL(default), usesdata$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_methodselects: 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 rulespatialize_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 asmethod_unsupported. May also be supplied asdata$method_unsupported, which is howbuild_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. WhenNULL(default), usesdata$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 andmethod_unsupportednever 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, somethod_unsupportedis 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 logicalreporting_polity_out_of_span, marking exactly which rows are stand-ins."drop"removes them. All three warn; only"drop"changes the numbers. Seepolity_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_typeto 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 awhep_absent_inputerror, 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 iscarbon_balance, which warns instead: its stream keeps onlyson_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'sn_input_tis thebnf_tcolumn ofcalculate_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, optionallyresidue_soil_dm_t). The"recycling"term'sn_input_tisroot_n_tplusresidue_soil_n_t, the residue N left on the field, fromcalculate_npp_carbon_nitrogen()'s output. When the input does not carryresidue_soil_dm_t,calculate_residue_destinies()is applied to it first, withresidue_destiny_method,residue_destiny_recoveryandresidue_bedding_fraction(seebuild_nitrogen_balance(); the same arguments split the residue removal outputs there) and the polity'sregion_krausmannandregion_un_subtaken fromarea_codewhen the input does not carry them. An input that already carriesresidue_soil_dm_tis recycled on the caller's destinies, whilebuild_nitrogen_balance()books its residue removals fromresidue_destiny_inputwith 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 inmethod_recycling_n.livestock_intake:build_livestock_nutrient_flows()'sintakeargument (theredistribute_feed()realised-intake contract), plusgridded(its land-surface layer) andresolution/methods(forwarded as-is). The manure terms sumapplied_nfrom the$appliedtable ofbuild_livestock_nutrient_flows()'s output, keyed by itsterritory,sub_territory,crop,land_use,manure_typeandyear;manure_typeselects the"excreta","manure_solid"or"manure_liquid"term. Undermanure_method = "faostat"it supplies only the solid:liquid split.manure,livestock_spatial: themanure_method = "faostat"inputs, read only under that method.manureis thefaostat-emissions-livestockpin, scoped by the caller to the years wanted, asfertilizeris.livestock_spatialis a list of the surfacesbuild_gridded_livestock()spreads pasture manure over:gridded_pasture,gridded_croplandandmanure_pattern(thespatialize-*pins its production callers read), plus an optionalspecies_proxyoverriding the mapping's proxies; needed only whencell_polityis supplied.nhx,noy,cell_polity:build_n_deposition()'s inputs. From its output the"deposition"term readsdeposition_kgn_ha(the whole-cell rate),deposition_n_tandarea_category(only to form the in-scope share of each polycell's mass, seedeposition_scope) andmethod_deposition;n_input_tisdeposition_kgn_hatimes that share times the support'sarea_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 acell_politycarryingland_area_ha,inland_water_haandice_area_ha; asking for it without them aborts rather than silently returning the whole territory. Recorded inmethod_deposition_scope.ag_land_support: agricultural physical land support keyed bylon,lat,area_code,year,item_cbs_code, withland_use("cropland"or"grassland") and positivearea_ha. Optional: when absent it is derived natively bybuild_ag_land_support()fromcell_polity,type_croplandandcrop_patterns(plusstatesorgrassland_hafor 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 tobuild_ag_land_support()when the support is derived.grassland_sourceselects itsgrasslandargument ("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 byhuman_n_population_basis. The"human"term'sn_input_tis thehuman_n_tcolumn ofbuild_human_n()'s output. The term was called"urban"before it was renamed.human_n_population_basis:build_human_n()'spopulation_basis,"total"(default:total_population, frombuild_total_population_grid(), with the per-inhabitant rate) or"urban"(urban_populationwith the per-urban-inhabitant rate). Supplying only the other basis's population aborts. Recorded inmethod_human_populationandmethod_human_kgn_cap.human_n_method_residual:build_human_n()'smethod_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 reachesmethod_unsupported.human_n_method_local_residual:build_human_n()'smethod_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 byhuman_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 withson_change_kgn_ha > 0and takesson_change_kgn_hatimesarea_ha, divided by 1000, asn_input_t.primary_prod,fertilizer,crop_patterns,type_cropland,cell_polity: the synthetic-fertiliser assembly (country total fromfertilizer, thefaostat-fertilizer-nutrientspin, split to crops by the chosen crop-share method, then to cells bycrop_patterns/type_cropland). Fromfertilizerit reads the raw FAOSTAT columnsElement("Agricultural Use"),Item("Nutrient nitrogen N (total)"),Year,Area CodeandValue;Valuebecomes the country totalsynthetic_n_t, passed on as then_tcolumn ofspatialize_country_n_to_crops()'scountry_totals, whose outputn_tis the"synthetic"term'sn_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 towhep::coello_synthetic_n. Used only whensynthetic_method = "coello".method_unsupported: themethod_unsupportedargument, supplied ondatainstead, so thatbuild_nitrogen_balance()can select it by forwarding its owndatalist.gridded,resolution,methods: forwarded tobuild_livestock_nutrient_flows().resolutionis the manure engine's own axis, not this function's: it defaults to"subnational"atresolution = "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 splitbuild_nitrogen_balance()documents. The straw it beds is passed tobuild_livestock_nutrient_flows()asbedding, one supply peryear x territorythatadd_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 toFALSE.- 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, thefaostat-emissions-livestockpin, 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 byspatialize_country_n_to_crops();"excreta"from the per-species "Manure left on pasture (N content)", mapped to species groups byinst/extdata/livestock_mapping.csvand spread to cells bybuild_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 inmethod_manure. WhenNULL(default), usesdata$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: usereporting_polity_codeto say which territory a row belongs to.reporting_polity_code: The polity itself, e.g.ESP-1846-1914. It is year-aware, so the samearea_coderesolves 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.FALSEis 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>