The central conversion verb: takes a table keyed by unit code at frame
from and returns one keyed at to – a frame name of the same
NestedScale, or ANOTHER NestedScale (whose atom layer is the target, matched on
shared atom keys). Handles both aggregation (fine to coarse) and
disaggregation (coarse to fine) with one rule per value column; frames that
cross-cut work too, because the route always goes through the atom layer.
A crosswalk registered with register_scale_map() short-circuits the
derivation.
The data to recast, in any supported backend, with a column
named by key plus one or more numeric value columns; other columns are
preserved as identifiers.
The NestedScale the codes in data belong to.
Frame name the codes belong to. NULL (default) is inferred:
key when it is a frame name, else the single frame name appearing among
the data's columns.
Target frame name of x, or another (named) NestedScale – then
the target is that object's atom layer, matched on shared atom keys.
Name of the code column in data. Defaults to from when that
column exists, otherwise the scale's own key.
Character vector of value columns to convert. Default: all
numeric columns other than the key and the scale's frame columns. Numeric
identifiers (e.g. year) must be excluded explicitly.
One of SCALE_RULES applied to every value column, or a
named vector with one entry per column; NULL (default) looks each
column up with get_scale_rule(). A column with neither an explicit
rule nor a registry entry is an ERROR – there is deliberately no
fallback (a silently guessed rule is a silent unit error).
Weight column used by sum (splitting) and weighted_mean;
scalar or named per column. NULL uses each column's registered weight,
falling back to the object's default weight; when the object declares no
weights at all, atoms weigh 1 (an equal split, warned about when it is
material).
What to do with atoms that have no code at from or to:
"drop" (default, with a warning – the affected source share is
genuinely lost), "error", or "keep" (retain an explicit NA row so
totals conserve).
rule = "share" only: the frame defining the groups the
shares are taken within. NULL (default) uses to when it differs from
from, else the frame immediately above from.
For lazy inputs: materialise the result (TRUE) or return
the uncollected query (default).
Whether to run the checks that need a scan of the data:
unknown source codes, dropped shares, and source units missing from the
data. "auto" (default) runs them on eager inputs and skips them on
arrow/dtplyr, where a scan would defeat the point of returning a query;
"on" and "off" force the choice. Errors that do not depend on the
data – unknown frames, reserved column names, a missing rule – are
raised regardless.
What to do when a target unit's sources are only
partially present in the data. "na" (default) makes the result NA –
a partial answer is not an answer. "ignore" aggregates whatever is
there, which is faster but silently turns incomplete data into a
plausible number, so it is opt-in.
Process the job in chunks of at most batch distinct
values of the identifier column batch_by (default: the first identifier
column), binding the pieces. Chunking by an identifier partitions the
aggregation groups exactly, so the numbers are unchanged; any other
column is refused. Applies only when materialising, and turns off
per-chunk diagnostics, which would otherwise report sources as missing
merely because they fall in another chunk.
Passed to scale_map() (e.g. year for a calendar).
The recast table in the input's class, with columns
c(to, identifiers, values).
Columns of the data that are neither the key nor a value column are treated as identifiers (panel columns – a year, a technology) and preserved as grouping columns, so panel data recasts correctly in one call. This is what makes mixed pipelines across two dimensions work: a recast in one dimension carries the other dimension's key through untouched. Columns named like the scale's own frames are treated as unit attributes and dropped.
"sum" splits each source value across its unit's atoms proportionally to
the weight before summing up, so totals are conserved. "weighted_mean"
weights by the atom weights; "mean" is the plain atom-count mean – the
two differ exactly when atom weights differ. "copy" requires a constant
value per target unit; "sd" is aggregation-only.
"share" inverts the output contract: the result stays keyed at from,
and each value becomes that unit's share of the total over its parent
group. from must nest within the parent. It cannot be combined with other
rules in one call, and weight= is ignored – the observed values
themselves are the weights.
The data may be a data.frame, tibble, data.table, dtplyr lazy table,
or an arrow Dataset/Table/query. The result comes back in the input's class;
lazy inputs return the uncollected query unless collect = TRUE. Lazy
results contain the observed target units only – the full-vocabulary
completion (and its NA rows) applies when the result is materialised.
s <- scale_example()
# Extensive quantity, fine -> coarse: totals are preserved
d <- data.frame(
unit = c("U1", "U2", "U3", "U4", "U5", "U6"),
capacity = c(1, 2, 3, 4, 5, 6)
)
recast_scale(d, s, from = "unit", to = "sector", rule = "sum")
#> sector capacity
#> 1 P 10
#> 2 S 11
# Coarse -> fine: split proportionally to size
y <- data.frame(sector = c("P", "S"), capacity = c(10, 20))
recast_scale(y, s,
from = "sector", to = "class",
rule = "sum", weight = "size"
)
#> class capacity
#> 1 G1 3
#> 2 G2 7
#> 3 S1 20
# Share within parent: result stays at the atoms, sums to 1 per sector
recast_scale(
d, s,
from = "unit", to = "sector",
rule = "share",
na_action = "keep"
)
#> unit capacity
#> 1 U1 0.1000000
#> 2 U2 0.2000000
#> 3 U3 0.3000000
#> 4 U4 0.4000000
#> 5 U5 0.4545455
#> 6 U6 0.5454545
#> 7 OTH NA