在R中基于另一数据框对数据框多列高效计算众数占比
高效计算数据框各列众数及占比(dplyr方案)
需求说明
我有一个包含字符型、字符型数值的大型数据框,需要不使用循环快速计算每列的众数及其在该列中的占比。之前用自定义函数得到了各列众数,但用lapply/sapply调用占比计算函数时速度很慢,希望用dplyr包的mutate或summarize实现高效解决方案。
示例数据
df <- data.frame( id = paste0("SubID_", 1:100), score = as.character(sample(1:100, 100, replace=TRUE)), dob = sample(seq(as.Date('1999/01/01'), as.Date('2000/01/01'), by="day"), 100) )
原有实现及问题
自定义众数函数
用以下函数获取了各列众数并存入数据框f:
# 自定义众数计算函数 mode <- function(v) { uniqv <- unique(v) uniqv[which.max(tabulate(match(v, uniqv)))] } # 获取各列众数 f <- data.frame(sapply(df, mode))
得到的结果:
> f sapply.df..mode. id SubID_1 score 84 dob 10739 # 日期列被转成数值,类型丢失
占比计算的问题
尝试编写frequencycounter函数计算占比,但用lapply/sapply调用时速度很慢:
frequencycounter <- function(x,df,f){ sum(str_count(df[,x], f[x,]))/length(df[,x]) } # 调用方式(速度慢) lapply(colnames(df),frequencycounter,df=df,f=f) sapply(colnames(df),frequencycounter,df=df,f=f)
问题在于:str_count是字符串匹配操作,对数值、日期类型的列不适用,且逐个列循环处理在大型数据框上效率极低。
dplyr高效解决方案
优化后的众数函数
先优化众数函数,保留原列的数据类型(比如日期列不会被转成数值):
get_mode <- function(v) { uniq_v <- unique(v) uniq_v[which.max(tabulate(match(v, uniq_v)))] }
一次性计算众数及占比
用dplyr的across实现向量化操作,一次性完成所有列的众数和占比计算:
library(dplyr) library(tidyr) # 计算各列众数及占比 mode_stats <- df %>% summarize( across( .cols = everything(), # 对所有列处理 .fns = list( mode = ~get_mode(.), # 计算众数 mode_pct = ~mean(. == get_mode(.)) # 计算众数占比,mean(逻辑值)等价于占比 ) ) ) # 整理结果为长格式(可选,更易读) mode_stats <- mode_stats %>% pivot_longer( everything(), names_to = c("column", ".value"), names_sep = "_" )
结果展示
运行后得到清晰的统计结果:
> print(mode_stats) # # A tibble: 3 × 3 # column mode mode_pct # <chr> <chr> <dbl> # 1 id SubID_1 0.01 # 2 score 84 0.02 # 3 dob 1999-05-15 0.01
方案优势
- 向量化操作:dplyr的
across底层是向量化处理,比循环或lapply逐个列处理快得多,尤其适合大型数据框。 - 类型保留:优化后的
get_mode函数会保留原列的数据类型(比如日期列显示为日期格式,而非数值)。 - 高效占比计算:
mean(. == get_mode(.))直接对整列进行逻辑比较,再取均值得到占比,避免了字符串匹配的开销,且比sum(...) / length(...)更简洁高效。
内容的提问来源于stack exchange,提问作者TDeramus
相关产品推荐
相关产品推荐

