From 024a9b57dfca7a760938cc92595db983eca173cc Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Mon, 20 Jul 2026 23:24:41 -0400 Subject: [PATCH 1/7] docs: Added documentation for named formulas. Closes #83. --- .vscode/extensions.json | 5 -- .vscode/settings.json | 10 --- NAMESPACE | 1 + R/emean.R | 111 ++++++++++++++++++++++++ R/geks_index.R | 16 ++-- _pkgdown.yml | 1 + inst/tinytest/test-emean.R | 70 +++++++++++++++ inst/tinytest/test-geks_index.R | 18 ++-- man/emean.Rd | 83 ++++++++++++++++++ man/geks_index.Rd | 6 +- man/gmean.Rd | 1 + man/nested_gmean.Rd | 1 + man/scale_weights.Rd | 1 + man/transmute_weights.Rd | 1 + man/transmute_weights2.Rd | 1 + man/update_weights.Rd | 1 + vignettes/index-number-formulas.qmd | 130 +++++++++++++++++++++++++++- 17 files changed, 421 insertions(+), 36 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json create mode 100644 R/emean.R create mode 100644 inst/tinytest/test-emean.R create mode 100644 man/emean.Rd diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 344f76eb..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "Posit.air-vscode" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a9f69fe4..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "[r]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "Posit.air-vscode" - }, - "[quarto]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "quarto.quarto" - } -} diff --git a/NAMESPACE b/NAMESPACE index cbe0b1a2..4057f9a6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -97,6 +97,7 @@ export(contrib) export(contrib2DF) export(elemental_index) export(elementary_index) +export(emean) export(expand_classification) export(geks_index) export(gmean) diff --git a/R/emean.R b/R/emean.R new file mode 100644 index 00000000..8ba2e164 --- /dev/null +++ b/R/emean.R @@ -0,0 +1,111 @@ +#' Extended mean +#' +#' Calculate the component-wise extended mean. +#' +#' Both `x` and `y` should be strictly positive. This is not enforced, but the +#' results may not make sense when the extended mean is not defined. The usual +#' recycling rules apply when `x` and `y` are not the same length. +#' +#' By definition, the extended mean of `x` +#' and `y` is `x` when `x == y`. The `tol` argument is used +#' to test equality by checking if `abs(x - y) <= tol`. In some cases it's +#' useful to multiply +#' `tol` by a scale factor, such as `max(abs(x), abs(y))`. This often +#' doesn't matter when making price indexes, however, as `x` and `y` +#' are usually around 1. +#' +#' @family math functions +#' @export +#' +#' @param x,y `[numeric > 0]` A strictly positive numeric vector. +#' @param order `[numeric(2)]` A pair of finite numbers giving the order of the +#' extended mean. +#' The default calculates the ordinary logarithmic mean. Setting either the +#' first or second element to 1 gives the generalized logarithmic mean. +#' @param tol `[numeric > 0]` The tolerance used to determine if `x == y`. +#' The default value is the same as [all.equal()]. +#' +#' @returns +#' A numeric vector, the same length as +#' `max(length(x), length(y))`, giving the component-wise extended mean +#' of `x` and `y`. +#' +#' @seealso +#' [transmute_weights()] uses the extended mean to turn a generalized +#' mean of a given order into a generalized mean of any other order. +#' +#' @references +#' Bullen, P. S. (2003). *Handbook of Means and Their Inequalities*. +#' Springer Science+Business Media. +#' +#' @examples +#' x <- 8:5 +#' y <- 1:4 +#' +#' # The arithmetic and geometric means are special cases of the +#' # generalized logarithmic mean. +#' all.equal(emean(x, y, c(2, 1)), (x + y) / 2) +#' all.equal(emean(x, y, c(-1, 1)), sqrt(x * y)) +#' +#' # The harmonic mean cannot be expressed as a logarithmic mean, but can +#' # be expressed as an extended mean. +#' all.equal(emean(x, y, c(-2, -1)), 2 / (1 / x + 1 / y)) +#' +#' # The quadratic mean is also a type of extended mean. +#' all.equal(emean(x, y, c(2, 4)), sqrt(x^2 / 2 + y^2 / 2)) +#' +#' # As are heronian and centroidal means. +#' all.equal( +#' emean(x, y, c(0.5, 1.5)), +#' (x + sqrt(x * y) + y) / 3 +#' ) +#' all.equal( +#' emean(x, y, c(2, 3)), +#' 2 / 3 * (x^2 + x * y + y^2) / (x + y) +#' ) +emean <- function( + x, + y, + order = c(0, 1), + tol = .Machine$double.eps^0.5 +) { + if (not_finite_pair(order)) { + stop("`order` must be a pair of finite numbers") + } + r <- order[[1]] + s <- order[[2]] + + # Recycling x and y here avoids multiple warnings if one is not a multiple + # length of the other. + if (length(x) > length(y)) { + if (length(x) %% length(y) != 0) { + warning("length of `x` is not a multiple of length of `y") + y <- rep_len(length(x)) + } + } else if (length(x) < length(y)) { + if (length(y) %% length(x) != 0) { + warning("length of `y` is not a multiple of length of `x") + x <- rep_len(length(y)) + } + } + + if (length(tol) > max(length(x), length(y))) { + stop("'tol' cannot be longer than 'x' or 'y'") + } + + if (r == 0 && s == 0) { + res <- sqrt(x * y) + } else if (r == 0) { + res <- ((x^s - y^s) / log(x / y) / s)^(1 / s) + } else if (s == 0) { + res <- ((x^r - y^r) / log(x / y) / r)^(1 / r) + } else if (r == s) { + res <- exp((x^r * log(x) - y^r * log(y)) / (x^r - y^r) - 1 / r) + } else { + res <- ((x^s - y^s) / (x^r - y^r) * (r / s))^(1 / (s - r)) + } + # Set output to a when a == b. + i <- which(abs(x - y) <= tol) + res[i] <- x[(i - 1L) %% length(x) + 1L] + res +} diff --git a/R/geks_index.R b/R/geks_index.R index be91e2b6..13fc447b 100644 --- a/R/geks_index.R +++ b/R/geks_index.R @@ -5,7 +5,7 @@ #' #' @export #' -#' @param index_number `[function]` A function giving the index-number formula +#' @param index_formula `[function]` A function giving the index-number formula #' in the GEKS #' index. Usually a Törnqvist, Fisher (the default), or Walsh index. #' It must have arguments `p1`, `p0`, `q1`, and `q0`, and satisfy the @@ -93,14 +93,14 @@ #' quantity, #' period, #' product, -#' index_number = \(p1, p0, ...) gmean(p1 / p0, na.rm = TRUE, order = 0) +#' index_formula = \(p1, p0, ...) gmean(p1 / p0, na.rm = TRUE, order = 0) #' ) geks_index <- function( price, quantity, period, product, - index_number = \(p1, p0, q1, q0) { + index_formula = \(p1, p0, q1, q0) { nested_gmean(p1 / p0, list(p0 * q0, p1 * q1), na.rm = TRUE) }, window = nlevels(period), @@ -144,7 +144,7 @@ geks_index <- function( } mat <- geks_matrix( - index_number, + index_formula, price, quantity, period, @@ -172,7 +172,7 @@ geks_index <- function( #' Make the GEKS matrix #' @noRd geks_matrix <- function( - index_number, + index_formula, price, quantity, period, @@ -204,18 +204,18 @@ geks_matrix <- function( # to minimize the number of back prices to find. js <- seq.int(to = i - 1L, length.out = min(window, i) - 1L) if (method == "all") { - ans <- Map(index_number, p1 = p[js], p0 = p[i], q1 = q[js], q0 = q[i]) + ans <- Map(index_formula, p1 = p[js], p0 = p[i], q1 = q[js], q0 = q[i]) } else { m <- Map(match, product[js], product[i]) bp <- Map(`[`, p[i], m) bq <- Map(`[`, q[i], m) - ans <- Map(index_number, p1 = p[js], p0 = bp, q1 = q[js], q0 = bq) + ans <- Map(index_formula, p1 = p[js], p0 = bp, q1 = q[js], q0 = bq) } } # Add the diagonal at the end and pad with NAs. ans <- c( unlist(ans, use.names = FALSE), - index_number(p[[i]], p[[i]], q[[i]], q[[i]]) + index_formula(p[[i]], p[[i]], q[[i]], q[[i]]) ) front_pad <- rep_len(NA_real_, max(i - window, 0L)) back_pad <- rep_len(NA_real_, length(lt) - length(ans) - length(front_pad)) diff --git a/_pkgdown.yml b/_pkgdown.yml index 126d3b78..71a310bc 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -105,3 +105,4 @@ reference: - transmute_weights2 - scale_weights - update_weights + - emean diff --git a/inst/tinytest/test-emean.R b/inst/tinytest/test-emean.R new file mode 100644 index 00000000..4d1511fb --- /dev/null +++ b/inst/tinytest/test-emean.R @@ -0,0 +1,70 @@ +# Extended mean satisfied key identities. +a <- 1:5 +b <- c(1, 5, 4, 1, 2) + +# Symmetry. +expect_equal(emean(a, b), emean(b, a)) +expect_equal( + emean(a, b, order = c(-0.1, 2.5)), + emean(b, a, order = c(2.5, -0.1)) +) +expect_equal( + emean(a, b, order = c(0, 2)), + emean(b, a, order = c(2, 0)) +) +expect_equal( + emean(a, b, order = c(0, 0)), + emean(b, a, order = c(0, 0)) +) +expect_equal( + emean(a, b, order = c(1, 1)), + emean(b, a, order = c(1, 1)) +) +# Identities. +expect_equal( + emean(a, b, order = c(-1, 1)), + apply(matrix(c(a, b), ncol = 2), 1, \(x) gmean(x, order = 0)) +) +expect_equal( + emean(a, b, order = c(2, 1)), + apply(matrix(c(a, b), ncol = 2), 1, mean) +) +expect_equal( + emean(a, b, order = c(-2, 1)), + apply( + matrix(c(a, b), ncol = 2), + 1, + \(x) { + (gmean(x, order = -1) * gmean(x, order = 0)^2)^(1 / + 3) + } + ) +) +expect_equal( + emean(a, b, order = c(0.5, 1)), + apply( + matrix(c(a, b), ncol = 2), + 1, + \(x) nested_gmean(x, order = c(1, 0), outer_order = 1) + ) +) +expect_equal( + emean(a, b), + apply(matrix(c(a, b), ncol = 2), 1, \(x) gmean(x, order = 0))^2 * + emean(1 / a, 1 / b) +) +expect_equal( + emean(a, b, order = c(-2, -1)), + apply(matrix(c(a, b), ncol = 2), 1, \(x) gmean(x, order = -1)) +) +expect_equal( + emean(a, b, order = c(-2, 2)), + apply(matrix(c(a, b), ncol = 2), 1, \(x) gmean(x, order = 0)) +) +expect_equal( + emean(a, b, order = c(2, 2)), + c(1, ((a^a^2 / b^b^2)^(1 / (a^2 - b^2)) / exp(1)^(1 / 2))[-1]) +) + +# Errors when expected. +expect_error(emean(a, b, tol = 1:10)) diff --git a/inst/tinytest/test-geks_index.R b/inst/tinytest/test-geks_index.R index d1b2cb0e..a20034dc 100644 --- a/inst/tinytest/test-geks_index.R +++ b/inst/tinytest/test-geks_index.R @@ -82,7 +82,7 @@ local({ quantity, period, product, - index_number = tornqvist_index + index_formula = tornqvist_index ) )) ) @@ -112,7 +112,7 @@ local({ quantity, period, product, - index_number = jevons_index + index_formula = jevons_index ) )) ) @@ -225,7 +225,7 @@ local({ period, product, match_method = "back-price", - index_number = walsh_index + index_formula = walsh_index ) ) ) @@ -256,7 +256,7 @@ local({ 10, 3, match_method = "back-price", - index_number = walsh_index + index_formula = walsh_index ) ) expect_equal( @@ -287,7 +287,7 @@ local({ quantity, period, product, - index_number = tornqvist_index + index_formula = tornqvist_index ) ) ) @@ -318,7 +318,7 @@ local({ quantity, period, product, - index_number = jevons_index + index_formula = jevons_index ) )) ) @@ -366,7 +366,7 @@ local({ period, product, 2, - index_number = walsh_index + index_formula = walsh_index ) ) ) @@ -401,7 +401,7 @@ local({ period, product, window = 7, - index_number = tornqvist_index + index_formula = tornqvist_index ) ) ), @@ -456,7 +456,7 @@ local({ period, product, window = 6, - index_number = jevons_index + index_formula = jevons_index ) ), periods = 3 diff --git a/man/emean.Rd b/man/emean.Rd new file mode 100644 index 00000000..161dce48 --- /dev/null +++ b/man/emean.Rd @@ -0,0 +1,83 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/emean.R +\name{emean} +\alias{emean} +\title{Extended mean} +\usage{ +emean(x, y, order = c(0, 1), tol = .Machine$double.eps^0.5) +} +\arguments{ +\item{x, y}{\verb{[numeric > 0]} A strictly positive numeric vector.} + +\item{order}{\verb{[numeric(2)]} A pair of finite numbers giving the order of the +extended mean. +The default calculates the ordinary logarithmic mean. Setting either the +first or second element to 1 gives the generalized logarithmic mean.} + +\item{tol}{\verb{[numeric > 0]} The tolerance used to determine if \code{x == y}. +The default value is the same as \code{\link[=all.equal]{all.equal()}}.} +} +\value{ +A numeric vector, the same length as +\code{max(length(x), length(y))}, giving the component-wise extended mean +of \code{x} and \code{y}. +} +\description{ +Calculate the component-wise extended mean. +} +\details{ +Both \code{x} and \code{y} should be strictly positive. This is not enforced, but the +results may not make sense when the extended mean is not defined. The usual +recycling rules apply when \code{x} and \code{y} are not the same length. + +By definition, the extended mean of \code{x} +and \code{y} is \code{x} when \code{x == y}. The \code{tol} argument is used +to test equality by checking if \code{abs(x - y) <= tol}. In some cases it's +useful to multiply +\code{tol} by a scale factor, such as \code{max(abs(x), abs(y))}. This often +doesn't matter when making price indexes, however, as \code{x} and \code{y} +are usually around 1. +} +\examples{ +x <- 8:5 +y <- 1:4 + +# The arithmetic and geometric means are special cases of the +# generalized logarithmic mean. +all.equal(emean(x, y, c(2, 1)), (x + y) / 2) +all.equal(emean(x, y, c(-1, 1)), sqrt(x * y)) + +# The harmonic mean cannot be expressed as a logarithmic mean, but can +# be expressed as an extended mean. +all.equal(emean(x, y, c(-2, -1)), 2 / (1 / x + 1 / y)) + +# The quadratic mean is also a type of extended mean. +all.equal(emean(x, y, c(2, 4)), sqrt(x^2 / 2 + y^2 / 2)) + +# As are heronian and centroidal means. +all.equal( + emean(x, y, c(0.5, 1.5)), + (x + sqrt(x * y) + y) / 3 +) +all.equal( + emean(x, y, c(2, 3)), + 2 / 3 * (x^2 + x * y + y^2) / (x + y) +) +} +\references{ +Bullen, P. S. (2003). \emph{Handbook of Means and Their Inequalities}. +Springer Science+Business Media. +} +\seealso{ +\code{\link[=transmute_weights]{transmute_weights()}} uses the extended mean to turn a generalized +mean of a given order into a generalized mean of any other order. + +Other math functions: +\code{\link[=gmean]{gmean()}}, +\code{\link[=nested_gmean]{nested_gmean()}}, +\code{\link[=scale_weights]{scale_weights()}}, +\code{\link[=transmute_weights]{transmute_weights()}}, +\code{\link[=transmute_weights2]{transmute_weights2()}}, +\code{\link[=update_weights]{update_weights()}} +} +\concept{math functions} diff --git a/man/geks_index.Rd b/man/geks_index.Rd index 186c9058..31e4ea8a 100644 --- a/man/geks_index.Rd +++ b/man/geks_index.Rd @@ -9,7 +9,7 @@ geks_index( quantity, period, product, - index_number = function(p1, p0, q1, q0) { + index_formula = function(p1, p0, q1, q0) { nested_gmean(p1/p0, list(p0 * q0, p1 * q1), na.rm = TRUE) }, @@ -36,7 +36,7 @@ one, that gives the corresponding product identifier for each element in \code{price} and \code{quantity}.} -\item{index_number}{\verb{[function]} A function giving the index-number formula +\item{index_formula}{\verb{[function]} A function giving the index-number formula in the GEKS index. Usually a Törnqvist, Fisher (the default), or Walsh index. It must have arguments \code{p1}, \code{p0}, \code{q1}, and \code{q0}, and satisfy the @@ -102,7 +102,7 @@ geks_index( quantity, period, product, - index_number = \(p1, p0, ...) gmean(p1 / p0, na.rm = TRUE, order = 0) + index_formula = \(p1, p0, ...) gmean(p1 / p0, na.rm = TRUE, order = 0) ) } \references{ diff --git a/man/gmean.Rd b/man/gmean.Rd index 3aeddc22..6082f1c3 100644 --- a/man/gmean.Rd +++ b/man/gmean.Rd @@ -77,6 +77,7 @@ edition). Cambridge University Press. } \seealso{ Other math functions: +\code{\link[=emean]{emean()}}, \code{\link[=nested_gmean]{nested_gmean()}}, \code{\link[=scale_weights]{scale_weights()}}, \code{\link[=transmute_weights]{transmute_weights()}}, diff --git a/man/nested_gmean.Rd b/man/nested_gmean.Rd index 73f45272..1c713d16 100644 --- a/man/nested_gmean.Rd +++ b/man/nested_gmean.Rd @@ -57,6 +57,7 @@ nested_gmean(x, list(w1, w2)) } \seealso{ Other math functions: +\code{\link[=emean]{emean()}}, \code{\link[=gmean]{gmean()}}, \code{\link[=scale_weights]{scale_weights()}}, \code{\link[=transmute_weights]{transmute_weights()}}, diff --git a/man/scale_weights.Rd b/man/scale_weights.Rd index 8ac55fb8..8a89e33c 100644 --- a/man/scale_weights.Rd +++ b/man/scale_weights.Rd @@ -24,6 +24,7 @@ scale_weights(c(1:5, NA)) } \seealso{ Other math functions: +\code{\link[=emean]{emean()}}, \code{\link[=gmean]{gmean()}}, \code{\link[=nested_gmean]{nested_gmean()}}, \code{\link[=transmute_weights]{transmute_weights()}}, diff --git a/man/transmute_weights.Rd b/man/transmute_weights.Rd index 5ff1ab8f..1641fbd8 100644 --- a/man/transmute_weights.Rd +++ b/man/transmute_weights.Rd @@ -56,6 +56,7 @@ Cambridge University Press. } \seealso{ Other math functions: +\code{\link[=emean]{emean()}}, \code{\link[=gmean]{gmean()}}, \code{\link[=nested_gmean]{nested_gmean()}}, \code{\link[=scale_weights]{scale_weights()}}, diff --git a/man/transmute_weights2.Rd b/man/transmute_weights2.Rd index 705a2ebe..62fe5f14 100644 --- a/man/transmute_weights2.Rd +++ b/man/transmute_weights2.Rd @@ -83,6 +83,7 @@ Cambridge University Press. } \seealso{ Other math functions: +\code{\link[=emean]{emean()}}, \code{\link[=gmean]{gmean()}}, \code{\link[=nested_gmean]{nested_gmean()}}, \code{\link[=scale_weights]{scale_weights()}}, diff --git a/man/update_weights.Rd b/man/update_weights.Rd index d5622d36..dfb01452 100644 --- a/man/update_weights.Rd +++ b/man/update_weights.Rd @@ -56,6 +56,7 @@ International Monetary Fund. } \seealso{ Other math functions: +\code{\link[=emean]{emean()}}, \code{\link[=gmean]{gmean()}}, \code{\link[=nested_gmean]{nested_gmean()}}, \code{\link[=scale_weights]{scale_weights()}}, diff --git a/vignettes/index-number-formulas.qmd b/vignettes/index-number-formulas.qmd index 69167598..07a7841b 100644 --- a/vignettes/index-number-formulas.qmd +++ b/vignettes/index-number-formulas.qmd @@ -13,7 +13,8 @@ knitr: Price indexes based on a generalized mean of price relatives constitute a large family of bilateral index-number formulas that are consistent in aggregation, and any index based on the generalized mean can be used to -make and aggregate elementary indexes. To see how to make superlative +make and aggregate elementary indexes. See @sec-appendix for a comprehensive list. +To see how to make superlative indexes as well, let's start with a simple dataset of prices and quantities for two businesses over three periods. We'll be making extensive use of the lower-level math functions in this package @@ -191,3 +192,130 @@ contrib(fisher_index, "B1") Aggregating the elementary indexes will then consistently aggregate the contributions for both businesses, even though they use different methods. + +## Indexed based on generalized means {#sec-appendix} + +```{r} +#| eval: false +# ---- Arithmetic mean ---- +# Carli +carli <- \(p1, p0, q1, q0) gmean(p1 / p0) + +# Dutot +dutot <- \(p1, p0, q1, q0) gmean(p1 / p0, p0) + +# Laspeyres +laspeyres <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 * q0) + +# Palgrave +palgrave <- \(p1, p0, q1, q0) gmean(p1 / p0, p1 * q1) + +# Walsh-1 +walsh1 <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 * sqrt(q0 * q1)) + +# Marshall-Edgeworth +marshall_edgeworth <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 * (q0 * q1)) + +# Geay-Khamis +geary_khamis <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 / (1 / q0 + 1 / q1)) + +# Hybrid CSWD +hybrid_cswd <- \(p1, p0, q1, q0) gmean(p1 / p0, sqrt(p0 / p1)) + +# Martini +martini <- \(p1, p0, q1, q0, a) gmean(p1 / p0, p0 * q0 * (q1 / p0)^a) + +# ---- Geometric mean ---- +# Jevons +jevons <- \(p1, p0, q1, q0) gmean(p1 / p0, order = 0) + +# Geometric Laspeyres / Jöhr +geo_laspeyres <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 * q0, order = 0) + +# Geometric Paasche +geo_paasche <- \(p1, p0, q1, q0) gmean(p1 / p0, p1 * q1, order = 0) + +# Walsh-2 +walsh2 <- \(p1, p0, q1, q0) gmean(p1 / p0, sqrt(p0 * p1 * q0 * q1), order = 0) + +# Theil +theil <- \(p1, p0, q1, q0) { + w0 <- scale_weights(p0 * q0) + w1 <- scale_weights(p1 * q1) + gmean(p1 / p0, ((w0 + w1) / 2 * w0 * w1)^(1 / 3), order = 0) +} + +# Rao +rao <- \(p1, p0, q1, q0) { + w0 <- scale_weights(p0 * q0) + w1 <- scale_weights(p1 * q1) + gmean(p1 / p0, w0 * w1 / (w0 + w1), order = 0) +} + +# Sato-Vartia +sato_vartia <- function(p1, p0, q1, q0) { + v0 <- scale_weights(p0 * q0) + v1 <- scale_weights(p1 * q1) + gmean(p1 / p0, emean(v0, v1), order = 0) +} + +# ---- Harmonic mean ---- +# Coggeshall +coggeshall <- \(p1, p0, q1, q0) gmean(p1 / p0, order = -1) + +# Paasche +paasche <- \(p1, p0, q1, q0) gmean(p1 / p0, p1 * q1, order = -1) + +# Harmonic Laspeyres +harm_laspeyres <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 * q0, order = -1) + +# ---- Generalized mean ---- +# Lloyd-Moulton +lloyd_moulton <- \(p1, p0, q1, q0, sigma) { + gmean(p1 / p0, p0 * q0, order = 1 - sigma) +} + +# ---- Nested means ---- +# Drobisch/Sidgwick +drobisch <- \(p1, p0, q1, q0) { + nested_gmean(p1 / p0, list(p0 * q0, p1 * q1), outer_order = 1) +} + +# Unnamed +unnamed <- \(p1, p0, q1, q0) { + nested_gmean( + p1 / p0, + list(p0 * q0, p1 * q1), + order = c(1, 1), + outer_order = 1 + ) +} + +# Törnqvist-Theil +tornqvist <- \(p1, p0, q1, q0) { + nested_gmean( + p1 / p0, + list(p0 * q0, p1 * q1), + order = c(0, 0) + ) +} + +# Fisher +fisher <- \(p1, p0, q1, q0) nested_gmean(p1 / p0, list(p0 * q0, p1 * q1)) + +# CSWD +cswd <- \(p1, p0, q1, q0) nested_gmean(p1 / p0) + +# Balk-Walsh +balk_walsh <- \(p1, p0, q1, q0) nested_gmean(p1 / p0, order = c(0.5, -0.5)) + +# Geometric AG mean +ag_mean <- \(p1, p0, q1, q0, elasticity) { + nested_gmean( + p1 / p0, + list(p0 * q0, p0 * q0), + order = c(0, 1), + outer_weights = c(elasticity, 1 - elasticity) + ) +} +``` From 39ddb27102fe35a6cb85ca2548e2e7f48dcf638d Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Mon, 20 Jul 2026 23:28:39 -0400 Subject: [PATCH 2/7] docs: Fixed see also --- R/emean.R | 4 ---- man/emean.Rd | 3 --- 2 files changed, 7 deletions(-) diff --git a/R/emean.R b/R/emean.R index 8ba2e164..21f66bd2 100644 --- a/R/emean.R +++ b/R/emean.R @@ -30,10 +30,6 @@ #' `max(length(x), length(y))`, giving the component-wise extended mean #' of `x` and `y`. #' -#' @seealso -#' [transmute_weights()] uses the extended mean to turn a generalized -#' mean of a given order into a generalized mean of any other order. -#' #' @references #' Bullen, P. S. (2003). *Handbook of Means and Their Inequalities*. #' Springer Science+Business Media. diff --git a/man/emean.Rd b/man/emean.Rd index 161dce48..9b21dc74 100644 --- a/man/emean.Rd +++ b/man/emean.Rd @@ -69,9 +69,6 @@ Bullen, P. S. (2003). \emph{Handbook of Means and Their Inequalities}. Springer Science+Business Media. } \seealso{ -\code{\link[=transmute_weights]{transmute_weights()}} uses the extended mean to turn a generalized -mean of a given order into a generalized mean of any other order. - Other math functions: \code{\link[=gmean]{gmean()}}, \code{\link[=nested_gmean]{nested_gmean()}}, From 59c70c1b3073439b55c396bd1cf6e358985bf208 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Mon, 20 Jul 2026 23:37:05 -0400 Subject: [PATCH 3/7] Links with geks --- R/geks_index.R | 2 +- man/geks_index.Rd | 2 +- vignettes/index-number-formulas.qmd | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/geks_index.R b/R/geks_index.R index 13fc447b..56208667 100644 --- a/R/geks_index.R +++ b/R/geks_index.R @@ -9,7 +9,7 @@ #' in the GEKS #' index. Usually a Törnqvist, Fisher (the default), or Walsh index. #' It must have arguments `p1`, `p0`, `q1`, and `q0`, and satisfy the -#' time-reversal test. +#' time-reversal test. See `vignette("index-number-formulas")` for details. #' @param order `[numeric(1)]` A finite number giving the order of the #' generalized mean used to #' average price indexes over the rolling window. The default uses a diff --git a/man/geks_index.Rd b/man/geks_index.Rd index 31e4ea8a..c71bb3a8 100644 --- a/man/geks_index.Rd +++ b/man/geks_index.Rd @@ -40,7 +40,7 @@ gives the corresponding product identifier for each element in \code{price} and in the GEKS index. Usually a Törnqvist, Fisher (the default), or Walsh index. It must have arguments \code{p1}, \code{p0}, \code{q1}, and \code{q0}, and satisfy the -time-reversal test.} +time-reversal test. See \code{vignette("index-number-formulas")} for details.} \item{window}{\verb{[integer(1) > 0]} A positive integer giving the length of the rolling window. diff --git a/vignettes/index-number-formulas.qmd b/vignettes/index-number-formulas.qmd index 07a7841b..ac516aa8 100644 --- a/vignettes/index-number-formulas.qmd +++ b/vignettes/index-number-formulas.qmd @@ -193,7 +193,7 @@ contrib(fisher_index, "B1") Aggregating the elementary indexes will then consistently aggregate the contributions for both businesses, even though they use different methods. -## Indexed based on generalized means {#sec-appendix} +## Indexes based on generalized means {#sec-appendix} ```{r} #| eval: false From c72085744989dc5cc6c18eec519a5b8c14e6e1e2 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Mon, 20 Jul 2026 23:46:25 -0400 Subject: [PATCH 4/7] Fixed lint --- inst/tinytest/test-emean.R | 3 +-- vignettes/index-number-formulas.qmd | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/inst/tinytest/test-emean.R b/inst/tinytest/test-emean.R index 4d1511fb..afd8ff1f 100644 --- a/inst/tinytest/test-emean.R +++ b/inst/tinytest/test-emean.R @@ -35,8 +35,7 @@ expect_equal( matrix(c(a, b), ncol = 2), 1, \(x) { - (gmean(x, order = -1) * gmean(x, order = 0)^2)^(1 / - 3) + (gmean(x, order = -1) * gmean(x, order = 0)^2)^(1 / 3) } ) ) diff --git a/vignettes/index-number-formulas.qmd b/vignettes/index-number-formulas.qmd index ac516aa8..955ae154 100644 --- a/vignettes/index-number-formulas.qmd +++ b/vignettes/index-number-formulas.qmd @@ -229,7 +229,7 @@ martini <- \(p1, p0, q1, q0, a) gmean(p1 / p0, p0 * q0 * (q1 / p0)^a) # Jevons jevons <- \(p1, p0, q1, q0) gmean(p1 / p0, order = 0) -# Geometric Laspeyres / Jöhr +# Geometric Laspeyres (Jöhr) geo_laspeyres <- \(p1, p0, q1, q0) gmean(p1 / p0, p0 * q0, order = 0) # Geometric Paasche @@ -276,7 +276,7 @@ lloyd_moulton <- \(p1, p0, q1, q0, sigma) { } # ---- Nested means ---- -# Drobisch/Sidgwick +# Drobisch (Sidgwick) drobisch <- \(p1, p0, q1, q0) { nested_gmean(p1 / p0, list(p0 * q0, p1 * q1), outer_order = 1) } From 5b0ee6100af5477c22299c17c4f905a56b83d6a6 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Tue, 21 Jul 2026 22:47:11 -0400 Subject: [PATCH 5/7] Fixed recycling bug in emean() --- R/emean.R | 20 ++++++++++++-------- R/gmean.R | 4 ++-- inst/tinytest/test-emean.R | 31 +++++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/R/emean.R b/R/emean.R index 21f66bd2..49233562 100644 --- a/R/emean.R +++ b/R/emean.R @@ -2,6 +2,9 @@ #' #' Calculate the component-wise extended mean. #' +#' The extended mean is also called the difference mean, Stolarsky mean, or +#' extended mean-value mean; see Bullen (2003, p. 393) for details. +#' #' Both `x` and `y` should be strictly positive. This is not enforced, but the #' results may not make sense when the extended mean is not defined. The usual #' recycling rules apply when `x` and `y` are not the same length. @@ -74,19 +77,20 @@ emean <- function( # Recycling x and y here avoids multiple warnings if one is not a multiple # length of the other. if (length(x) > length(y)) { - if (length(x) %% length(y) != 0) { - warning("length of `x` is not a multiple of length of `y") - y <- rep_len(length(x)) + if (length(y) > 0 && length(x) %% length(y) != 0) { + warning("length of `y` is not a multiple of length of `x`") + y <- rep_len(y, length(x)) } } else if (length(x) < length(y)) { - if (length(y) %% length(x) != 0) { - warning("length of `y` is not a multiple of length of `x") - x <- rep_len(length(y)) + if (length(x) > 0 && length(y) %% length(x) != 0) { + warning("length of `x` is not a multiple of length of `y`") + x <- rep_len(x, length(y)) } } - if (length(tol) > max(length(x), length(y))) { - stop("'tol' cannot be longer than 'x' or 'y'") + max_len <- max(length(x), length(y)) + if (max_len > 0 && length(tol) > max_len) { + stop("`tol` cannot be longer than `x` or `y`") } if (r == 0 && s == 0) { diff --git a/R/gmean.R b/R/gmean.R index 8c9ab329..1a67f28b 100644 --- a/R/gmean.R +++ b/R/gmean.R @@ -31,8 +31,8 @@ #' @param x `[numeric > 0]` A strictly positive numeric vector. #' @param weights `[numeric >= 0]` A positive numeric vector of weights, the #' same length as `x`. The default is to equally weight each element of `x`. -#' @param order `[numeric(1)]` A finite number giving the order of the -#' generalized mean. The default calculates an arithmetic mean. +#' @param order `[numeric(1)]` A finite number giving the order (or exponent) of +#' the generalized mean. The default calculates an arithmetic mean. #' @param na.rm `[logical(1)]` Should missing values be removed? By default, #' missing values are not removed. #' diff --git a/inst/tinytest/test-emean.R b/inst/tinytest/test-emean.R index afd8ff1f..612b0ead 100644 --- a/inst/tinytest/test-emean.R +++ b/inst/tinytest/test-emean.R @@ -1,4 +1,4 @@ -# Extended mean satisfied key identities. +# Extended mean satisfies key identities. a <- 1:5 b <- c(1, 5, 4, 1, 2) @@ -20,6 +20,7 @@ expect_equal( emean(a, b, order = c(1, 1)), emean(b, a, order = c(1, 1)) ) + # Identities. expect_equal( emean(a, b, order = c(-1, 1)), @@ -66,4 +67,30 @@ expect_equal( ) # Errors when expected. -expect_error(emean(a, b, tol = 1:10)) +expect_error(emean(a, b, tol = 1:10), "`tol` cannot be longer than `x` or `y`") +expect_error( + emean(a, b, order = 1:3), + "`order` must be a pair of finite numbers" +) + +# Recycling works +expect_warning( + emean(1:3, 1:5), + "length of `x` is not a multiple of length of `y`" +) +expect_equal( + suppressWarnings(emean(1:3, 1:5)), + emean(c(1:3, 1:2), 1:5) +) +expect_equal(emean(1:5, numeric(0)), numeric(0)) + +expect_warning( + emean(1:5, 1:3), + "length of `y` is not a multiple of length of `x`" +) +expect_equal( + suppressWarnings(emean(1:5, 1:3)), + emean(1:5, c(1:3, 1:2)) +) +expect_equal(emean(numeric(0), 1:5), numeric(0)) +expect_equal(emean(numeric(0), numeric(0)), numeric(0)) From f23b63ccb5e211187d39ed2dc803c6a461bbe5bd Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Wed, 22 Jul 2026 00:19:02 -0400 Subject: [PATCH 6/7] Deprecated 'r' --- NEWS.md | 8 +++- R/aggregate.piar_index.R | 46 ++++++++++++--------- R/elementary_index.R | 13 ++++-- R/impute_prices.R | 25 +++++++----- R/mean.piar_index.R | 28 ++++++++----- R/update.aggregation_structure.R | 12 ++++-- inst/tinytest/test-aggregate.R | 49 +++++++++++++---------- inst/tinytest/test-elemental_index.R | 12 +++--- inst/tinytest/test-mean.R | 4 +- inst/tinytest/test-stack.R | 2 +- inst/tinytest/test-update.R | 2 +- man/aggregate.piar_index.Rd | 10 +++-- man/elementary_index.Rd | 10 +++-- man/emean.Rd | 3 ++ man/gmean.Rd | 4 +- man/impute_prices.Rd | 10 +++-- man/mean.piar_index.Rd | 10 +++-- man/update.piar_aggregation_structure.Rd | 6 +-- man/update_weights.Rd | 4 +- tests/test-making-price-indexes.R | 16 ++++++-- tests/test-making-price-indexes.Rout.save | 16 ++++++-- vignettes/index-number-formulas.qmd | 4 +- vignettes/superlative-aggregation.qmd | 2 +- 23 files changed, 184 insertions(+), 112 deletions(-) diff --git a/NEWS.md b/NEWS.md index c2fb6f8f..0fcd845c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,17 +2,21 @@ ## Significant changes -- Many functions from `{gpindex}` are now part of `{piar}`. This gives a somewhat +- Many functions from `{gpindex}` are now part of this package. This gives a somewhat more consistent experience and allows for improvements that would be difficult with two separate packages. In particular: - A new function `outliers()` to find extreme price relatives. - - Functions for multilateral indexes. + - Functions for multilateral GEKS indexes. - Core mathematical machinery used to aggregate indexes and make product contributions. - A new vignette outlining some theory that unlies how indexes are calculated. + +- The argument `r` in `elementary_index()`, `aggregate()`, `mean()`, `update()`, +and `impute_prices()` is deprecated and will be removed in a future version. +Use `order` instead. ## Improvements diff --git a/R/aggregate.piar_index.R b/R/aggregate.piar_index.R index 09d43d4e..87ca0afa 100644 --- a/R/aggregate.piar_index.R +++ b/R/aggregate.piar_index.R @@ -71,8 +71,8 @@ #' @param na.rm `[logical(1)]` Should missing values be removed? By default, #' missing values are not removed. Setting `na.rm = TRUE` is equivalent to #' overall mean imputation. -#' @param r `[numeric(1)]` Order of the generalized mean to aggregate index -#' values. 0 for a +#' @param order,r `[numeric(1)]` Order of the generalized mean to aggregate +#' index values. 0 for a #' geometric index (the default for making elementary indexes), 1 for an #' arithmetic index (the default for aggregating elementary indexes and #' averaging indexes over subperiods), or -1 for a harmonic index (usually for @@ -149,11 +149,15 @@ aggregate.chainable_piar_index <- function( pias2 = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, include_ea = TRUE, duplicate_contrib = c("sum", "make.unique"), impute_rules = NULL ) { + if ("r" %in% names(sys.call())) { + warning("`r` is deprecated and will be removed; use `order` instead") + } chkDots(...) aggregate_index( x, @@ -161,7 +165,7 @@ aggregate.chainable_piar_index <- function( pias2, na.rm = na.rm, contrib = contrib, - r = r, + order = r, include_ea = include_ea, chainable = TRUE, duplicate_contrib = match.arg(duplicate_contrib), @@ -178,11 +182,15 @@ aggregate.direct_piar_index <- function( pias2 = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, include_ea = TRUE, duplicate_contrib = c("sum", "make.unique"), impute_rules = NULL ) { + if ("r" %in% names(sys.call())) { + warning("`r` is deprecated and will be removed; use `order` instead") + } chkDots(...) aggregate_index( x, @@ -190,7 +198,7 @@ aggregate.direct_piar_index <- function( pias2, na.rm = na.rm, contrib = contrib, - r = r, + order = r, include_ea = include_ea, chainable = FALSE, duplicate_contrib = match.arg(duplicate_contrib), @@ -206,21 +214,21 @@ aggregate_index <- function( pias2, na.rm, contrib, - r, + order, include_ea, chainable, duplicate_contrib, impute_rules ) { pias <- as_aggregation_structure(pias) - r <- as.numeric(r) + order <- as.numeric(order) has_contrib <- !is.null(x$contrib) && contrib res <- .aggregate( x, pias, na.rm, has_contrib, - r, + order, include_ea, chainable, duplicate_contrib, @@ -246,7 +254,7 @@ aggregate_index <- function( pias2, na.rm, has_contrib, - -r, + -order, include_ea, chainable, duplicate_contrib, @@ -259,7 +267,7 @@ aggregate_index <- function( res2$contrib, res$index, res2$index, - r = 0 + order = 0 ) } res$index[] <- (res$index * res2$index)^0.5 @@ -279,7 +287,7 @@ aggregate_index <- function( pias, na.rm, has_contrib, - r, + order, include_ea, chainable, duplicate_contrib, @@ -311,7 +319,7 @@ aggregate_index <- function( nodes <- unname(pias$child[[i - 1L]]) rel[[i]] <- vapply( nodes, - \(z) gmean(rel[[i - 1L]][z], w[[i - 1L]][z], r, na.rm), + \(z) gmean(rel[[i - 1L]][z], w[[i - 1L]][z], order, na.rm), numeric(1L) ) if (has_contrib) { @@ -322,7 +330,7 @@ aggregate_index <- function( con[[i - 1L]][nodes[[j]]], rel[[i - 1L]][nodes[[j]]], w[[i - 1L]][nodes[[j]]], - r, + order, rel[[i]][j], duplicate_contrib ) @@ -343,7 +351,7 @@ aggregate_index <- function( # Price update weights for all periods after the first. if (chainable) { - pias$weights <- update_weights(rel[[1L]], w[[1L]], r) + pias$weights <- update_weights(rel[[1L]], w[[1L]], order) } if (!include_ea && length(rel) > 1L) { @@ -359,8 +367,8 @@ aggregate_index <- function( #' Aggregate product contributions #' @noRd -aggregate_contrib <- function(x, rel, w, r, index, duplicate_contrib) { - w <- transmute_weights(rel, w, r, to = 1, mean = index) +aggregate_contrib <- function(x, rel, w, order, index, duplicate_contrib) { + w <- transmute_weights(rel, w, order, to = 1, mean = index) res <- Map(`*`, x, w) if (all(lengths(res) == 0L)) { return(numeric(0L)) @@ -385,7 +393,7 @@ aggregate_contrib <- function(x, rel, w, r, index, duplicate_contrib) { #' Aggregate product contributions for a superlative index #' @noRd -super_aggregate_contrib <- function(x, y, rel1, rel2, r) { - w <- transmute_weights(c(rel1, rel2), order = r, to = 1) +super_aggregate_contrib <- function(x, y, rel1, rel2, order) { + w <- transmute_weights(c(rel1, rel2), order = order, to = 1) w[1L] * x + w[2L] * y } diff --git a/R/elementary_index.R b/R/elementary_index.R index f2fca160..27fcf2d0 100644 --- a/R/elementary_index.R +++ b/R/elementary_index.R @@ -9,7 +9,8 @@ #' (if `contrib = TRUE`) to `x` and `weights` grouped by `ea` and `period`. That #' is, for every combination of elementary aggregate and time period, #' `elementary_index()` calculates an index based on a generalized mean of -#' order `r` and, optionally, percent-change contributions. Product names should +#' order `order` and, optionally, percent-change contributions. Product names +#' should #' be unique within each elementary aggregate at each time period when making #' contributions and, if not, are #' passed to [make.unique()] with a warning. The default @@ -81,8 +82,8 @@ #' missing values #' are not removed. Setting `na.rm = TRUE` is equivalent to overall mean #' imputation. -#' @param r `[numeric(1)]` Order of the generalized mean to aggregate price -#' relatives. 0 for a +#' @param order,r `[numeric(1)]` Order of the generalized mean to aggregate +#' price relatives. 0 for a #' geometric index (the default for making elementary indexes), 1 for an #' arithmetic index (the default for aggregating elementary indexes and #' averaging indexes over subperiods), or -1 for a harmonic index (usually for @@ -163,6 +164,9 @@ #' r = 1 #' ) elementary_index <- function(x, ...) { + if ("r" %in% ...names()) { + warning("`r` is deprecated and will be removed; use `order` instead") + } UseMethod("elementary_index") } @@ -184,7 +188,8 @@ elementary_index.numeric <- function( chainable = TRUE, na.rm = FALSE, contrib = FALSE, - r = 0 + order = 0, + r = order ) { chkDots(...) if (!is.null(weights)) { diff --git a/R/impute_prices.R b/R/impute_prices.R index ec22030b..0de56e0c 100644 --- a/R/impute_prices.R +++ b/R/impute_prices.R @@ -59,8 +59,8 @@ #' product weights), or something that can be coerced into one. The default is #' to give each price equal weight. This is evaluated in `x` for the data #' frame method. -#' @param r `[numeric(2)]` A pair of numeric values. The first gives the order -#' of the generalized-mean price index used to calculate the +#' @param order,r `[numeric(2)]` A pair of numeric values. The first gives the +#' order of the generalized-mean price index used to calculate the #' elementary price indexes, defaulting to a geometric index. The second #' gives the order of the generalized-mean price index used to aggregate the #' elementary price indexes, defaulting to an arithmetic index. Other values @@ -123,6 +123,9 @@ #' method = "overall-mean" #' ) impute_prices <- function(x, ...) { + if ("r" %in% ...names()) { + warning("`r` is deprecated and will be removed; use `order` instead") + } UseMethod("impute_prices") } @@ -142,7 +145,8 @@ impute_prices.matrix <- function( ea = NULL, weights = NULL, pias = NULL, - r = c(0, 1), + order = c(0, 1), + r = order, method = c("overall-mean", "carry-forward"), impute_rules = NULL ) { @@ -192,7 +196,7 @@ impute_prices.matrix <- function( ea = ea[[t]], weights = weights[[t]], na.rm = TRUE, - r = r[1L] + order = r[1L] ) time(index) <- names(res)[t] if (!is.null(pias)) { @@ -200,10 +204,10 @@ impute_prices.matrix <- function( index, pias, na.rm = TRUE, - r = r[2L], + order = r[2L], impute_rules = impute_rules ) - pias <- update(pias, index, r = r[2L]) + pias <- update(pias, index, order = r[2L]) } eas <- if (!is.null(ea)) { match(as.character(ea[[t]][impute]), index$levels) @@ -240,7 +244,8 @@ impute_prices.numeric <- function( ea = NULL, weights = NULL, pias = NULL, - r = c(0, 1), + order = c(0, 1), + r = order, method = c("overall-mean", "carry-forward", "carry-backward"), impute_rules = NULL ) { @@ -296,7 +301,7 @@ impute_prices.numeric <- function( ea = ea[[t]], weights = weights[[t]], na.rm = TRUE, - r = r[1L] + order = r[1L] ) time(index) <- names(res)[t] if (!is.null(pias)) { @@ -304,10 +309,10 @@ impute_prices.numeric <- function( index, pias, na.rm = TRUE, - r = r[2L], + order = r[2L], impute_rules = impute_rules ) - pias <- update(pias, index, r = r[2L]) + pias <- update(pias, index, order = r[2L]) } eas <- if (!is.null(ea)) { match(as.character(ea[[t]][impute]), index$levels) diff --git a/R/mean.piar_index.R b/R/mean.piar_index.R index f92c4a3d..190aa2af 100644 --- a/R/mean.piar_index.R +++ b/R/mean.piar_index.R @@ -39,8 +39,8 @@ #' missing values #' are not removed. Setting `na.rm = TRUE` is equivalent to overall mean #' imputation. -#' @param r `[numeric(1)]` Order of the generalized mean to aggregate index -#' values. 0 for a +#' @param order,r `[numeric(1)]` Order of the generalized mean to aggregate +#' index values. 0 for a #' geometric index (the default for making elementary indexes), 1 for an #' arithmetic index (the default for aggregating elementary indexes and #' averaging indexes over subperiods), or -1 for a harmonic index (usually for @@ -74,17 +74,21 @@ mean.chainable_piar_index <- function( window = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, duplicate_contrib = c("sum", "make.unique") ) { chkDots(...) + if ("r" %in% names(sys.call())) { + warning("`r` is deprecated and will be removed; use `order` instead") + } mean_index( x, weights, window = window, na.rm = na.rm, contrib = contrib, - r = r, + order = r, chainable = TRUE, duplicate_contrib = match.arg(duplicate_contrib) ) @@ -99,17 +103,21 @@ mean.direct_piar_index <- function( window = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, duplicate_contrib = c("sum", "make.unique") ) { chkDots(...) + if ("r" %in% names(sys.call())) { + warning("`r` is deprecated and will be removed; use `order` instead") + } mean_index( x, weights, window = window, na.rm = na.rm, contrib = contrib, - r = r, + order = r, chainable = FALSE, duplicate_contrib = match.arg(duplicate_contrib) ) @@ -123,7 +131,7 @@ mean_index <- function( window, na.rm, contrib, - r, + order, chainable, duplicate_contrib ) { @@ -138,7 +146,7 @@ mean_index <- function( dim(weights) <- c(nlevels(x), ntime(x)) } - r <- as.numeric(r) + order <- as.numeric(order) window <- as.integer(window %||% ntime(x)) if (window < 1L) { stop("`window` must be a positive integer") @@ -166,7 +174,7 @@ mean_index <- function( if (!is.null(weights)) { w <- split_rows(weights[, j, drop = FALSE], rows) } - res[[i]] <- mapply(gmean, rel, w, r, na.rm = na.rm, USE.NAMES = FALSE) + res[[i]] <- mapply(gmean, rel, w, order, na.rm = na.rm, USE.NAMES = FALSE) if (has_contrib) { con <- split_rows(x$contrib[, j, drop = FALSE], rows) contrib[[i]] <- mapply( @@ -174,7 +182,7 @@ mean_index <- function( con, rel, w, - r, + order, res[[i]], duplicate_contrib, SIMPLIFY = FALSE, diff --git a/R/update.aggregation_structure.R b/R/update.aggregation_structure.R index 41854fd3..43ba1a7c 100644 --- a/R/update.aggregation_structure.R +++ b/R/update.aggregation_structure.R @@ -13,8 +13,8 @@ #' [`aggregate()`][aggregate.piar_index]. #' @param period `[character(1)]` The time period used to price update the #' weights. The default uses the last period in `index`. -#' @param r `[numeric(1)]` Order of the generalized mean to update the weights. -#' The default is 1 for an arithmetic index. +#' @param order,r `[numeric(1)]` Order of the generalized mean to update the +#' weights. The default is 1 for an arithmetic index. #' @param ... Not currently used. #' #' @returns @@ -53,8 +53,12 @@ update.piar_aggregation_structure <- function( index, ..., period = NULL, - r = 1 + order = 1, + r = order ) { + if ("r" %in% names(sys.call())) { + warning("`r` is deprecated and will be removed; use `order` instead") + } chkDots(...) index <- as_index(index, chainable = FALSE) r <- as.numeric(r) @@ -70,7 +74,7 @@ update.piar_aggregation_structure <- function( weights(object) <- update_weights( index$index[, period][eas], object$weights, - r + order = r ) object } diff --git a/inst/tinytest/test-aggregate.R b/inst/tinytest/test-aggregate.R index 372a116a..1f356357 100644 --- a/inst/tinytest/test-aggregate.R +++ b/inst/tinytest/test-aggregate.R @@ -224,7 +224,7 @@ local({ price_relative(price, period = period, product = product) ~ period + business, contrib = TRUE, - r = 0.2 + order = 0.2 ) ms_pias <- with( @@ -235,7 +235,7 @@ local({ ) ) - ms_index <- aggregate(ms_epr, ms_pias, r = -1.7, na.rm = TRUE) + ms_index <- aggregate(ms_epr, ms_pias, order = -1.7, na.rm = TRUE) res <- c( 1, @@ -283,11 +283,14 @@ local({ expect_equal(as.matrix(ms_index), res) expect_equal( - as.matrix(aggregate(ms_index, ms_pias, r = -1.7, na.rm = TRUE)), + as.matrix(aggregate(ms_index, ms_pias, order = -1.7, na.rm = TRUE)), as.matrix(ms_index) ) - expect_equal(aggregate(chain(ms_index), ms_pias, r = -1.7), chain(ms_index)) + expect_equal( + aggregate(chain(ms_index), ms_pias, order = -1.7), + chain(ms_index) + ) expect_equal( as.matrix(ms_index)[1, ], @@ -295,10 +298,11 @@ local({ ) expect_equal( as.matrix(ms_index)[1, ], - colSums(contrib(aggregate(ms_index, ms_pias, r = -1.7)), na.rm = TRUE) + 1 + colSums(contrib(aggregate(ms_index, ms_pias, order = -1.7)), na.rm = TRUE) + + 1 ) expect_equal( - aggregate(ms_index, ms_pias, r = -1.7, duplicate_contrib = "sum"), + aggregate(ms_index, ms_pias, order = -1.7, duplicate_contrib = "sum"), ms_index ) @@ -320,9 +324,9 @@ local({ as.matrix(chain(ms_index[1:3, ])) ) - ms_index <- aggregate(ms_epr, ms_pias, r = -1.7) + ms_index <- aggregate(ms_epr, ms_pias, order = -1.7) - expect_equal(aggregate(ms_index, ms_pias, r = -1.7), ms_index) + expect_equal(aggregate(ms_index, ms_pias, order = -1.7), ms_index) expect_equal(as.matrix(ms_index)[1, ], colSums(contrib(ms_index)) + 1) expect_equal( @@ -476,8 +480,8 @@ local({ # Should work for a non-arithmetic index expect_equal( - chain(aggregate(epr_pop, pias, r = 3)), - aggregate(epr_fx, pias, r = 3) + chain(aggregate(epr_pop, pias, order = 3)), + aggregate(epr_fx, pias, order = 3) ) # Consistency in aggregation holds with a change in base period @@ -519,8 +523,8 @@ local({ 1:3 ) - arithmetic_contributions <- function(x, w, r = 1) { - (x - 1) * transmute_weights(x, w, r, to = 1) + arithmetic_contributions <- function(x, w, order = 1) { + (x - 1) * transmute_weights(x, w, order, to = 1) } index <- aggregate(epr, pias) expect_equal( @@ -548,7 +552,7 @@ local({ ea = gl(2, 2), period = gl(1, 4), contrib = TRUE, - r = 1 + order = 1 ) epr2 <- epr1 levels(epr2) <- 3:4 @@ -621,9 +625,9 @@ local({ ) levels(epr2) <- c(211, 221, 222) - index <- aggregate(merge(epr1, epr2), pias, r = 0.5) - index1 <- aggregate(epr1, pias, r = 0.5) - index2 <- aggregate(epr2, pias, r = 0.5) + index <- aggregate(merge(epr1, epr2), pias, order = 0.5) + index1 <- aggregate(epr1, pias, order = 0.5) + index2 <- aggregate(epr2, pias, order = 0.5) expect_equal(index[1], index1[1]) expect_equal(index[2], index2[2]) @@ -691,11 +695,14 @@ local({ ) ) - ms_index <- chain(aggregate(ms_epr, ms_pias, na.rm = TRUE, r = 2)) + ms_index <- chain(aggregate(ms_epr, ms_pias, na.rm = TRUE, order = 2)) ms_index2 <- unchain(ms_index[, -3]) - expect_equal(chain(aggregate(ms_index2, ms_pias, r = 2))[, 3], ms_index[, 4]) + expect_equal( + chain(aggregate(ms_index2, ms_pias, order = 2))[, 3], + ms_index[, 4] + ) }) # Skipping eas works. @@ -778,7 +785,7 @@ local({ ) res <- aggregate(epr, pias, pias2 = pias2, na.rm = TRUE) res1 <- aggregate(epr, pias, na.rm = TRUE) - res2 <- aggregate(epr, pias2, r = -1, na.rm = TRUE) + res2 <- aggregate(epr, pias2, order = -1, na.rm = TRUE) expect_equal( sqrt(as.matrix(res1) * as.matrix(res2)), @@ -812,8 +819,8 @@ local({ # Tornqvist. expect_equal( - aggregate(epr, pias, r = 0, na.rm = TRUE), - aggregate(epr, pias, pias2 = pias, r = 0, na.rm = TRUE) + aggregate(epr, pias, order = 0, na.rm = TRUE), + aggregate(epr, pias, pias2 = pias, order = 0, na.rm = TRUE) ) }) diff --git a/inst/tinytest/test-elemental_index.R b/inst/tinytest/test-elemental_index.R index f8a4628a..d51d477b 100644 --- a/inst/tinytest/test-elemental_index.R +++ b/inst/tinytest/test-elemental_index.R @@ -7,7 +7,7 @@ epr1 <- elementary_index(ms_prices, rel ~ period + business, contrib = TRUE) epr2 <- elementary_index( ms_prices, rel ~ period + business, - r = -1, + order = -1, contrib = TRUE, na.rm = TRUE ) @@ -58,13 +58,13 @@ local({ ms_prices2, rel ~ period + business, weights = w1, - r = 1 + order = 1 ) p <- elementary_index( ms_prices2, rel ~ period + business, weights = w2, - r = -1 + order = -1 ) expect_equal(sqrt(as.matrix(l) * as.matrix(p)), as.matrix(epr3)) @@ -80,12 +80,12 @@ local({ sepr <- elementary_index(ms_prices2, rel ~ period + business, weights = w) - l <- elementary_index(ms_prices2, rel ~ period + business, r = 1.5) + l <- elementary_index(ms_prices2, rel ~ period + business, order = 1.5) p <- elementary_index( ms_prices2, rel ~ period + business, weights = w2, - r = -1.5 + order = -1.5 ) expect_equal(sqrt(as.matrix(l) * as.matrix(p)), as.matrix(sepr)) }) @@ -117,7 +117,7 @@ local({ expect_error(elementary_index(1:3, period = factor(1:3, levels = numeric(0)))) expect_error(elementary_index(1:3, ea = factor(1:3, levels = numeric(0)))) expect_error(elementary_index(setNames(1:3, c("", 1, 2)), contrib = TRUE)) - expect_error(elementary_index(-1:1, period = 1:3, ea = 1:3, r = 1)) + expect_error(elementary_index(-1:1, period = 1:3, ea = 1:3, order = 1)) expect_warning(elementary_index( setNames(1:3, rep(1, 3)), period = gl(1, 3), diff --git a/inst/tinytest/test-mean.R b/inst/tinytest/test-mean.R index 0285c800..ee89062f 100644 --- a/inst/tinytest/test-mean.R +++ b/inst/tinytest/test-mean.R @@ -63,7 +63,7 @@ local({ ms_epr, weights = w, window = 2, - r = 2.5, + order = 2.5, na.rm = TRUE, duplicate_contrib = "sum" ) @@ -78,7 +78,7 @@ local({ local({ x <- as_index(matrix(1:9, 3)) expect_equal(mean(x, window = 1), x) - expect_equal(mean(x, weights = 9:1, window = 1, r = 0), x) + expect_equal(mean(x, weights = 9:1, window = 1, order = 0), x) expect_equal(mean(window(x, end(x))), window(x, end(x))) }) diff --git a/inst/tinytest/test-stack.R b/inst/tinytest/test-stack.R index 04a4bbf3..b88a3e45 100644 --- a/inst/tinytest/test-stack.R +++ b/inst/tinytest/test-stack.R @@ -23,7 +23,7 @@ local({ expect_error(suppressWarnings(stack(index1, epr1))) time(epr2) <- time(epr1) expect_error(stack(epr1, epr2)) - expect_error(stack(index1, aggregate(epr2, pias2, r = 0))) + expect_error(stack(index1, aggregate(epr2, pias2, order = 0))) }) # Stacking and unstacking are opposite operations. diff --git a/inst/tinytest/test-update.R b/inst/tinytest/test-update.R index 4bee4a93..ff756d40 100644 --- a/inst/tinytest/test-update.R +++ b/inst/tinytest/test-update.R @@ -32,7 +32,7 @@ local({ ) ) expect_equal( - weights(update(agg, index, r = 0), ea_only = FALSE), + weights(update(agg, index, order = 0), ea_only = FALSE), list( level1 = c("1" = NA_real_, "2" = NA), level2 = c("11" = NA_real_, "21" = NA, "12" = 1), diff --git a/man/aggregate.piar_index.Rd b/man/aggregate.piar_index.Rd index e0ee447c..f8220236 100644 --- a/man/aggregate.piar_index.Rd +++ b/man/aggregate.piar_index.Rd @@ -13,7 +13,8 @@ pias2 = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, include_ea = TRUE, duplicate_contrib = c("sum", "make.unique"), impute_rules = NULL @@ -26,7 +27,8 @@ pias2 = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, include_ea = TRUE, duplicate_contrib = c("sum", "make.unique"), impute_rules = NULL @@ -52,8 +54,8 @@ overall mean imputation.} \item{contrib}{\verb{[logical(1)]} Aggregate percent-change contributions in \code{x}? By default contributions are aggregated.} -\item{r}{\verb{[numeric(1)]} Order of the generalized mean to aggregate index -values. 0 for a +\item{order, r}{\verb{[numeric(1)]} Order of the generalized mean to aggregate +index values. 0 for a geometric index (the default for making elementary indexes), 1 for an arithmetic index (the default for aggregating elementary indexes and averaging indexes over subperiods), or -1 for a harmonic index (usually for diff --git a/man/elementary_index.Rd b/man/elementary_index.Rd index 1cac3f99..84e335fc 100644 --- a/man/elementary_index.Rd +++ b/man/elementary_index.Rd @@ -22,7 +22,8 @@ elementary_index(x, ...) chainable = TRUE, na.rm = FALSE, contrib = FALSE, - r = 0 + order = 0, + r = order ) \method{elementary_index}{data.frame}(x, formula, ..., weights = NULL, product = NULL) @@ -72,8 +73,8 @@ imputation.} \item{contrib}{\verb{[logical(1)]} Should percent-change contributions be calculated? The default does not calculate contributions.} -\item{r}{\verb{[numeric(1)]} Order of the generalized mean to aggregate price -relatives. 0 for a +\item{order, r}{\verb{[numeric(1)]} Order of the generalized mean to aggregate +price relatives. 0 for a geometric index (the default for making elementary indexes), 1 for an arithmetic index (the default for aggregating elementary indexes and averaging indexes over subperiods), or -1 for a harmonic index (usually for @@ -102,7 +103,8 @@ wrapper that applies \code{\link[=gmean]{gmean()}} and \code{\link[=transmute_we (if \code{contrib = TRUE}) to \code{x} and \code{weights} grouped by \code{ea} and \code{period}. That is, for every combination of elementary aggregate and time period, \code{elementary_index()} calculates an index based on a generalized mean of -order \code{r} and, optionally, percent-change contributions. Product names should +order \code{order} and, optionally, percent-change contributions. Product names +should be unique within each elementary aggregate at each time period when making contributions and, if not, are passed to \code{\link[=make.unique]{make.unique()}} with a warning. The default diff --git a/man/emean.Rd b/man/emean.Rd index 9b21dc74..196c890c 100644 --- a/man/emean.Rd +++ b/man/emean.Rd @@ -26,6 +26,9 @@ of \code{x} and \code{y}. Calculate the component-wise extended mean. } \details{ +The extended mean is also called the difference mean, Stolarsky mean, or +extended mean-value mean; see Bullen (2003, p. 393) for details. + Both \code{x} and \code{y} should be strictly positive. This is not enforced, but the results may not make sense when the extended mean is not defined. The usual recycling rules apply when \code{x} and \code{y} are not the same length. diff --git a/man/gmean.Rd b/man/gmean.Rd index 6082f1c3..434c3a11 100644 --- a/man/gmean.Rd +++ b/man/gmean.Rd @@ -12,8 +12,8 @@ gmean(x, weights = NULL, order = 1, na.rm = FALSE) \item{weights}{\verb{[numeric >= 0]} A positive numeric vector of weights, the same length as \code{x}. The default is to equally weight each element of \code{x}.} -\item{order}{\verb{[numeric(1)]} A finite number giving the order of the -generalized mean. The default calculates an arithmetic mean.} +\item{order}{\verb{[numeric(1)]} A finite number giving the order (or exponent) of +the generalized mean. The default calculates an arithmetic mean.} \item{na.rm}{\verb{[logical(1)]} Should missing values be removed? By default, missing values are not removed.} diff --git a/man/impute_prices.Rd b/man/impute_prices.Rd index b44c0208..f6b82e59 100644 --- a/man/impute_prices.Rd +++ b/man/impute_prices.Rd @@ -23,7 +23,8 @@ impute_prices(x, ...) ea = NULL, weights = NULL, pias = NULL, - r = c(0, 1), + order = c(0, 1), + r = order, method = c("overall-mean", "carry-forward"), impute_rules = NULL ) @@ -36,7 +37,8 @@ impute_prices(x, ...) ea = NULL, weights = NULL, pias = NULL, - r = c(0, 1), + order = c(0, 1), + r = order, method = c("overall-mean", "carry-forward", "carry-backward"), impute_rules = NULL ) @@ -83,8 +85,8 @@ structure, or something that can be coerced into one, as made with \code{\link[=aggregation_structure]{aggregation_structure()}}. The default imputes from elementary indexes only (i.e., not recursively).} -\item{r}{\verb{[numeric(2)]} A pair of numeric values. The first gives the order -of the generalized-mean price index used to calculate the +\item{order, r}{\verb{[numeric(2)]} A pair of numeric values. The first gives the +order of the generalized-mean price index used to calculate the elementary price indexes, defaulting to a geometric index. The second gives the order of the generalized-mean price index used to aggregate the elementary price indexes, defaulting to an arithmetic index. Other values diff --git a/man/mean.piar_index.Rd b/man/mean.piar_index.Rd index d549f4e9..2d37c0ea 100644 --- a/man/mean.piar_index.Rd +++ b/man/mean.piar_index.Rd @@ -13,7 +13,8 @@ window = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, duplicate_contrib = c("sum", "make.unique") ) @@ -24,7 +25,8 @@ window = NULL, na.rm = FALSE, contrib = TRUE, - r = 1, + order = 1, + r = order, duplicate_contrib = c("sum", "make.unique") ) } @@ -54,8 +56,8 @@ imputation.} \item{contrib}{\verb{[logical(1)]} Aggregate percent-change contributions in \code{x}? By default contributions are aggregated.} -\item{r}{\verb{[numeric(1)]} Order of the generalized mean to aggregate index -values. 0 for a +\item{order, r}{\verb{[numeric(1)]} Order of the generalized mean to aggregate +index values. 0 for a geometric index (the default for making elementary indexes), 1 for an arithmetic index (the default for aggregating elementary indexes and averaging indexes over subperiods), or -1 for a harmonic index (usually for diff --git a/man/update.piar_aggregation_structure.Rd b/man/update.piar_aggregation_structure.Rd index beb37dd2..013cdad3 100644 --- a/man/update.piar_aggregation_structure.Rd +++ b/man/update.piar_aggregation_structure.Rd @@ -4,7 +4,7 @@ \alias{update.piar_aggregation_structure} \title{Update an aggregation structure} \usage{ -\method{update}{piar_aggregation_structure}(object, index, ..., period = NULL, r = 1) +\method{update}{piar_aggregation_structure}(object, index, ..., period = NULL, order = 1, r = order) } \arguments{ \item{object}{\verb{[piar_aggregation_structure]} A price index aggregation @@ -19,8 +19,8 @@ that can be coerced into one. Usually an aggregate price index as made by \item{period}{\verb{[character(1)]} The time period used to price update the weights. The default uses the last period in \code{index}.} -\item{r}{\verb{[numeric(1)]} Order of the generalized mean to update the weights. -The default is 1 for an arithmetic index.} +\item{order, r}{\verb{[numeric(1)]} Order of the generalized mean to update the +weights. The default is 1 for an arithmetic index.} } \value{ A copy of \code{object} with price-updated weights using the index diff --git a/man/update_weights.Rd b/man/update_weights.Rd index dfb01452..013a512c 100644 --- a/man/update_weights.Rd +++ b/man/update_weights.Rd @@ -12,8 +12,8 @@ update_weights(x, weights = NULL, order = 1) \item{weights}{\verb{[numeric >= 0]} A positive numeric vector of weights, the same length as \code{x}. The default is to equally weight each element of \code{x}.} -\item{order}{\verb{[numeric(1)]} A finite number giving the order of the -generalized mean. The default calculates an arithmetic mean.} +\item{order}{\verb{[numeric(1)]} A finite number giving the order (or exponent) of +the generalized mean. The default calculates an arithmetic mean.} } \value{ A numeric vector the same length as \code{x}. diff --git a/tests/test-making-price-indexes.R b/tests/test-making-price-indexes.R index c60397bb..e47a2865 100644 --- a/tests/test-making-price-indexes.R +++ b/tests/test-making-price-indexes.R @@ -132,10 +132,20 @@ ms_elementary2 aggregate(ms_elementary2, pias, na.rm = TRUE) ## ----------------------------------------------------------------------------- -elementary_index(ms_prices, relatives ~ period + business, na.rm = TRUE, r = 1) +elementary_index( + ms_prices, + relatives ~ period + business, + na.rm = TRUE, + order = 1 +) ## ----------------------------------------------------------------------------- -elementary_index(ms_prices, relatives ~ period + business, na.rm = TRUE, r = -1) +elementary_index( + ms_prices, + relatives ~ period + business, + na.rm = TRUE, + order = -1 +) ## ----------------------------------------------------------------------------- ms_prices2 <- transform(ms_prices, quantity = 10 - price) @@ -294,7 +304,7 @@ pias <- with( ## ----------------------------------------------------------------------------- paasche <- Reduce( stack, - Map(aggregate, ms_elementary, pias, na.rm = TRUE, r = -1) + Map(aggregate, ms_elementary, pias, na.rm = TRUE, order = -1) ) paasche diff --git a/tests/test-making-price-indexes.Rout.save b/tests/test-making-price-indexes.Rout.save index 68e91a5f..b487c1f1 100644 --- a/tests/test-making-price-indexes.Rout.save +++ b/tests/test-making-price-indexes.Rout.save @@ -299,7 +299,12 @@ levels 202001 202002 202003 202004 B5 1 1.1721550 1.0400686 4.576286 > > ## ----------------------------------------------------------------------------- -> elementary_index(ms_prices, relatives ~ period + business, na.rm = TRUE, r = 1) +> elementary_index( ++ ms_prices, ++ relatives ~ period + business, ++ na.rm = TRUE, ++ order = 1 ++ ) Period-over-period price index for 4 levels over 4 time periods time levels 202001 202002 202003 202004 @@ -309,7 +314,12 @@ levels 202001 202002 202003 202004 B4 NaN NaN NaN 9.368610 > > ## ----------------------------------------------------------------------------- -> elementary_index(ms_prices, relatives ~ period + business, na.rm = TRUE, r = -1) +> elementary_index( ++ ms_prices, ++ relatives ~ period + business, ++ na.rm = TRUE, ++ order = -1 ++ ) Period-over-period price index for 4 levels over 4 time periods time levels 202001 202002 202003 202004 @@ -636,7 +646,7 @@ levels 202004 > ## ----------------------------------------------------------------------------- > paasche <- Reduce( + stack, -+ Map(aggregate, ms_elementary, pias, na.rm = TRUE, r = -1) ++ Map(aggregate, ms_elementary, pias, na.rm = TRUE, order = -1) + ) > > paasche diff --git a/vignettes/index-number-formulas.qmd b/vignettes/index-number-formulas.qmd index 955ae154..639687d9 100644 --- a/vignettes/index-number-formulas.qmd +++ b/vignettes/index-number-formulas.qmd @@ -52,7 +52,7 @@ corresponds to an arithmetic mean. ```{r} prices |> - elementary_index(price / back_price ~ period + business, r = 1) + elementary_index(price / back_price ~ period + business, order = 1) ``` The Coggeshall index (equally-weighted harmonic mean of price relatives) @@ -62,7 +62,7 @@ an order `r` of -1. ```{r} prices |> - elementary_index(price / back_price ~ period + business, r = -1) + elementary_index(price / back_price ~ period + business, order = -1) ``` Weights can be added to make, for example, elementary indexes using the diff --git a/vignettes/superlative-aggregation.qmd b/vignettes/superlative-aggregation.qmd index c56e732d..1438e9fd 100644 --- a/vignettes/superlative-aggregation.qmd +++ b/vignettes/superlative-aggregation.qmd @@ -92,7 +92,7 @@ paasche <- Map( paasche_pias, na.rm = TRUE, include_ea = FALSE, - r = -1 + order = -1 ) |> Reduce(stack, x = _) From c73cf4f10b3c6e210aa5c67639709e769b1ac556 Mon Sep 17 00:00:00 2001 From: Steve Martin Date: Wed, 22 Jul 2026 00:31:47 -0400 Subject: [PATCH 7/7] Bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 472d7506..3b4e63b1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: piar Title: Price Index Aggregation -Version: 0.9.0.9005 +Version: 0.9.0.9006 Authors@R: c( person("Steve", "Martin", role = c("aut", "cre", "cph"), email = "marberts@protonmail.com",