nestedscales represents a discrete data dimension at multiple resolutions and recasts tabular data between them. A nested scale stores one row per finest-grained unit, or atom, and one column for each coarser grouping, or frame. Each frame assigns atoms to units; frames may form a hierarchy or cross-cut one another, including product-like overlays. All mappings are derived from membership at the atom level.
The package defines:
sum,
weighted_mean, mean, and copy;
sd for aggregation; and share or
logshare for within-parent shares.The package is dimension-agnostic. Time and space are provided by two packages built on it, designed for optimization and simulation models, where labeled time slices and regions serve as the index sets of variables and constraints:
Calendar, labeled discrete time slices (hours, days,
seasons, representative days).GeoScale, labeled geographical regions with geometry.Both classes are subclasses of NestedScale. Any other
dimension (industries, income brackets, technology vintages) is declared
directly from a table. vignette("nestedscales") introduces
the functions; the package website
includes a roadmap of what is available and what is planned.
Until the first CRAN release, install the development version from GitHub:
pak::pkg_install("optimal2050/nestedscales")The package is pure R; its non-base runtime dependencies are dplyr, rlang, S7 and jsonlite.
Declare the levels, coarsest first:
library(nestedscales)
industries <- data.frame(
section = c("C", "C", "C", "D", "D"),
division = c("C10", "C10", "C11", "D35", "D35"),
class = c("C101", "C102", "C110", "D351", "D352"),
gva = c(120, 80, 45, 300, 150)
)
ind <- scale_from_leaftable(
industries,
frames = c("section", "division", "class"),
weights = "gva",
name = "nace"
)
ind
#> NestedScale: nace
#> Frames (3, coarsest first):
#> - section (2)
#> - division (3)
#> - class (5)
#> Atoms: 5
#> Weights: gva (default: gva)Every structural question is answered from the atoms, never from an assumed tree:
scale_frames(ind)
#> [1] "section" "division" "class"
scale_units(ind, "division")
#> [1] "C10" "C11" "D35"
scale_nests(ind, "section", "division")
#> [1] TRUEHierarchies that cross-cut are normal and supported – one detailed
code may belong to several aggregates.
scale_nests(x, parent, child) tests whether every child
code belongs to a single parent code; conversion never depends on that
relationship.
summary(ind)
#> <summary of NestedScale 'nace'>
#> frames: section (2) / division (3) / class (5)
#> atoms: 5
#> weight totals: gva = 695 (default: gva)
#> nesting: section > division: nested
#> nesting: division > class: nestedData keyed at one level is recast to another with a rule per value
column. With complete coverage, "sum" conserves totals;
intensive quantities use "weighted_mean" with a declared
weight:
x <- data.frame(
class = c("C101", "C102", "C110", "D351", "D352"),
jobs = c(12, 8, 5, 30, 20),
wage = c(31, 29, 27, 45, 40)
)
recast_scale(x, ind, source = "class", target = "section",
rule = c(jobs = "sum", wage = "weighted_mean"),
weight = "gva")
#> section jobs wage
#> 1 C 25 29.61224
#> 2 D 50 43.33333Scales travel as definition files (a JSON header with the tables inline, or a bundle with Arrow or CSV tables) that the Python binding reads as well. The package ships one: the energypal fuel taxonomy with the IPCC 2006 default calorific values and CO2 factors attached as attributes.
carriers <- read_scale("carriers")
carriers
#> NestedScale: carriers
#> Description: Energy carriers: fuels and energy vectors with subtypes, from energypal's carriers palette (IPCC 2006 / IEA classifications)
#> Frames (3, coarsest first):
#> - group (8)
#> - carrier (17)
#> - fuel (71)
#> Atoms: 71
#> Attributes: lhv, ef_co2 (sources: IPCC2006) -- 26 at fuel
#> Aliases: 136 (energypal, commodity)
#> Source: energypal carriers.yml
fuel_use <- data.frame(fuel = c("Anthracite", "NaturalGas"), t = c(100, 100))
convert_scale(fuel_use, carriers, "lhv", from_unit = "t", to_unit = "GJ")
#> fuel t lhv lhv.origin_frame
#> 1 Anthracite 2670 26.7 fuel
#> 2 NaturalGas 4800 48.0 fuelscale_attributes() resolves a value for every member of
a frame, walking to the nearest coarser level that holds one;
scale_match() resolves labels and model codes through the
alias table, strictly.
gva, employment and count side by
side, with one declared default.NA, not an error, and a subset records
what fraction of its parent it keeps.data.frame, a
data.table, or an arrow dataset; lazy inputs stay
lazy.reconcile_scale() here compares data aggregated with
sum or weighted_mean against independent
totals and can balance discrepancies.Apache License 2.0.