Skip to contents

Assembles per-capita protein and dietary-energy supply, the state variable for the SJOS-N nourishment ("just") axis. Protein is the SJOS-N nourishment axis; dietary energy is a secondary cross-check. The default "whep_native" method multiplies the WHEP commodity-balance food element (tonnes fresh matter, per year, area_code, item_cbs_code) by the per-item nutrition coefficients in whep::biomass_coefs and divides by national population. Protein per kilogram fresh matter is nitrogen times 6.25 (nitrogen-to-protein factor), on the basis selected by protein_basis. The nitrogen density is N_kgN_kgFM where available, otherwise Product_kgN_kgDM * Product_kgDM_kgFM. Edible_N_kgFM is not read: it is empty in every coefficient row, upstream as well as in the packaged data, so the edible basis is derived from Edible_portion instead of stored redundantly. N_kgN_kgFM must be a food-composition density for this to mean anything, because food_t for a processed FBS item is the primary equivalent of the products actually eaten: a whole-grain nitrogen density on that mass counts milling offal as food. Five cereal rows carried the agronomic value instead, and others a bare literal; biomass_coefs names both sets and what became of each (#1096: Olive, Rye and Honey now follow their food-composition rows, Oats, Maize and Rice do not). Wheat was the largest, at 1.27x FAOSTAT FBS world protein until #796, and now ships on the flour basis, 93 g of protein per kg, sourced and measured in biomass_coefs. Energy per kilogram fresh matter follows GE_product_edible_portion_MJ_kgFM, then GE_product_MJ_kgFM (MJ per kg fresh matter), converted to kilocalories via MJ / 0.004184. The energy term is GROSS (combustion) energy, not Atwater metabolisable energy, and so is only a secondary cross-check for SJOS-N; Atwater factors could refine it (O-B). Food items with no protein coefficient after the coalesce chain are excluded with a warning naming the count and a few examples (the residual gap-fill, O-B), never silently dropped.

An FBS item that covers several species reaches biomass_coefs through one representative Name_biomass in items_full. For FBS 2551 Nuts and products, ten species reached through Almonds, that one row is not representative: on FAO's own food-composition factors for the FBS, protein per 100 g of nut in shell runs from 1.8 g (chestnuts) to 10.3 g (pistachios), and almonds sit near the top at 8.0 (FAO, Food balance sheets: a handbook, Rome, 2001, Annex I, PDF page 65, https://www.fao.org/docrep/pdf/011/x9892e/x9892e00.pdf). With basket = "fao_composition" (default) the protein density of 2551 is those ten factors weighted by 2010 world production of each species (FAOSTAT production, element 5510, area 351 excluded), 64.7 g per kg against 80 for almonds alone (#1453). It replaces the protein term only, and only on the "edible_portion" basis, whose meaning (edible protein per kg of commodity) is what FAO's in-shell factor is. Energy keeps the representative row: FAO's factor is dietary energy, not the gross energy this function carries. Against the pre-2014 FBS, which FAO built from these same factors on what each country actually ate, the basket gives 0.98x 2010 world nut protein over 130 countries, against 1.21x for almonds alone. The world basket moves little over time (61 g/kg on 1961 production, 66 on 2024). "representative_species" keeps the single-species coefficient, for continuity and sensitivity analysis.

The "faostat_fbs" method is FAOSTAT's own per-capita supply, the independent benchmark for the default. With data$fbs_supply injected it is returned unchanged. Otherwise it is read from the faostat-fbs-old (1961-2013) and faostat-fbs-new (2010-2023) pins: protein and dietary energy per capita per day from item 2901 "Grand Total" (elements 674 and 664), which FAOSTAT reports directly, and population from item 2501 (element 511, thousands, converted to persons). FAOSTAT's energy element is dietary energy as FAOSTAT derives it from food composition, not the gross (combustion) energy of "whep_native", so the two methods' energy columns are not like for like. FAOSTAT areas are resolved onto area_code exactly as read_fbs_population() resolves them: year by year, dropping any area that resolves to no polity (the regional aggregates and area 351 "China", the aggregate over areas 41, 96, 128 and 214, #939); where more than one FAOSTAT area lands in a bucket-year the per-capita values are population-weighted. faostat-fbs-new wins an overlapping (year, area_code). The old pin follows FAOSTAT's pre-2014 FBS methodology and the new one the revised methodology, so a series crossing 2010 changes vintage there.

An area with food but no population row has no denominator, so it is absent from the output rather than wrong in it. Those areas are named at runtime in a warning, with the share of food protein that leaves with them, because which areas they are moves with every refresh of the gdp-population pin and of the food input: this sentence carried a count of 15 that was already 16 by the time #644 measured it. Read the warning, not a number in the documentation.

For orientation only, the areas with food protein and no population row in any year, measured against the faostat-fbs-new pin over the population pin's 1850-2021 span, are 13, headed by the China aggregate (351), Sudan (276) and South Sudan (277), then Comoros, New Caledonia, Bhutan and the small island states. Two of those are known open issues rather than data gaps: 351 is the aggregate whose members carry population separately, and 151 Netherlands Antilles is #787. options(whep.warn_missing_population = FALSE) silences the warning.

Usage

build_food_supply(
  method = c("whep_native", "faostat_fbs"),
  data = list(),
  protein_basis = c("edible_portion", "whole_commodity", "product_nitrogen"),
  basket = c("fao_composition", "representative_species"),
  example = FALSE
)

Arguments

method

Supply source: "whep_native" (default, commodity-balance food tonnes times whep::biomass_coefs divided by population) or "faostat_fbs" (FAOSTAT FBS per-capita supply, injected or read from the FBS pins).

data

Named list of injected inputs. For "whep_native": cbs_food (year, area_code, item_cbs_code, food_t) and population (year, area_code, population) are required, and biomass_coefs / items_full override the packaged whep::biomass_coefs / whep::items_full. For "faostat_fbs": fbs_supply (year, area_code, protein_g_cap_day, energy_kcal_cap_day, population) is returned as given if supplied; otherwise fbs_old and/or fbs_new, the raw pins in their own long FAOSTAT layout (Area Code, Item Code, Element Code, Year, Value), replace the whep_read_file() read of whichever is absent.

protein_basis

How the inedible fraction is treated when converting nitrogen density to protein, for "whep_native" only: "edible_portion" (default) scales the nitrogen density by Edible_portion, which is correct when food_t is commodity mass while the density applies to the edible part, and agrees best with FAOSTAT FBS; "whole_commodity" applies no edible scaling, the behaviour before this argument existed, kept for continuity and sensitivity analysis; "product_nitrogen" uses the agronomic Product_kgN_kgDM for both the edible and inedible fractions, scaled by Edible_portion, ignoring N_kgN_kgFM. A missing Edible_portion counts as 1.

basket

Protein density of multi-species FBS items, for "whep_native" with protein_basis = "edible_portion" only: "fao_composition" (default) weights FAO's FBS food-composition factors per species by world production (today FBS 2551 Nuts and products); "representative_species" uses the one biomass_coefs row that items_full bridges the item to.

example

If TRUE, return a small fixture instead of computing. Defaults to FALSE.

Value

A tibble keyed by year, area_code with protein_g_cap_day, energy_kcal_cap_day, population, method_food_supply, method_protein_basis and method_basket (both NA for "faostat_fbs"), plus the polity columns below.

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_food_supply(example = TRUE)
#> # A tibble: 3 × 12
#>    year area_code polity_area_code reporting_polity_code reporting_polity_name
#>   <int>     <int>            <int> <chr>                 <chr>                
#> 1  2010        10               10 AUS-1901-2025         Australia            
#> 2  2010        32               32 CMR-1961-2025         Cameroon             
#> 3  2011        10               10 AUS-1901-2025         Australia            
#> # ℹ 7 more variables: reporting_polity_has_geometry <lgl>,
#> #   protein_g_cap_day <dbl>, energy_kcal_cap_day <dbl>, population <dbl>,
#> #   method_food_supply <chr>, method_protein_basis <chr>, method_basket <chr>