如何按类别裁剪data frame指定百分比的首尾数据?R语言疑问
问题描述
数据准备
我有如下数据框:
set.seed(3994) val <- round(runif(n=30, min = 5, max= 300), digits=0) cat <- rep(c("A", "B", "C"), each= 10) date <- as.Date(sample(seq(as.Date('2000/01/01'), as.Date('2020/01/01'), by="day"), 30)) df <- data.frame(val, cat, date) df <- df %>% arrange(cat, val)
需求
希望按cat列的类别,分别裁剪每个类别数据中基于val列排序后的顶部X%和底部X%数据,例如移除类别"A"、"B"、"C"各自的顶部2%和底部2%数据。
自行编写的代码
我编写了如下代码:
trimTopBottomByCategory <- function(dataframe, category_col, numeric_col, date_column, x) { trimmed_dataframes <- list() categories <- unique(dataframe[[category_col]]) for (category in categories) { subset_df <- dataframe[dataframe[[category_col]] == category, ] n <- nrow(subset_df) num_to_trim <- ceiling(x / 100 * n) sorted_subset <- subset_df[order(subset_df[[numeric_col]]), ] trimmed_dataframes[[category]] <- sorted_subset[(num_to_trim + 1):(n - num_to_trim), ] } trimmed_combined <- do.call(rbind, trimmed_dataframes) return(trimmed_combined <- trimmed_combined %>% arrange(category_col, date_column)) }
疑问
- 我认为自己的代码能实现需求,但想知道R中是否有现成的方法可以完成该操作?
- 额外问题:我不理解为什么最终数据的
date列没有被排序?
回答
1. R中现成的实现方法
当然有,用dplyr的分组操作就能更简洁地完成,不用写循环:
方法一:基于百分位数排名裁剪
library(dplyr) trim_by_group <- function(df, group_col, num_col, x) { df %>% group_by({{group_col}}) %>% mutate(rank = percent_rank({{num_col}})) %>% filter(rank > x/100 & rank < 1 - x/100) %>% select(-rank) %>% ungroup() }
调用示例:trimmed_df <- trim_by_group(df, cat, val, 2)
方法二:和你原逻辑一致(按数量裁剪)
如果要和你原代码一样用ceiling计算每个组需要裁剪的行数,代码可以写成:
trim_by_group <- function(df, group_col, num_col, x) { df %>% group_by({{group_col}}) %>% arrange({{num_col}}) %>% slice((ceiling(x/100*n())+1):(n() - ceiling(x/100*n()))) %>% ungroup() }
大数据集推荐:data.table实现
如果处理的是大数据集,data.table的效率更高:
library(data.table) setDT(df) trimmed_df <- df[, .SD[(ceiling(0.02*.N)+1):(.N - ceiling(0.02*.N))], by = cat]
2. date列未排序的原因及解决方法
问题出在你最后arrange的参数上:你传入的category_col和date_column是字符串变量,但dplyr的arrange默认识别的是直接写的列名,不是字符串。你原代码相当于在尝试对名为category_col和date_column的列排序,而不是你实际传入的cat和date,所以自然不会按date列排序。
修改方法很简单,用.data代词引用字符串形式的列名即可:
return(trimmed_combined %>% arrange(.data[[category_col]], .data[[date_column]]))
内容的提问来源于stack exchange,提问作者Joe the Second
相关产品推荐
相关产品推荐

