
Build a constant-territory time series for a reference year's boundaries
Source:R/constant_territory.R
build_constant_territory_series.RdEstimates a time series of a quantity over a fixed set of territorial
boundaries — the polities active in ref_year — from data reported under
the changing historical boundaries of each data year.
Country borders change over time, so there is no raw constant-territory series: a 1900 figure for "Austria-Hungary" is not a figure for present-day Austria. This function estimates one by spatial reallocation (dasymetric areal interpolation):
For each data
year, the value reported by each source polity is spread over that polity's own extent for that year across a regular grid, weighted by acovariatedensity (e.g. gridded cropland or population; uniform = plain areal weighting).The grid is then re-aggregated to the
ref_yeartarget boundaries: a target's estimate is the sum of grid mass falling inside it.Target territory not covered by any source with data in that year is imputed — its grid cells still carry covariate mass, so they are filled at a donor intensity (value per unit covariate) rather than left at zero. The fraction of a target's covariate mass that had to be imputed is reported as
imputed_share, an honest confidence signal.
The estimate is only as good as the covariate: supply the same gridded
surface used elsewhere in WHEP spatialization (cropland for crop output,
population for demographic series, livestock density for animals). With
covariate = NULL the method reduces to area-weighted areal interpolation.
Usage
build_constant_territory_series(
data,
ref_year,
polities = NULL,
covariate = NULL,
resolution = 25000,
donor = c("regional", "none"),
crs_equal_area = 6933,
max_cells = 2e+06,
verbose = TRUE
)Arguments
- data
A data frame of reported values with columns:
year: integer data year.polity_code: the source polity that reported the value (must be active inyearand carry a polygon).value: numeric value (summed if a polity appears more than once).
- ref_year
Integer. Target boundaries are the polities active in this year, under the validity convention described for
polities: a polity does not answer for the year its successor takes over. The same reading selects the sources of each data year.- polities
An
sfof polity polygons withpolity_code,start_year,end_yearand geometry, plus an optionalwiki_statuswhose effect is described below. Defaults toget_polity_geometries(), which supplies it.start_yearis inclusive;end_yearis exclusive at a succession and inclusive at the open end. So 2014 resolves to"RUS-2014-2025", never to"RUS-1991-2014", while 2025 still resolves to"RUS-2014-2025"because nothing succeeds it. An interval counts as open when it ends on the last year the supplied table covers and no later-starting interval of the same polity follows it. Where the table still carries overlapping intervals for one polity, the interval starting on the resolved year wins, then the latest-starting one, and finally – only whenwiki_statusis supplied – a live interval beats a"retired"or"superseded"one.That last key is the one behavioural difference between the two call styles, and it is stated here rather than left implicit. Supplying only the three required columns is legal and keeps the older, status-blind ordering, under which a shared start year is decided by nothing at all and an interval upstream has already replaced can take the year from the one that replaced it. On the shipped polities snapshot that is 1,344 polity-years across 10 polities, of which one moves territory rather than only a label: 1913 and 1914 resolve to
"MNE-1913-1915"(0.9923 Mha) instead of"MNE-1913-1918"(1.5893 Mha), a Montenegro 37.6% too small. Passwiki_status– as the default does – to get the live interval.wiki_statusnever selects which years an interval covers; it breaks a tie between two intervals that both cover the resolved year.- covariate
NULL(uniform density, i.e. area weighting) or a functionfunction(centroids_sf, year) -> numericreturning a non-negative density per grid-cell centroid (centroids are supplied incrs_equal_area).- resolution
Grid cell size, in metres of
crs_equal_area. Default 25000 (25 km). Smaller is more accurate but slower.- donor
Gap-imputation rule:
"regional"(default) fills uncovered target cells at the region-wide value-per-covariate intensity of the sources with data that year;"none"leaves them at zero (covered-only).- crs_equal_area
EPSG code of an equal-area CRS used for gridding and areas. Default 6933 (NSIDC EASE-Grid 2.0 Global).
- max_cells
Safety cap on grid cells per year (default 2e6). Aborts if the source/target extent would exceed it (usually a stray continent-scale target); restrict
polities, coarsenresolution, or raise this.- verbose
Logical; emit progress/warnings.
Value
A tibble, one row per (ref_year-target, data year):
target_polity_code,yearvalue: constant-territory estimate (covered + imputed)covered: mass from cells overlapping a source with dataimputed: mass added for uncovered cellsimputed_share: covariate fraction imputed (0 = fully observed)n_sources: number of source polities contributing that yearunallocated: total reported value from sources that could not be placed on the grid that year (constant within a year; 0 when all were placed). Kept out ofcovered/imputedso it is neither smeared nor lost.
Examples
# Self-contained toy: two adjacent square polities. Only "P1" reports a
# value in 1900, so when the series is rebuilt onto the boundaries active in
# `ref_year` 2000 (both polities), "P2" is imputed from "P1"'s intensity.
# `sf` is a suggested dependency, so the example runs only when it is there.
if (requireNamespace("sf", quietly = TRUE)) {
make_square <- function(xmin, ymin, side) {
sf::st_polygon(list(rbind(
c(xmin, ymin),
c(xmin + side, ymin),
c(xmin + side, ymin + side),
c(xmin, ymin + side),
c(xmin, ymin)
)))
}
polities <- sf::st_sf(
polity_code = c("P1", "P2"),
start_year = c(1800L, 1800L),
end_year = c(2025L, 2025L),
geometry = sf::st_sfc(
make_square(0, 0, 2),
make_square(2, 0, 2),
crs = 4326
)
)
reported <- tibble::tibble(
year = 1900L,
polity_code = "P1",
value = 100
)
build_constant_territory_series(
reported,
ref_year = 2000,
polities = polities,
resolution = 50000,
verbose = FALSE
)
}
#> # A tibble: 2 × 8
#> target_polity_code year value covered imputed imputed_share n_sources
#> <chr> <int> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 P1 1900 100 100 0 0 1
#> 2 P2 1900 100 0 100 1 1
#> # ℹ 1 more variable: unallocated <dbl>