---
title: "Get started with nestedscales"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Get started with nestedscales}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

nestedscales provides a multi-level discrete representation of a data
dimension and the operations that move data between its levels. This
vignette introduces the representation and the main functions on a small
industry classification. The dimension packages
[timescales](https://github.com/optimal2050/timescales) and
[geoscales](https://github.com/optimal2050/geoscales) apply the same
functions to calendars and region hierarchies. The
[package website](https://optimal2050.github.io/nestedscales/) includes a
roadmap of the package's scope.

```{r setup}
library(nestedscales)
```

## Representation

A **nested scale** is a table with one row per atom, the finest unit of the
dimension, and one column per frame, a coarser level that groups the atoms.
The frames are given coarsest first. Numeric columns that are not frames are
weights.

```{r build}
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"),
  name   = "nace"
)
ind
```

The scale records the frames, the units at each frame, the atom key and the
weights:

```{r accessors}
scale_frames(ind)
scale_units(ind, "division")
scale_key(ind)
scale_weights(ind)
```

Every frame partitions the atoms. Frames need not nest in each other: a
unit at one frame may belong to several units at a coarser frame, and two
frames may be independent, in which case the atoms are their combinations.
`scale_nests()`, `scale_crosses()` and `scale_is_uniform()` report the
relation a given pair of frames has; no relation is assumed.

```{r structure}
scale_nests(ind, "section", "division")
scale_family(ind, "section", "division")
```

## Transition rules

Data keyed at one frame is converted to another frame with
`recast_scale()`. Each value column follows a rule from `SCALE_RULES`:

| rule | aggregation (fine to coarse) | disaggregation (coarse to fine) |
|---|---|---|
| `sum` | sum | split in proportion to the weight |
| `weighted_mean` | weighted mean | copy |
| `mean` | mean over atoms | copy |
| `copy` | the common value | copy |
| `sd` | standard deviation over atoms | not defined |
| `share` | value as a share of its parent's total | not defined |

The rule is given per value column, so extensive and intensive quantities
convert in one call:

```{r recast-up}
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")
)
```

The same function converts in the other direction. A total at the section
level is split over the classes in proportion to the weight:

```{r recast-down}
y <- data.frame(section = c("C", "D"), investment = c(100, 400))

recast_scale(y, ind, source = "section", target = "class", rule = "sum")
```

Columns that are neither the key nor a value column are identifiers and are
preserved as groups, so a panel of several years or scenarios converts in
one call. A rule may also be registered once for a named value column with
`register_scale_rule()`, after which `rule =` can be omitted for that
column.

## Labels

`join_scale()` attaches the scale's labels to a dataset: the unit at the
keyed frame, optionally the coarser frames (`attach =`) and the unit's share
and weight (`meta =`):

```{r join}
join_scale(x, ind, frame = "class", attach = TRUE)
```

## Subsets

`filter_scale()` keeps the atoms under given units; the result records what
fraction of each weight it retains. `prune_scale()` collapses a scale to a
coarser atom level.

```{r subset}
manufacturing <- filter_scale(ind, "section", "C")
scale_units(manufacturing)
scale_coverage(manufacturing)

scale_frames(prune_scale(ind, "division"))
```

## Attributes and aliases

A scale may carry values that belong to its members but are not weights: a
calorific value per fuel, a density, an emission factor. They live in an
attribute table keyed by `(frame, unit)`, at whatever level they are
published, and `scale_attributes()` resolves one value per member of a
frame, walking to the nearest coarser level when a member has none of its
own. `convert_scale()` applies the resolved value row by row. Alternative
labels live in an alias table, and `scale_match()` resolves labels against
member names and aliases exactly, reporting ambiguity rather than guessing.
The package ships one such scale, the energypal fuel taxonomy with the IPCC
2006 defaults:

```{r attributes}
carriers <- read_scale("carriers")
lhv <- scale_attributes(carriers, "lhv", frame = "fuel")
head(lhv[!is.na(lhv$value), c("unit", "value", "unit_of_measure")])

fuel_use <- data.frame(fuel = c("Lignite", "NaturalGas"), t = c(1000, 1000))
convert_scale(fuel_use, carriers, "lhv", from_unit = "t", to_unit = "GJ")

scale_match(carriers, c("GAS", "lignite", "Hydrogen"))
```

`vignette("attributes")` has the full account.

## Definition files

A scale is stored as a definition: a JSON header with its tables inline, or
a bundle directory with Arrow or CSV tables, which the Python binding reads
as well. `write_scale()` and `read_scale()` exchange them, and
`scale_catalog()` lists the definitions installed packages ship:

```{r definition}
path <- file.path(tempdir(), "nace.json")
write_scale(ind, path)
scale_frames(read_scale(path))
scale_catalog()[c("id", "name", "frames", "n_atoms", "package")]
```

```{r definition-cleanup, include = FALSE}
unlink(path)
```

## Several dimensions

`scale_product()` combines scales into one multi-dimensional index without
materialising the product of their atoms. `recast_product()`,
`join_product()` and `filter_product()` apply the single-scale operations
axis by axis.

```{r product}
p <- scale_product(a = scale_example(), b = scale_example2())
product_size(p)
```

## Further operations

* `cluster_scale()` groups units by their data and returns the scale with
  the clusters as a new frame.
* `reconcile_scale()` compares aggregated data with an independent total.
* `as_scale_array()` converts long data to a dense array over a scale, and
  `write_scale_dataset()` stores data as an Arrow dataset together with its
  scales.

All operations accept a `data.frame`, tibble, `data.table`, dtplyr table or
arrow dataset and return the same class; lazy inputs return a query unless
`collect = TRUE`.
