Skip to contents

This page is the map: what is in the package, what the cut-offs are, one complete run from raw columns to every metric, and one thing about survey weights that affects every number you report.

The run is deliberately brief. Each metric family has its own article, with worked examples for a single value, a column, and adding results to a data frame. Links are at the bottom, and in the Articles menu above.

What EIOF is

Intake’s Environmental Impacts of Foods (EIOF) database gives country-specific environmental impact values for more than 6,500 foods across seven impact areas spanning five domains of planetary health. What makes it unusual is not only its coverage but its adjustment factors: non-edible portion, processing and cooking yield, so that the form of food consumed in a dietary recall can be matched correctly to the form of food the impact data describes.

Alongside the database, Intake published a set of dietary environmental impact metrics for use with food consumption survey data. These come in three families:

Family Question it answers Source Article
Absolute threshold Is this diet below the per capita daily share of a planetary boundary? Table 3 vignette("table3-absolute")
Relative benchmark Where does this diet sit in a global reference distribution? Tables 4a–4g vignette("table4-relative")
Composite Is this diet both healthy and environmentally sound? Tables 5a, 5b vignette("table5-composites")

All of them are age- and sex-specific, because average dietary energy requirement differs across groups.

What reiod is — and is not

reiod implements those metrics in R.

It does not contain the EIOF database. It ships the published cut-offs and applies them to impact values you have already derived. The work of matching food items to impact factors, applying the adjustment factors, and summing to the respondent-day happens upstream; reiod starts where that ends. It also does not calculate the Global Diet Quality Score — the composite metrics take a GDQS value as an input.

So the input reiod expects is one row per respondent-day, with the impact totals already summed:

head(eiod_demo, 3)
#>   id  age  sex land_total_m2yr land_arable_m2yr land_pasture_m2yr ghg_kgco2e
#> 1  1 16.4 male        6.661992        9.1117404          6.257198   7.709933
#> 2  2 33.0 male        6.173535        3.6705075          1.322853   7.400251
#> 3  3 45.5 male        8.260851        0.4574517          9.283150   1.127271
#>   eutroph_gpo4e water_litres   biodiv_pdf gdqs
#> 1      3.062745     289.1277 1.670692e-12 26.7
#> 2     10.051376     361.7203 5.547457e-13 18.4
#> 3      8.827951     401.3645 9.416969e-13 35.0

The package has no dependencies beyond base R: it imports only stats and utils, which ship with every R installation. The practical consequence is a plain vector API: you pass df$biodiv_pdf, not biodiv_pdf.

A first run, start to finish

Every step below is covered in depth in its own article. This is the whole workflow on the demo data, so you can see how the pieces fit before reading them one at a time.

1. The demographic group. Every cut-off is age- and sex-specific, so this comes first (vignette("demographic-groups")):

d <- eiod_demo
d$grp <- eiod_age_sex_group(d$age, d$sex)
table(d$grp)
#> 
#>   1   2   3   4   5   6   7   8 
#>   6  17  25 257   9  11  22 253

2. Check the inputs (vignette("data-checks")). Your columns can be named anything; eiod_demo_map states which column holds which indicator:

eiod_demo_map
#>           landtotal          landarable         landpasture                 ghg 
#>   "land_total_m2yr"  "land_arable_m2yr" "land_pasture_m2yr"        "ghg_kgco2e" 
#>             eutroph               water              biodiv 
#>     "eutroph_gpo4e"      "water_litres"        "biodiv_pdf"
summary(eiod_check_data(d, impacts = eiod_demo_map,
                        age = "age", sex = "sex", gdqs = "gdqs"))
#> PASS - 0 error, 0 warning, 33 note

3. Table 3, absolute thresholds (vignette("table3-absolute")). One 0/1 column per indicator, 1 meaning below the threshold:

abs7 <- eiod_absolute_all(d, d$grp, eiod_demo_map)
round(100 * colMeans(abs7, na.rm = TRUE), 1)
#>   abs_landtotal  abs_landarable abs_landpasture         abs_ghg     abs_eutroph 
#>            70.2            80.8            68.7            22.2            53.5 
#>       abs_water      abs_biodiv 
#>            95.2            57.1

4. Tables 4a–4g, relative bands (vignette("table4-relative")). Bands 1–10 against the fixed global reference distribution:

band7 <- eiod_relative_all(d, d$grp, eiod_demo_map)
table(band7$band_ghg)
#> 
#>   1   2   3   4   5   6   7   8   9  10 
#> 249  30  65  78  29   1   4  63  23  58

5. Tables 5a and 5b, composites (vignette("table5-composites")). The under-twos are set to NA, and a message says so:

