R语言:编写识别任意百分位数的函数是否正确?
R语言百分位数分组函数验证与分组支持问题
我在用R做数据分析,现有数据集如下:
library(dplyr) var1 = rnorm(10000, 100,100) var2 = rnorm(10000, 100,100) var3 = rnorm(10000, 100,100) var4 = rnorm(10000, 100,100) id = 1:10000 final = data.frame(id, var1, var2, var3, var4)
之前我用下面的代码给var3按十分位数分组生成新变量:
final_without_function = final %>% mutate(class3 = case_when(ntile(var3, 10) == 1 ~ paste0(round(min(var3), 2), " to ", round(quantile(var3, 0.1), 2), " decile 1"), ntile(var3, 10) == 2 ~ paste0(round(quantile(var3, 0.1), 2), " to ", round(quantile(var3, 0.2), 2), " decile 2"), ntile(var3, 10) == 3 ~ paste0(round(quantile(var3, 0.2), 2), " to ", round(quantile(var3, 0.3), 2), " decile 3"), ntile(var3, 10) == 4 ~ paste0(round(quantile(var3, 0.3), 2), " to ", round(quantile(var3, 0.4), 2), " decile 4"), ntile(var3, 10) == 5 ~ paste0(round(quantile(var3, 0.4), 2), " to ", round(quantile(var3, 0.5), 2), " decile 5"), ntile(var3, 10) == 6 ~ paste0(round(quantile(var3, 0.5), 2), " to ", round(quantile(var3, 0.6), 2), " decile 6"), ntile(var3, 10) == 7 ~ paste0(round(quantile(var3, 0.6), 2), " to ", round(quantile(var3, 0.7), 2), " decile 7"), ntile(var3, 10) == 8 ~ paste0(round(quantile(var3, 0.7), 2), " to ", round(quantile(var3, 0.8), 2), " decile 8"), ntile(var3, 10) == 9 ~ paste0(round(quantile(var3, 0.8), 2), " to ", round(quantile(var3, 0.9), 2), " decile 9"), ntile(var3, 10) == 10 ~ paste0(round(quantile(var3, 0.9), 2), " to ", round(max(var3), 2), " decile 10")))
但要改成五分位数或其他分组时,得手动修改代码,所以我写了个自定义函数来实现任意百分位数分组:
percentile_classifier <- function(x, n_percentiles) { # 计算百分位数 percentiles <- quantile(x, probs = seq(0, 1, 1/n_percentiles)) # 创建存储标签的字符向量 labels <- character(length(x)) # 遍历每个百分位数,为每个元素分配对应标签 for (i in 1:length(percentiles)) { lower <- percentiles[i] upper <- ifelse(i == length(percentiles), max(x), percentiles[i+1]) label <- paste0(round(lower, 2), " to ", round(upper, 2), " percentile ", i) labels[x >= lower & x < upper] <- label } # 返回标签 return(labels) }
调用方式如下:
final <- final %>% mutate(class3 = percentile_classifier(var3, 20))
我不确定这个函数写得对不对,同时希望它能支持group_by分组后的场景,比如下面这种:
final = final %>% group_by (factor_var_5,factor_var_6, factor_var_7) %>% mutate(class3 = case_when(ntile(var3, 10) == 1 ~ paste0(round(min(var3), 2), " to ", round(quantile(var3, 0.1), 2), " decile 1"), ntile(var3, 10) == 2 ~ paste0(round(quantile(var3, 0.1), 2), " to ", round(quantile(var3, 0.2), 2), " decile 2"), ntile(var3, 10) == 3 ~ paste0(round(quantile(var3, 0.2), 2), " to ", round(quantile(var3, 0.3), 2), " decile 3"), ntile(var3, 10) == 4 ~ paste0(round(quantile(var3, 0.3), 2), " to ", round(quantile(var3, 0.4), 2), " decile 4"), ntile(var3, 10) == 5 ~ paste0(round(quantile(var3, 0.4), 2), " to ", round(quantile(var3, 0.5), 2), " decile 5"), ntile(var3, 10) == 6 ~ paste0(round(quantile(var3, 0.5), 2), " to ", round(quantile(var3, 0.6), 2), " decile 6"), ntile(var3, 10) == 7 ~ paste0(round(quantile(var3, 0.6), 2), " to ", round(quantile(var3, 0.7), 2), " decile 7"), ntile(var3, 10) == 8 ~ paste0(round(quantile(var3, 0.7), 2), " to ", round(quantile(var3, 0.8), 2), " decile 8"), ntile(var3, 10) == 9 ~ paste0(round(quantile(var3, 0.8), 2), " to ", round(quantile(var3, 0.9), 2), " decile 9"), ntile(var3, 10) == 10 ~ paste0(round(quantile(var3, 0.9), 2), " to ", round(max(var3), 2), " decile 10")))
请帮忙验证这个自定义函数是否正确,能不能满足需求?
函数问题分析与修正
原函数的问题
- 边界值覆盖不全:最后一组的判断条件
x >= lower & x < upper会漏掉等于max(x)的元素,导致这些元素标签为空。 - 分组逻辑不一致:原手动代码用
ntile保证分组数量均等,但原函数用quantile计算分位数,和ntile的分组逻辑有差异,可能导致分组数量不均。 - 不支持分组数据:原函数是针对整个向量计算分位数,在
group_by后使用时,会对全数据集计算而非每个分组内计算。
修正后的函数
结合ntile保证分组数量均等,同时生成对应区间标签,且支持dplyr分组操作:
percentile_classifier <- function(x, n_percentiles) { # 获取每个元素的分组编号 group_num <- ntile(x, n_percentiles) # 按分组计算上下界 group_bounds <- tibble(x = x, group = group_num) %>% group_by(group) %>% summarise(lower = min(x), upper = max(x), .groups = "drop") # 生成对应标签 labels <- paste0(round(group_bounds$lower[group_num], 2), " to ", round(group_bounds$upper[group_num], 2), " percentile ", group_num) return(labels) }
支持group_by场景的用法
dplyr会自动对每个分组应用函数,直接在mutate中调用即可:
# 添加测试用分组变量 final$factor_var_5 <- sample(letters[1:2], 10000, replace = TRUE) final$factor_var_6 <- sample(letters[3:4], 10000, replace = TRUE) final$factor_var_7 <- sample(letters[5:6], 10000, replace = TRUE) # 分组后应用函数 final <- final %>% group_by(factor_var_5, factor_var_6, factor_var_7) %>% mutate(class3 = percentile_classifier(var3, 10)) %>% ungroup()
验证函数正确性
- 检查分组数量是否大致均等:
final %>% count(class3)
- 检查分组上下界是否匹配:
final %>% group_by(class3) %>% summarise(min_val = min(var3), max_val = max(var3))
- 对比原手动代码的分组结果:
# 原手动代码结果 final_old <- final %>% mutate(class3_old = case_when(ntile(var3, 10) == 1 ~ paste0(round(min(var3), 2), " to ", round(quantile(var3, 0.1), 2), " decile 1"), ntile(var3, 10) == 2 ~ paste0(round(quantile(var3, 0.1), 2), " to ", round(quantile(var3, 0.2), 2), " decile 2"), ntile(var3, 10) == 3 ~ paste0(round(quantile(var3, 0.2), 2), " to ", round(quantile(var3, 0.3), 2), " decile 3"), ntile(var3, 10) == 4 ~ paste0(round(quantile(var3, 0.3), 2), " to ", round(quantile(var3, 0.4), 2), " decile 4"), ntile(var3, 10) == 5 ~ paste0(round(quantile(var3, 0.4), 2), " to ", round(quantile(var3, 0.5), 2), " decile 5"), ntile(var3, 10) == 6 ~ paste0(round(quantile(var3, 0.5), 2), " to ", round(quantile(var3, 0.6), 2), " decile 6"), ntile(var3, 10) == 7 ~ paste0(round(quantile(var3, 0.6), 2), " to ", round(quantile(var3, 0.7), 2), " decile 7"), ntile(var3, 10) == 8 ~ paste0(round(quantile(var3, 0.7), 2), " to ", round(quantile(var3, 0.8), 2), " decile 8"), ntile(var3, 10) == 9 ~ paste0(round(quantile(var3, 0.8), 2), " to ", round(quantile(var3, 0.9), 2), " decile 9"), ntile(var3, 10) == 10 ~ paste0(round(quantile(var3, 0.9), 2), " to ", round(max(var3), 2), " decile 10"))) # 新函数结果 final_new <- final %>% mutate(class3_new = percentile_classifier(var3, 10)) # 对比分组一致性 table(final_old$class3_old, final_new$class3_new)
总结
修正后的函数解决了原函数的边界值问题,和原手动代码的ntile逻辑完全一致,同时完美支持group_by后的分组计算场景,能满足任意百分位数分组的需求。
内容的提问来源于stack exchange,提问作者stats_noob
相关产品推荐
相关产品推荐

