R语言逐行统计数据框值出现次数过慢,求高效优化方案
高效统计R语言数据框每行特定值出现次数的方法
嘿,我来帮你解决这个大数据量下的统计效率问题!先理清楚你的需求和当前的困境:
你的场景
你有一个类似这样的数据框:
# 输入数据框示例 df <- data.frame( a = c(1, 0, -1), b = c(1, -1, 0), c = c(0, -1, NA), d = c(-1, 1, NA), e = c(NA, NA, 1) )
打印出来是这样的:
a b c d e 1 1 1 0 -1 NA 2 0 -1 -1 1 NA 3 -1 0 NA NA 1
你需要给每行统计-1、0、1和NA的出现次数,最终得到这样的结果:
a b c d e count.-1 count.0 count.1 count.NA 1 1 1 0 -1 NA 1 1 2 1 2 0 -1 -1 1 NA 2 1 1 1 3 -1 0 NA NA 1 0 1 2 2
你现在用的是purrr::by_row逐行处理的方法,代码如下:
library(dplyr) library(purrr) df = df %>% by_row( ..f = function(x) { sum(is.na(x[1:8])) }, .to = "count_na", .collate = "cols" ) %>% by_row( ..f = function(x) { sum(x[1:8] == 1, na.rm = T) }, .to = "count_positive", .collate = "cols" ) %>% by_row( ..f = function(x) { sum(x[1:8] == -1, na.rm = T) }, .to = "count_negative", .collate = "cols" ) %>% by_row( ..f = function(x) { sum(x[1:8] == 0, na.rm = T) }, .to = "count_neutral", .collate = "cols" )
但处理500万行数据时耗时超3小时,这显然是因为逐行循环的开销太大了,接下来给你两个高效的替代方案:
方案1:用dplyr的向量化rowSums(快速且易读)
rowSums是R原生的向量化操作,直接对整个列维度计算,完全避免了逐行循环的开销,速度会快很多:
library(dplyr) # 统计所有列的情况 df_result <- df %>% mutate( count.-1 = rowSums(. == -1, na.rm = TRUE), count.0 = rowSums(. == 0, na.rm = TRUE), count.1 = rowSums(. == 1, na.rm = TRUE), count.NA = rowSums(is.na(.)) ) # 如果只需要统计前8列(和你原代码一致),可以指定列范围 df_result <- df %>% mutate( count.-1 = rowSums(select(., 1:8) == -1, na.rm = TRUE), count.0 = rowSums(select(., 1:8) == 0, na.rm = TRUE), count.1 = rowSums(select(., 1:8) == 1, na.rm = TRUE), count.NA = rowSums(is.na(select(., 1:8))) )
方案2:用data.table(超大数据量的最优选择)
如果你的数据量真的达到500万行,data.table是更好的选择——它的底层是C语言实现的,操作效率比普通dplyr还要高一个量级:
library(data.table) # 先把普通data.frame转成data.table setDT(df) # 统计所有列的情况 df_stats <- df[, .( count.-1 = rowSums(.SD == -1, na.rm = TRUE), count.0 = rowSums(.SD == 0, na.rm = TRUE), count.1 = rowSums(.SD == 1, na.rm = TRUE), count.NA = rowSums(is.na(.SD)) )] # 合并原数据和统计结果 df_result <- cbind(df, df_stats) # 如果只统计前8列,指定.SDcols参数 df_stats <- df[, .( count.-1 = rowSums(.SD == -1, na.rm = TRUE), count.0 = rowSums(.SD == 0, na.rm = TRUE), count.1 = rowSums(.SD == 1, na.rm = TRUE), count.NA = rowSums(is.na(.SD)) ), .SDcols = 1:8] df_result <- cbind(df, df_stats)
为什么原方法这么慢?
by_row本质是在做逐行循环,每一行都要单独调用一次函数,这种“循环+函数调用”的模式在R里是出了名的低效,尤其是数据量到百万级的时候,累计的开销会非常夸张。而上面的两种方案都是向量化操作,直接对整个数据集的列进行批量计算,没有逐行的额外开销,速度能提升几十甚至上百倍。
内容的提问来源于stack exchange,提问作者R.vW
相关产品推荐
相关产品推荐

