Data dictionary¶
What each table contains, column by column, where it came from, and — for HLD, where one country-year is often covered by several published life tables — exactly which row a lookup returns and why.
How the tables get onto your machine¶
Only SSA ships inside the wheel: a US federal work in the public domain, 10 KB, so US lookups work offline the moment the package is installed. HLD and WHO are downloaded on request:
lost_years update --source all # or hld / who / ssa
lost_years status # what is installed, and has upstream moved?
lost_years sources # where each table comes from, and on what terms
HLD is not shipped because lifetable.de asks that users fetch their own copy (“Please do not pass your copy of these data to other users. Rather refer them to the HLD website, where they may download the data for themselves”). WHO is not shipped because a packaged copy is stale the day WHO publishes a revision and nothing in the wheel would say so.
Tables are written to the per-user data directory, overridable with
$LOST_YEARS_DATA_DIR; lost_years status prints the path. A downloaded table
always takes precedence over a shipped one.
What an update does¶
Download to a scratch directory. A transfer that stops short of the announced
Content-Lengthis an error, not a smaller file.Build a typed Parquet table with an explicit Arrow schema.
Validate the candidate: the schema must match field for field, including types and nullability; the row count must not be below the release the package was tested against, because these databases only grow; and the values have to pass the source’s own checks (below).
Swap atomically. The candidate is written under a temporary name in the destination directory and renamed into place, so a reader sees either the old table or the new one. A candidate that fails any check is not installed and the previous table is untouched.
What each source is checked against¶
Source |
Check |
|---|---|
HLD |
US male life expectancy at birth must come out at 76.22 / 76.31 / 74.19 / 73.55 for 2018-2021, through the selection rule below, against NCHS’s published 76.2 / 76.3 / 74.2 / 73.5. Plus: one row per life table and age in the national table, and at least 90% of rows carrying a published |
SSA |
Complete single years of age from 0; life expectancy falling with age for both sexes; life expectancy at birth in 60-95 years; women outliving men at every age. SSA is itself the publisher, so there is no external oracle — what is checked is that the table has the structure a period life table must have. |
WHO |
At least 180 countries; every value in 15-100 years; the both-sexes figure between the male and female ones for 99% of country-years; women outliving men in at least 95% of them. The last check is the asymmetric one: exchanging the sex labels leaves the both-sexes test passing and only this notices. |
Provenance manifest¶
Every derived table has a <name>.parquet.manifest.json beside it:
Field |
Meaning |
|---|---|
|
which database, and where to read its terms |
|
the URL the release is published at |
|
redistribution terms |
|
upstream’s own identifier — HLD’s release date, SSA’s and WHO’s table year |
|
|
|
UTC timestamp of the build |
|
row count and digest of the table |
|
digest of the raw artifact it was built from |
|
every field’s name, Arrow type and nullability |
|
source-specific facts, e.g. how many upstream lines could not be read |
lost_years status re-digests the installed table and reports damaged when it
no longer matches its manifest.
HLD (Human Life-Table Database)¶
Source. The pooled file at
https://www.lifetable.de/File/GetDocument/data/hld.zip, a single ZIP holding
one bare CSV named res (21 columns, 202 MB). The upstream codebook is
archived at data/hld/source/formats.pdf; the raw archive this repository
builds from is data/hld/source/hld.zip.
Release identifier. The modification time of the res member inside the
archive: the release lifetable.de labels 07.04.2025 carries 2025-04-07.
Taking it from the artifact rather than from the page it was linked on means
the manifest describes the file that was actually built. lost_years status
compares it against the newest date on
lifetable.de/Data/WhatsNew.
Terms. No affirmative redistribution grant. Cite the HLD and send others to the website.
Unit of observation of the file. One row is one age interval of one published life table: country × sub-population × source publication × version × reference period × table type × sex × age. 2,182,429 rows, 45,330 life tables, 142 countries, 1751-2024.
Unit of observation of a lookup. One row per input row, from one life table.
HLD is not an estimate per country-year, so this is a choice the package makes,
not a property of the data; the rule is below and hld_n_candidates reports how
many tables the last step of it chose between.
Columns of hld.parquet¶
The derived table keeps the 15 of 21 upstream columns a lookup needs, adds two derived ones, and drops the other life-table functions — m(x), q(x), l(x), d(x), L(x), T(x) — which play no part in reading a life expectancy off a table.
Column |
Arrow type |
Unit / universe |
Value set |
Missing |
Upstream field |
|---|---|---|---|---|---|
|
|
one country or area |
142 ISO 3166-1 alpha-3 codes (or HLD extensions) |
never |
|
|
|
principal subdivision |
|
never (see repairs) |
|
|
|
urban / rural split |
|
never |
|
|
|
ethnicity, religion or race |
|
never |
|
|
|
socio-demographic group |
|
never |
|
|
|
HLD’s revision counter within one source and year |
1-3 |
never |
|
|
|
source publication |
|
never |
|
|
|
|
derived |
when |
derived |
|
|
first calendar year the table covers |
1751-2023 |
never |
|
|
|
last calendar year covered; equals |
1760-2024 |
never |
|
|
|
how the table was produced |
|
never |
|
|
|
sex |
|
never |
|
|
|
lower bound of the age interval, exact years |
0-119 |
never |
|
|
|
length of the age interval, years |
1-15, and the sentinels below |
never |
|
|
|
remaining years at exact |
0-100 |
never (rows without it are dropped) |
|
|
|
the same quantity as printed in the original publication |
0-100 |
1,067 rows; upstream writes |
|
|
|
largest |
≥ 0 |
when no row of the table has a published value |
derived |
Sentinel and out-of-range values in age_interval¶
Value |
Rows |
Meaning |
|---|---|---|
|
45,330 |
the open-ended top age interval — exactly one per life table, so |
|
34 |
upstream defect: KOR 2020 sub-national tables (Ref-IDs 3446.01-3446.02, regions 10 and 20) |
|
3 |
upstream defect: ITA 2018 sub-national tables (Ref-IDs 390.15-390.17, regions 10, 11, 70) |
A negative interval cannot define [age, age + interval), so those 37 rows are
never selected. All 37 are sub-national, so no whole-country answer changes.
Known upstream and packaging defects¶
Defect |
Extent |
Handling |
|---|---|---|
Life tables written with a comma decimal separator inside a comma-delimited file, so the row carries 25 or 26 fields where the header declares 21 |
1,290 lines in 5 tables: ITA region 200 (Ref-ID 392.09, 2020), MYS region 160 (1492.17, 2020), NZL ethnicities E020/E090/E350/E360 (3363, 3363.04-.06, 2017) |
dropped, and counted — |
|
1,334 rows, 7 countries |
treated as whole-country: the codebook has no “region unknown” code and every such row is a national table (NIU, KIR, NRU, and the single-year national tables for HUN 2018, IRN 2004, ISR 2013-17, SWE 2019) |
Sub-population codes and |
181,953 |
gone: the build reads those columns as text. A naive |
Negative |
37 rows, sub-national ITA and KOR |
cannot define an interval, so dropped at selection |
|
2,357 life tables in all, 357 of them whole-country (15,306 national rows) |
quarantined, see below |
The selection rule¶
For a query (country, year, sex, age), in order:
Whole country, total population.
region == residence == ethnicity == socdem == '0'.subpopulations=Truelifts this and returns one row per sub-population instead.Drop
type_lt == 2. Type 2 is HLD’s own abridgement of the type 1 complete table from the same source, so it never carries information the finer table does not already have.Quarantine tables that fail their own cross-check.
e(x)ande(x)Origare independent computations of the same quantity, so a large gap means at least one is wrong and neither can be trusted. A table is dropped when the gap exceeds 2 years anywhere in it. This is what catches ARG 1980 Ref-ID 1042.01, whose recalculated e(0) of 79.11 contradicts its own published 65.48 — implying 24,014 men per 100,000 still alive at 95. The error runs both ways: YUG 1980-82 Ref-ID 3950.01 printse(x)Origof 80.00 at age 93 against a recalculated 1.81, a slip for 1.80. Passmax_ex_discrepancy=Noneto serve them anyway.The quarantine changes real answers. Three national tables cover France in 2010; the 2010-2012 one recalculates female life expectancy at birth to 81.51 against its own published 84.84. Dropping it leaves 84.70, against INSEE’s published 84.7.
Period containment. Keep life tables with
year1 <= year <= year2. 2,482 country-years in HLD are reachable only through a multi-year period table, so containment is required rather than optional. A year no table covers returns nothing;year_tolerancereaches to the nearest period and records the distance inhld_match_status.Narrowest period. Of those, keep the tables with the smallest
year2 - year1, so a 1980 table beats a 1976-1980 table for 1980.Age interval. Keep the row whose interval
[age, age + age_interval)contains the requested age. Many HLD tables are abridged, so age 22 is answered from the interval starting at 20;hld_ageandhld_age_intervalreport which.Tie-break convention. Highest
version, then highestref_id_sort, then latestyear1, then lowesttype_lt. Version is HLD’s own revision counter, so the highest is the most revised table; Ref-ID rises as sources are added, so the highest is the most recently added source. The last two keys exist only to make the order total. About 17% of country-year cells reach this step with more than one candidate — median disagreement 0.38 years, but 78 cells disagree by more than 2 — sohld_n_candidatesreports the count rather than hiding it.
What each filter costs¶
Stage |
Rows |
|---|---|
read from |
2,182,429 |
whole country, total population |
717,457 |
drop |
600,113 |
quarantine |
584,807 |
142 countries appear in the file; 133 have a whole-country total-population
table at all, and 129 have one that survives the quarantine. BFA, ETH, GHA,
GMB, GNB, MOZ, PSE, TZA and ZMB have only sub-national or sub-population
tables, and ARE, ASM, PAK and THA have only tables that fail the
cross-check. All thirteen return hld_match_status of “no eligible life table
for country” rather than a sub-population figure passed off as the country.
Output columns¶
Column |
Meaning |
|---|---|
|
matched country and sex |
|
period the matched life table covers |
|
matched age interval; |
|
|
|
which life table was used |
|
largest absolute gap between |
|
how many equally eligible tables the tie-break chose between |
|
|
|
sub-population codes; |
WHO (Global Health Observatory)¶
Source. GHO OData indicator
WHOSIS_000001, life
expectancy at birth. CC BY 4.0 — attribution to the World Health
Organization is required when you redistribute figures derived from it. The
archived raw payload is data/who/source/WHOSIS_000001.json.gz.
Unit of observation. One row per population, calendar year and sex. There
is no age dimension: this indicator is life expectancy at birth only.
lost_years_who therefore takes no age input, returns
who_life_expectancy_at_birth, and raises ValueError if an age mapping is
passed. For remaining life expectancy at a given age, use lost_years_hld.
Universe. 12,936 rows, 2000-2021. Not all rows are countries: the
indicator also carries WHO regional, World Bank income-group and global
aggregates, and spatial_type says which is which. Filter on
spatial_type == 'COUNTRY' if you want countries only.
Columns of who.parquet¶
Column |
Arrow type |
Unit / universe |
Value set |
Missing |
|---|---|---|---|---|
|
|
population |
185 ISO-3 country codes, 5 WHO region codes, 4 World Bank income groups, |
never |
|
|
display name |
from |
never |
|
|
what kind of population |
|
never |
|
|
calendar year |
2000-2021 |
never |
|
|
sex |
|
never |
|
|
years at birth |
36.60-87.37 |
never (rows without a value are dropped) |
|
|
lower bound of WHO’s uncertainty interval |
20 rows |
|
|
|
upper bound |
20 rows |
ParentLocation is deliberately not carried: GHO puts the WHO region
there, not a country name, which is how Somalia once came to be labeled
“Eastern Mediterranean”.
Year is matched to the closest table year with no default limit, and the
matched year is always reported in who_year; year_tolerance imposes a hard
limit.