You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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")))

请帮忙验证这个自定义函数是否正确,能不能满足需求?


函数问题分析与修正

原函数的问题

  1. 边界值覆盖不全:最后一组的判断条件x >= lower & x < upper会漏掉等于max(x)的元素,导致这些元素标签为空。
  2. 分组逻辑不一致:原手动代码用ntile保证分组数量均等,但原函数用quantile计算分位数,和ntile的分组逻辑有差异,可能导致分组数量不均。
  3. 不支持分组数据:原函数是针对整个向量计算分位数,在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()

验证函数正确性

  1. 检查分组数量是否大致均等:
final %>% count(class3)
  1. 检查分组上下界是否匹配:
final %>% 
  group_by(class3) %>%
  summarise(min_val = min(var3), max_val = max(var3))
  1. 对比原手动代码的分组结果:
# 原手动代码结果
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:50:30