Grouped and weighted cuts

A numeric vector defines common boundaries. A single number requests that many equal-width intervals, except with .inf = TRUE, where it is a boundary extended with infinities. A function, or a list of numbers and functions, derives boundaries from the data.

With groups, boundary functions run within each group. Row order, other columns and input class are preserved. Missing group values form their own group. Existing tibble grouping does not replace the groups argument.

bf_args passes options to the boundary function; ... in cut3 passes options to base::cut. In cut3_quantile, ... passes options to quantile: an explicit probs is no longer duplicated. Output is always a factor, including with labels = FALSE.

Unweighted quantiles retain R’s type 7. With weights, the inverse weighted empirical distribution is used: for probability p, select the first ordered value whose normalized cumulative weight reaches p. This matches type 1 for integer frequencies and does not interpolate. Zero weights do not determine boundaries; used weights must be finite and nonnegative. Each group needs positive weight after missing values are handled. Only type = 1 is supported with weights.

Other functions must explicitly accept weights or w. Fixed boundaries and interval counts do not read weights. Repeated boundaries collapse; labels must match the remaining intervals. NA observations remain NA and quantiles require na.rm = TRUE when observations are missing.

In 0.3.11 boundary functions ran globally even with groups, and weights were discarded. The correction can change classifications; record the version and quantile convention. See the official R quantile documentation.

library(Dmisc)
x <- data.frame(group = rep(c("A", "B"), each = 3),
                value = c(1, 2, 100, 10, 20, 1000), weight = rep(c(1, 8, 1), 2))
result <- cut3_quantile(x, "value", .groups = "group", weights = "weight",
                        probs = 0.5, .labels = c("low", "high"))
print(result)
##   group value weight
## 1     A   low      1
## 2     A   low      8
## 3     A  high      1
## 4     B   low      1
## 5     B   low      8
## 6     B  high      1
stopifnot(identical(as.character(result$value), rep(c("low", "low", "high"), 2)))