如何在使用ntile()对堆叠数据按类别分5组时排除首尾10%数据并标记为NA
这是个很实用的需求!我们可以通过先界定每个类别需要排除的首尾10%数据范围,再对中间数据单独计算分位数分组的方式实现,同时保留所有原始数据并标记对应分组。下面给你两种不同思路的实现方案,你可以根据实际需求选择:
方案1:基于数值分位数排除首尾10%
这种方法是通过计算每个类别数值的10%和90%分位数,把落在分位数之外的数据标记为NA,中间区间的数据再分5组:
library(tidyverse) # 生成示例数据(用pivot_longer替代melt,更符合tidyverse语法) set.seed(123) # 设置随机种子保证结果可复现 r1 <- rnorm(10, 0, 1) r2 <- rnorm(10, 2, 4) df <- cbind(r1, r2) %>% as.data.frame() %>% pivot_longer(cols = everything(), names_to = "Var2", values_to = "value") # 处理分组 df_processed <- df %>% group_by(Var2) %>% mutate( # 计算每个组的10%和90%分位数(可通过type参数调整分位数计算规则) p10 = quantile(value, 0.1, type = 1), p90 = quantile(value, 0.9, type = 1), # 标记是否属于中间80%的数据 is_mid = value > p10 & value < p90, # 对中间数据计算分位数分组,首尾数据设为NA group = case_when( !is_mid ~ NA_integer_, # 提取中间数据计算ntile,再通过match映射回原数据位置 TRUE ~ ntile(value[is_mid], 5)[match(value, value[is_mid])] ) ) %>% select(-p10, -p90, -is_mid) # 移除临时辅助列 # 查看结果 print(df_processed)
关键逻辑说明:
quantile()的type参数可以调整分位数的计算方式(默认是type=7),你可以根据业务需求选择合适的规则match(value, value[is_mid])用来保证中间数据的分组结果能正确对应到原数据的位置,避免顺序混乱
方案2:基于数据数量比例排除首尾10%
如果你的需求是严格排除每个类别中数量占比10%的首尾数据(比如每组10条数据就各排除1条),而不是基于数值分位数,可以用排序后的位置来界定:
df_processed <- df %>% group_by(Var2) %>% mutate( total = n(), # 对每个组的数值排序,得到位次 value_rank = row_number(value), # 计算首尾需要排除的数量(用floor取整,也可以用ceiling根据需求调整) exclude_bottom = floor(total * 0.1), exclude_top = floor(total * 0.1), # 分配分组 group = case_when( # 首尾部分标记为NA value_rank <= exclude_bottom | value_rank > total - exclude_top ~ NA_integer_, # 中间部分计算ntile分组 TRUE ~ ntile(value[value_rank > exclude_bottom & value_rank <= total - exclude_top], 5)[match(value_rank, value_rank[value_rank > exclude_bottom & value_rank <= total - exclude_top])] ) ) %>% select(-total, -value_rank, -exclude_bottom, -exclude_top) # 移除临时列
关键逻辑说明:
row_number(value)会对每个组的数值从小到大排序,生成位次floor(total*0.1)保证按数量比例排除,比如10条数据排除1条,25条数据排除2条(25*10%=2.5,floor后是2)
两种方案都能满足你的需求,核心都是通过分组计算筛选条件,再结合case_when和ntile实现分组标记,同时保留所有原始数据。
内容的提问来源于stack exchange,提问作者Billsyd
相关产品推荐
相关产品推荐

