Superlative aggregation

All of the examples so far have used a single set of weights to aggregate an index. Although this is by far the most common case, it is not suitable for aggregating a superlative index where there is more than one set of aggregation weights that change every period.

Aggregating a Paasche index

Let’s start by making a chained Paasche index from 6 elemental indexes over 4 time periods to see how to deal with time-varying aggregation weights. This is similar to the example of building an index across multiple basket in vignette("multiple-baskets"), except that the weights change each period.

library(piar)

set.seed(12345)

# Make 6 elemental indexes over 4 time periods.
elementals <- matrix(c(rep(1, 6), runif(6 * 3, 0.8, 1.2)), nrow = 6) |>
  as_index() |>
  set_levels(paste0("B", 1:6))

head(elementals)
## Period-over-period price index for 6 levels over 4 time periods 
##    1         2         3         4
## B1 1 1.0883616 0.9300382 1.0942740
## B2 1 1.1503093 1.0036897 0.8004546
## B3 1 1.1043929 1.0910821 0.9564813
## B4 1 1.1544498 1.1958948 0.9849979
## B5 1 0.9825924 0.8138142 0.9552576
## B6 1 0.8665487 0.8609494 0.9609941
# Make aggregation weights over 4 time periods.
#            1
#      |-----+-----|
#      11          12
#  |---+---|   |---+---|
#  B1  B2  B3  B4  B5  B6

weights <- data.frame(
  level1 = 1,
  level2 = rep(11:12, each = 3),
  ea = levels(elementals),
  weights = runif(4 * 6, 100, 200),
  period = rep(1:4, each = 6)
)

head(weights)
##   level1 level2 ea  weights period
## 1      1     11 B1 117.8964      1
## 2      1     11 B2 195.1659      1
## 3      1     11 B3 145.3728      1
## 4      1     12 B4 132.6752      1
## 5      1     12 B5 196.5415      1
## 6      1     12 B6 170.7482      1

The key tools to deal with time-varying aggregation weights are the stack() and unstack() functions. stack() appends a later index series onto an earlier one for the same levels, whereas unstack() pulls apart an index series for many periods into a collection of one-period indexes. These function allow the aggregation to be done with a map-reduce.

The first step to making a Paasche index is to unstack the elemental indexes into a list of elemental indexes for each period. (Trying to make the elemental indexes period-by-period can be dangerous when there are missing values.)

elementals <- unstack(elementals)

This is followed by making a sequence of aggregation structures for each set of weights.

paasche_pias <- split(
  weights[c("level1", "level2", "ea", "weights")],
  weights[["period"]]
) |>
  lapply(as_aggregation_structure)

Computing the Paasche index for each period is now just a case of mapping the aggregate() function to each elemental index and aggregation structure, and then reducing the result with the stack() function.

paasche <- Map(
  aggregate,
  elementals,
  paasche_pias,
  na.rm = TRUE,
  include_ea = FALSE,
  r = -1
) |>
  Reduce(stack, x = _)

paasche
## Period-over-period price index for 3 levels over 4 time periods 
##    1         2         3         4
## 1  1 1.0504784 0.9666398 0.9468934
## 11 1 1.1117341 0.9920294 0.9329698
## 12 1 0.9898158 0.9447976 0.9647535

Making a Fisher index

Making a chained Fisher index requires two sets of weights: base-period weights to make the Laspeyres index and current-period weights to make the Paasche index. As with the Paasche index this can be done with a map-reduce, except now by passing two aggregation structures to the aggregate() function instead of one.

laspeyres_pias <- paasche_pias[c(1, 1, 2, 3)]

fisher <- Map(
  aggregate,
  elementals,
  pias = laspeyres_pias,
  pias2 = paasche_pias,
  na.rm = TRUE,
  include_ea = FALSE
) |>
  Reduce(stack, x = _)

fisher
## Period-over-period price index for 3 levels over 4 time periods 
##    1         2         3         4
## 1  1 1.0509134 0.9783795 0.9600792
## 11 1 1.1157686 1.0007074 0.9557479
## 12 1 0.9891911 0.9567760 0.9665552

This gives the same result as calculating the Laspeyres and Paasche indexes individually, and then caluclating the Fisher index manually.

laspeyres <- Map(
  aggregate,
  elementals,
  pias = laspeyres_pias,
  na.rm = TRUE,
  include_ea = FALSE
) |>
  Reduce(stack, x = _)

sqrt(as.matrix(laspeyres) * as.matrix(paasche))
##    1         2         3         4
## 1  1 1.0509134 0.9783795 0.9600792
## 11 1 1.1157686 1.0007074 0.9557479
## 12 1 0.9891911 0.9567760 0.9665552