d$hesd  <- eiod_hesd(d$biodiv_pdf, d$gdqs, d$grp, form = "binary")
#> 15 observation(s) aged under 2 years set to NA: the GDQS is not validated below age 2, so the HESD metric does not apply to them.
d$hdlei <- eiod_hdlei(d$biodiv_pdf, d$gdqs, d$grp, form = "binary")
#> 15 observation(s) aged under 2 years set to NA: the GDQS is not validated below age 2, so the HDLEI metric does not apply to them.

6. Put the results back in your data, one row per respondent-day:

d <- cbind(d, abs7, band7)
head(d[, c("id", "grp", "abs_ghg", "band_ghg", "hesd", "hdlei")])
#>   id grp abs_ghg band_ghg hesd hdlei
#> 1  1   4       0        8    0     0
#> 2  2   4       0        8    0     0
#> 3  3   4       1        1    1     0
#> 4  4   4       1        1    1     1
#> 5  5   8       0        2    0     0
#> 6  6   4       0        4    0     0

These percentages are unweighted, on synthetic data. They demonstrate the code, not a finding; see Survey weights before reporting real prevalences.

What is in the package

ls("package:reiod")
#>  [1] "eiod_absolute_all"       "eiod_absolute_threshold"
#>  [3] "eiod_age_sex_group"      "eiod_as_sex_factor"     
#>  [5] "eiod_check_data"         "eiod_check_demographics"
#>  [7] "eiod_check_gdqs"         "eiod_check_impact"      
#>  [9] "eiod_check_magnitude"    "eiod_check_precision"   
#> [11] "eiod_cutoffs"            "eiod_demo"              
#> [13] "eiod_demo_map"           "eiod_hdlei"             
#> [15] "eiod_healthy_diet"       "eiod_hesd"              
#> [17] "eiod_indicators"         "eiod_list_indicators"   
#> [19] "eiod_lookup_cutoff"      "eiod_low_impact"        
#> [21] "eiod_relative_all"       "eiod_relative_band"     
#> [23] "eiod_sustainable_diet"
Group Functions
Demographic group eiod_age_sex_group(), eiod_as_sex_factor()
Metrics eiod_absolute_threshold(), eiod_relative_band(), eiod_hesd(), eiod_hdlei()
Several indicators at once eiod_absolute_all(), eiod_relative_all()
Decomposed sub-indicators eiod_healthy_diet(), eiod_sustainable_diet(), eiod_low_impact()
Data quality checks eiod_check_data() and five component checks
Reference lookups eiod_list_indicators(), eiod_lookup_cutoff()
Shipped data eiod_cutoffs, eiod_indicators, eiod_demo, eiod_demo_map

The seven indicators, and their units

Units matter more here than in most analyses, because a unit error produces a confidently wrong answer rather than an obvious one. This is the function to check what the package expects:

eiod_list_indicators()
#> Seven environmental impact indicators, per capita per day:
#> 
#>   landtotal    Total land use             m2*year
#>   landarable   Arable land use            m2*year
#>   landpasture  Pasture land use           m2*year
#>   ghg          Greenhouse gas emissions   kg CO2eq
#>   eutroph      Eutrophication potential   g PO4(3-)eq
#>   water        Water use                  litres
#>   biodiv       Biodiversity loss          PDF*year
#> 
#> Values must already be summed across everything the respondent
#> consumed that day. reiod does not compute impacts from food items.
#> 
#> These are the EIOF database's own units. Supply your totals as they
#> come - no indicator needs converting.
#>   Changed Oct 2026: eutrophication used to need a /1000 step, because
#>   the paper printed its Table 3 threshold in kg. Intake has corrected
#>   that row to grams. Remove the division from older scripts.

The indicator key in the first column is what you pass to every function. Your own column can be called anything — the two never have to match.

The cut-offs

All 616 published cut-offs ship as eiod_cutoffs, in long format:

str(eiod_cutoffs)
#> 'data.frame':    616 obs. of  15 variables:
#>  $ metric             : chr  "abs" "abs" "abs" "abs" ...
#>  $ table              : chr  "3" "3" "3" "3" ...
#>  $ indicator          : chr  "biodiv" "biodiv" "biodiv" "biodiv" ...
#>  $ indicator_label    : chr  "Biodiversity loss" "Biodiversity loss" "Biodiversity loss" "Biodiversity loss" ...
#>  $ unit               : chr  "PDF*year" "PDF*year" "PDF*year" "PDF*year" ...
#>  $ demogroup          : int  1 2 3 4 5 6 7 8 1 2 ...
#>  $ demogroup_label    : chr  "Male 0-1.9 yr" "Male 2-4.9 yr" "Male 5-9.9 yr" "Male 10+ yr" ...
#>  $ sex                : Factor w/ 2 levels "male","female": 1 1 1 1 2 2 2 2 1 1 ...
#>  $ age_min            : num  0 2 5 10 0 2 5 10 0 2 ...
#>  $ age_max            : num  1.9 4.9 9.9 999 1.9 4.9 9.9 999 1.9 4.9 ...
#>  $ row_order          : int  7 7 7 7 7 7 7 7 5 5 ...
#>  $ percentile         : int  NA NA NA NA NA NA NA NA NA NA ...
#>  $ value              : num  3.69e-13 5.72e-13 7.88e-13 1.28e-12 3.57e-13 ...
#>  $ value_published_str: chr  "3.6896027444E-13" "5.7244838747E-13" "7.8836516181E-13" "1.2844358718E-12" ...
#>  $ value_source       : chr  "published" "published" "published" "published" ...
table(eiod_cutoffs$table, eiod_cutoffs$metric)
#>     
#>      abs rel
#>   3   56   0
#>   4a   0  80
#>   4b   0  80
#>   4c   0  80
#>   4d   0  80
#>   4e   0  80
#>   4f   0  80
#>   4g   0  80

