Skip to content

Commit 8701ad5

Browse files
authored
Merge pull request #57 from marberts/unbalanced
Added pad for unbalanced classifications, closes #56
2 parents 0af9b5e + 9af1749 commit 8701ad5

11 files changed

Lines changed: 111 additions & 31 deletions

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: piar
22
Title: Price Index Aggregation
3-
Version: 0.8.2.9003
3+
Version: 0.8.2.9004
44
Authors@R: c(
55
person("Steve", "Martin", role = c("aut", "cre", "cph"),
66
email = "marberts@protonmail.com",

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ control how product contributions are made when there are duplicate products.
88
- Coercing an index or an aggregation structure to a matrix now has dimension
99
names.
1010

11+
- `expand_classification()` and `split_classification()` get a new argument
12+
`pad` to better manage unbalanced classifications.
13+
1114
## Bug fixes
1215

1316
- Fixed description of how product contributions are combined across subperiods

R/expand_classification.R

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#' the hierarchy (as made by `expand_classification()`).
1717
#' @param sep A character used to combine codes/labels across elements of `...`.
1818
#' The default uses ":".
19+
#' @param pad A string used to pad the shorter labels for an unbalanced
20+
#' classification. The default pads with NA.
1921
#'
2022
#' @returns
2123
#' `expand_classification()` returns a list with a entry for each level
@@ -30,6 +32,9 @@
3032
#' [split_classification()] to expand a classification by splitting along
3133
#' a delimiter.
3234
#'
35+
#' `csh_from_digits()` in the \pkg{accumulate} package for different handling
36+
#' of unbalanced classifications.
37+
#'
3338
#' @examples
3439
#' # A simple classification structure
3540
#' # 1
@@ -57,7 +62,7 @@
5762
#' expand_classification(c("01.1.1", "01.1.2", "01.2.1"), width = 2)
5863
#'
5964
#' @export
60-
expand_classification <- function(x, width = 1L) {
65+
expand_classification <- function(x, width = 1L, pad = NA) {
6166
x <- as.character(x)
6267
if (length(x) == 0L) {
6368
return(list())
@@ -77,7 +82,7 @@ expand_classification <- function(x, width = 1L) {
7782
w <- cumsum(width)
7883
x <- strsplit(x, character(0L), fixed = TRUE)
7984
lapply(w, function(i) {
80-
vapply(x, \(x) paste(x[seq_len(i)], collapse = ""), character(1L))
85+
vapply(x, \(x) paste(padded_extract(x, i, pad), collapse = ""), character(1L))
8186
})
8287
}
8388

R/helpers.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
padded_extract <- function(x, i, pad) {
2+
pad <- as.character(pad)
3+
if (length(pad) != 1L) {
4+
stop("'pad' must be a length 1 character")
5+
}
6+
res <- x[seq_len(i)]
7+
res[seq.int(to = i, length.out = max(i - length(x), 0L))] <- pad
8+
res
9+
}
10+
111
#---- Replacing contributions ----
212
near <- function(x, y, tol = .Machine$double.eps^0.5) {
313
abs(x - y) < tol

R/split_classification.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#' @param ... Additional argument to pass to [strsplit()].
1212
#' @param sep A character used to delineate levels in `x` in the result. The
1313
#' default separates levels by ".".
14+
#' @param pad A string used to pad the shorter labels for an unbalanced
15+
#' classification. The default pads with NA.
1416
#'
1517
#' @returns
1618
#' A list with a entry for each level in `x` giving the "digits" that
@@ -37,7 +39,7 @@
3739
#' split_classification(c("01.1.1", "01.1.2", "01.2.1"), ".", fixed = TRUE)
3840
#'
3941
#' @export
40-
split_classification <- function(x, split, ..., sep = ".") {
42+
split_classification <- function(x, split, ..., sep = ".", pad = NA) {
4143
x <- as.character(x)
4244
if (length(x) == 0L) {
4345
return(list())
@@ -47,7 +49,7 @@ split_classification <- function(x, split, ..., sep = ".") {
4749
if (n == 0L) {
4850
return(list(rep.int("", length(x))))
4951
}
50-
res <- do.call(rbind, lapply(x, `[`, seq_len(n)))
52+
res <- do.call(rbind, lapply(x, \(z) padded_extract(z, n, pad)))
5153
Reduce(
5254
\(...) paste(..., sep = sep),
5355
split(res, gl(ncol(res), nrow(res))),

man/contrib.Rd

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/expand_classification.Rd

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/split_classification.Rd

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/Examples/piar-Ex.Rout.save

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,8 @@ levels 1 2 3
395395
> index <- elemental_index(prices, rel ~ period + ea, contrib = TRUE)
396396
>
397397
> pias <- aggregation_structure(
398-
+ list(c("top", "top", "top"), c("a", "b", "c")), weights = 1:3
398+
+ list(c("top", "top", "top"), c("a", "b", "c")),
399+
+ weights = 1:3
399400
+ )
400401
>
401402
> index <- aggregate(index, pias, na.rm = TRUE)
Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,57 @@
11
test_that("expand classification works", {
2-
expect_equal(expand_classification(c("1.1.1", "1.1.2", "1.2.1"), c(2, 2, 1)),
3-
list(c("1.", "1.", "1."),
4-
c("1.1.", "1.1.", "1.2."),
5-
c("1.1.1", "1.1.2", "1.2.1")))
6-
expect_equal(expand_classification(c("1.1.1", "1.1.2", "1.2.1"), c(3, 2)),
7-
list(c("1.1", "1.1", "1.2"),
8-
c("1.1.1", "1.1.2", "1.2.1")))
9-
expect_equal(expand_classification(c("1.1.1", "1.1.2", "1.2.1"), 3),
10-
list(c("1.1", "1.1", "1.2"),
11-
c("1.1.1NA", "1.1.2NA", "1.2.1NA")))
2+
expect_equal(
3+
expand_classification(c("1.1.1", "1.1.2", "1.2.1"), c(2, 2, 1)),
4+
list(
5+
c("1.", "1.", "1."),
6+
c("1.1.", "1.1.", "1.2."),
7+
c("1.1.1", "1.1.2", "1.2.1")
8+
)
9+
)
10+
expect_equal(
11+
expand_classification(c("1.1.1", "1.1.2", "1.2.1"), c(3, 2)),
12+
list(
13+
c("1.1", "1.1", "1.2"),
14+
c("1.1.1", "1.1.2", "1.2.1")
15+
)
16+
)
17+
expect_equal(
18+
expand_classification(c("1.1.1", "1.1.2", "1.2.1"), 3),
19+
list(
20+
c("1.1", "1.1", "1.2"),
21+
c("1.1.1NA", "1.1.2NA", "1.2.1NA")
22+
)
23+
)
1224
expect_equal(expand_classification(character(0)), list())
1325
expect_equal(expand_classification(c("", "")), list(c("NA", "NA")))
1426
})
1527

1628
test_that("expand classification fails when expected", {
1729
expect_error(expand_classification("123", width = c(1, 0, 1)))
1830
expect_error(expand_classification("123", width = c(1, NA, 1)))
31+
expect_error(expand_classification("123", pad = 1:3))
32+
expect_error(expand_classification("123", pad = NULL))
33+
})
34+
35+
test_that("expand classification works with unbalanced classification", {
36+
expect_identical(
37+
expand_classification(c(1234, 12345), c(1, 3, 1), pad = 0),
38+
list(c("1", "1"), c("1234", "1234"), c("12340", "12345"))
39+
)
40+
expect_identical(
41+
expand_classification(c(1234, 12345), c(1, 3, 1), pad = "00"),
42+
list(c("1", "1"), c("1234", "1234"), c("123400", "12345"))
43+
)
1944
})
2045

2146
test_that("interaction works", {
22-
c1 = expand_classification(c(11, 11, 12, 12))
23-
c2 = expand_classification(c(111, 112, 121, 122))
24-
47+
c1 <- expand_classification(c(11, 11, 12, 12))
48+
c2 <- expand_classification(c(111, 112, 121, 122))
49+
2550
expect_identical(
2651
interact_classifications(c1),
2752
list(c1)
2853
)
29-
54+
3055
expect_identical(
3156
interact_classifications(c1, c2),
3257
list(list(
@@ -39,7 +64,7 @@ test_that("interaction works", {
3964
c("11:111", "11:112", "12:121", "12:122")
4065
))
4166
)
42-
67+
4368
expect_identical(
4469
interact_classifications(list(rep(1, 4)), c1, c2),
4570
list(list(
@@ -52,7 +77,7 @@ test_that("interaction works", {
5277
c("1:11:111", "1:11:112", "1:12:121", "1:12:122")
5378
))
5479
)
55-
80+
5681
expect_error(interact_classifications(c1, list(c2)))
5782
expect_error(interact_classifications(list(), c1))
5883
expect_error(interact_classifications(c1, list(1)))
@@ -62,7 +87,7 @@ test_that("interacting a single classification does nothing", {
6287
expect_identical(interact_classifications(), list())
6388
expect_identical(interact_classifications(list(), list()), list())
6489
expect_identical(interact_classifications(list(1)), list(list(1)))
65-
66-
x = expand_classification(c("1.1.1", "1.1.2", "1.2.1"), c(2, 2, 1))
90+
91+
x <- expand_classification(c("1.1.1", "1.1.2", "1.2.1"), c(2, 2, 1))
6792
expect_identical(interact_classifications(x)[[1]], x)
68-
})
93+
})

0 commit comments

Comments
 (0)