You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在dplyr的group_by()中结合cut()实现分组动态分箱连续变量

在dplyr多分组下实现动态分箱的通用方案

哈哈,这个多分组下动态分箱的需求太常见了!我刚好之前处理过类似的场景,在dplyr里完全可以实现通用的方案,核心就是让每个分组自己计算分箱阈值,再用这个阈值给当前组的变量分箱。下面我一步步给你演示:

1. 先补全你的示例数据集

我先补一个包含groupsubgroup双分组,且有连续变量value的数据集,方便后续演示:

set.seed(123) # 设置随机种子保证结果可复现
df <- data.frame(
  group = c(rep("Group 1", 10), rep("Group 2", 10)),
  subgroup = c(rep("A", 5), rep("B", 5), rep("A", 5), rep("B", 5)),
  value = c(rnorm(10, mean = 50, sd = 5), rnorm(10, mean = 70, sd = 8))
)

2. 最常用的动态分箱:分位数分箱

比如我们要在每个group+subgroup的组合内,把value按四分位数分成低、中、高三箱,每个组的分位数阈值都是基于自身数据计算的:

library(dplyr)

df_quantile_binned <- df %>%
  group_by(group, subgroup) %>% # 这里可以放任意多列分组变量
  mutate(
    # 计算当前组的25%和75%分位数作为阈值(加na.rm处理缺失值)
    q25 = quantile(value, 0.25, na.rm = TRUE),
    q75 = quantile(value, 0.75, na.rm = TRUE),
    # 根据阈值给value分箱
    value_bin = case_when(
      value <= q25 ~ "Low",
      value <= q75 ~ "Medium",
      TRUE ~ "High"
    )
  ) %>%
  ungroup() %>% # 记得取消分组,避免后续操作受影响
  select(-q25, -q75) # 删掉临时计算的阈值列

# 查看结果
head(df_quantile_binned)

3. 自定义规则的动态分箱

如果你的分箱规则不是分位数,而是比如基于每个组的均值和标准差,也完全没问题,同样在分组内计算阈值即可:

df_custom_binned <- df %>%
  group_by(group, subgroup) %>%
  mutate(
    # 计算当前组的均值和标准差
    mean_val = mean(value, na.rm = TRUE),
    sd_val = sd(value, na.rm = TRUE),
    # 按均值±标准差分箱
    value_bin = case_when(
      value < mean_val - sd_val ~ "Below Mean - 1SD",
      value < mean_val ~ "Below Mean",
      value < mean_val + sd_val ~ "Above Mean",
      TRUE ~ "Above Mean + 1SD"
    )
  ) %>%
  ungroup() %>%
  select(-mean_val, -sd_val)

# 查看结果
head(df_custom_binned)

4. 封装成通用函数,方便重复使用

如果需要多次对不同变量、不同分组做动态分箱,最好把逻辑封装成函数,这样更灵活:

dynamic_bin <- function(data, group_cols, bin_col, bin_logic) {
  # 参数说明:
  # data: 输入数据集
  # group_cols: 分组列名的向量(比如c("group", "subgroup"))
  # bin_col: 需要分箱的连续变量列名
  # bin_logic: 自定义分箱逻辑的函数,输入是当前组的连续变量向量,输出是分箱后的类别向量
  
  data %>%
    group_by(across(all_of(group_cols))) %>%
    mutate(!!paste0(bin_col, "_bin") := bin_logic(!!sym(bin_col))) %>%
    ungroup()
}

# 用法示例:用分位数分箱
set.seed(123)
df_func_binned <- dynamic_bin(
  data = df,
  group_cols = c("group", "subgroup"),
  bin_col = "value",
  bin_logic = function(x) {
    qs <- quantile(x, c(0.25, 0.75), na.rm = TRUE)
    case_when(
      x <= qs[1] ~ "Low",
      x <= qs[2] ~ "Medium",
      TRUE ~ "High"
    )
  }
)

# 另一个用法示例:等宽分箱(每个组内分成3个等宽区间)
df_equal_width_binned <- dynamic_bin(
  data = df,
  group_cols = c("group", "subgroup"),
  bin_col = "value",
  bin_logic = function(x) cut(x, breaks = 3, labels = c("Low", "Medium", "High"))
)

关键注意点

  • group_by(across(all_of(group_cols)))可以完美适配任意数量的分组列,不管是1列还是N列都能搞定
  • 所有分箱阈值的计算都是在分组内部完成的,完全符合“根据观测值的分组动态调整”的需求
  • 如果数据中有缺失值,记得在计算分位数、均值、标准差时加上na.rm = TRUE,避免出错

内容的提问来源于stack exchange,提问作者Mikey Harper

火山引擎 最新活动