Table 3 — one absolute threshold per group

eiod_lookup_cutoff("ghg", metric = "abs")
#> Table 3 absolute threshold - Greenhouse gas emissions
#>   unit: kg CO2eq per capita per day
#> 
#>   group            value         as printed
#>   Male 0-1.9 yr    0.6155661134  0.6155661134
#>   Male 2-4.9 yr    0.9550617056  0.9550617056
#>   Male 5-9.9 yr    1.3152930335  1.3152930335
#>   Male 10+ yr      2.1429277142  2.1429277142
#>   Female 0-1.9 yr  0.5964328302  0.5964328302
#>   Female 2-4.9 yr  0.8845537944  0.8845537944
#>   Female 5-9.9 yr  1.2076082237  1.2076082237
#>   Female 10+ yr    1.7237261333  1.7237261333
#> 
#>   printed at 15 significant digits.
#>   `as printed` is the literal text this package ships, which has
#>   never been through a numeric type. Nothing is rescaled here.

Printed to 15 significant digits on purpose: the published cut-offs need up to 14, and R’s default options(digits = 7) would make it look as though the package had rounded the guideline. The as printed column is the literal text transcribed from the paper, so you can check it character for character.

Tables 4a–4g — nine decile cut-points per group

subset(eiod_cutoffs,
       metric == "rel" & indicator == "ghg" & demogroup == 4 &
         !is.na(percentile),
       select = c(percentile, value))
#>     percentile     value
#> 247         10  3.105870
#> 248         20  3.552641
#> 249         30  4.529023
#> 250         40  5.767824
#> 251         50  6.595250
#> 252         60  6.602176
#> 253         70  6.738681
#> 254         80  8.944839
#> 255         90 10.091426

Tables 5a and 5b — no new cut-offs

The composites define none. HESD reuses the Table 3 biodiversity threshold; HDLEI reuses the Table 4g P30. Both obtain them by calling the metric functions, so there is no second copy in the code and nothing extra in the data.

Eight values differ from the published PDF

On Intake’s instruction. The value_source column marks them:

subset(eiod_cutoffs, value_source != "published",
       c(demogroup_label, metric, unit, value))
#>    demogroup_label metric        unit     value
#> 9    Male 0-1.9 yr    abs g PO4(3-)eq  7.682720
#> 10   Male 2-4.9 yr    abs g PO4(3-)eq 11.919876
#> 11   Male 5-9.9 yr    abs g PO4(3-)eq 16.415830
#> 12     Male 10+ yr    abs g PO4(3-)eq 26.745323
#> 13 Female 0-1.9 yr    abs g PO4(3-)eq  7.443923
#> 14 Female 2-4.9 yr    abs g PO4(3-)eq 11.039886
#> 15 Female 5-9.9 yr    abs g PO4(3-)eq 15.071844
#> 16   Female 10+ yr    abs g PO4(3-)eq 21.513377

Supply eutrophication in grams and convert nothing. vignette("eutrophication") has the whole story.

Survey weights

reiod returns respondent-level indicator vectors and nothing else. It applies no survey design.

Every prevalence you report should come from a design-aware estimator — the survey package, or whatever your analysis uses — or both your point estimates and your standard errors will be wrong for a complex design. The percentages printed throughout these articles are unweighted, because they are demonstrating code rather than reporting results.

library(survey)
des <- svydesign(ids = ~psu, strata = ~stratum, weights = ~wt, data = d)
svymean(~abs_ghg, des, na.rm = TRUE)
svyby(~abs_ghg, ~group, des, svymean, na.rm = TRUE)

Where to go next

Read them in this order the first time:

  1. vignette("data-checks") — what to run before computing anything, and why only one check is automatic
  2. vignette("demographic-groups") — the eight age × sex cells every cut-off is scaled by
  3. vignette("table3-absolute") — Table 3
  4. vignette("table4-relative") — Tables 4a–4g
  5. vignette("table5-composites") — Tables 5a and 5b

Then as needed: