R语言如何高效计算x,y结构粒径数据框各密度列的中位数
问题背景
现有存储颗粒粒径分布的数据框:第一列Size存粒径值,其余每列存对应粒径下的颗粒计数/密度值,需要逐列计算粒径中位数。
最初的实现逻辑是把每个粒径按对应计数重复展开成完整的观测向量,再调median()计算,逻辑可行,但处理1200行规模的数据时运行速度极慢。
原测试数据与慢实现代码如下:
df <- data.frame(Size = c(1:100), val1 = sample(0:9,100,replace = TRUE,), val2 = sample(0:9,100,replace = TRUE)) get.median <- function(dataset){ results <- list() for(col in colnames(dataset)[2:ncol(dataset)]){ col.results <- c() for(i in 1:nrow(dataset)){ size <- dataset[i,"Size"] count <- dataset[i,col] out <- rep(size,count) col.results <- c(col.results,out) } med <- median(col.results) results <- append(results,med) } return(results) } get.median(df)
高效解决方法
原方法慢的核心原因
- 内层循环反复用
c()拼接向量,R中这种动态扩容向量的操作每次都会重新拷贝内存中的全量数据,当展开后的向量长度达到几万、几十万量级时,时间开销会非线性暴涨 - 把频数表展开成单个观测的操作本身是冗余的,完全不需要做这一步
优化逻辑
对于已经按粒径从小到大排序的频数分布表,直接通过累计频数定位中位数位置即可,计算结果和展开向量的方法完全一致:
- 对单个计数列,先计算所有颗粒的总计数N,中位数位置为
(N + 1) / 2,和R原生median()的计算规则对齐 - 计算该列的累计频数,找到第一个累计频数大于等于中位数位置对应的粒径值,即为该列的中位数
- 全程使用向量化运算,没有冗余的内存拷贝,速度比原实现高两个数量级以上
优化后代码
get_median_fast <- function(dataset) { # 先确保数据按粒径升序排列,未排序时自动排序 dataset <- dataset[order(dataset$Size), ] size_vals <- dataset$Size count_data <- dataset[, -1, drop = FALSE] res <- lapply(count_data, function(cnt) { total_cnt <- sum(cnt) # 列计数全为0时直接返回NA if (total_cnt == 0) return(NA_real_) med_pos <- (total_cnt + 1) / 2 cum_cnt <- cumsum(cnt) hit_idx <- which(cum_cnt >= med_pos)[1] size_vals[hit_idx] }) return(res) }
可以通过以下代码验证结果和原函数完全一致:
all.equal(get.median(df), get_median_fast(df)) # 输出TRUE即结果完全匹配
性能表现
用1200行、每列计数范围0-100的测试数据测算,原实现可能需要数秒到十几秒(总计数越高耗时越长),优化后的函数运行时间基本在1毫秒以内,哪怕数据量涨到十万行也能秒出结果。如果处理的列数特别多,还可以把lapply换成vapply直接返回数值向量,省去列表转向量的额外开销。
内容的提问来源于stack exchange,提问作者Csenger Kovácsházi
相关产品推荐
相关产品推荐

