运行 "apply" 非常大的数据框上的命令

Running "apply" command on a very large data frame

我在 R 中有一个尺寸为 15,000,000 x 140 的 tibble。大小约为 6 GB。

我想检查给定行的第 11-40 列中的任何列是否从特定列表开始。我想得到一个 1 和 0 的向量,然后是 15,000,000 长。

我可以使用以下方法做到这一点:

subResult <- apply(rawData[,11:40], c(1,2), function(x){substring(x,1,3) %in% c("295", "296", "297", "298", "299")})

result <- apply(subResult, 1, sum)

问题是这太慢了——仅第一行就需要 1 天多的时间。

有什么方法可以更快地做到这一点——也许直接通过 dplyr 或 data.table?

谢谢!

这里是数据的样本,只保留了第 11-40 列。

!> head(rawData)
 # A tibble: 6 x 30                                                                                                                                                                               
   X1    X2    X3    X4    X5    X6    X7    X8    X9    X10   X11   X12   X13
   <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
 1 39402 39451 3fv3i 19593 fk20 14p4  59304  329fj2 NA    NA    NA    NA    NA
 2 39422 f203ff vmio2  vo2493  19149 59833 13404 394034 43920  349304   59302 1934 34834
 3 3432f32 fe493  43943 H2344 53049  V602  3124  K148 K13  NA    NA    NA    NA
 # ... with 17 more variables: X14 <chr>, X15 <chr>, X16 <chr>, X17 <chr>,                                                                                                                         
 #   X18 <chr>, X19 <chr>, X20 <chr>, X21 <chr>, X22 <chr>, X23 <chr>,                                                                                                                             
 #   X24 <chr>, X25 <chr>, X26 <chr>, X27 <chr>, X28 <chr>, X29 <chr>, X30 <chr> 

根据描述,这可以通过 tidyverse

来完成
library(tidyverse)
rawData %>%
   select(11:40) %>% #select the columns
   #convert to logical columns
   mutate_all(funs(substring(.,1,3) %in% c("295", "296", "297", "298", "299"))) %>% 
   reduce('+') %>% #get the rowwise sum
   mutate(rawData, newcol = .) # assign a new column to the original data

或通过将 'data.frame' 转换为 'data.table' (setDT(rawData)),使用 data.table,在 .SDcols 中指定感兴趣的列,循环列,通过使用 OP 的条件将其转换为逻辑,Reduce 通过获取每一行的 sum 并将 (:=) 分配给 'newcol'

library(data.table)
setDT(rawData)[, newCol := Reduce('+', lapply(.SD, function(x) 
      substring(x, 1, 3) %chin% c("295", "296", "297", "298", "299"))), 
     .SDcols = 11:40]

尝试使用 Rcpp 包。

这是一个简单的 C++ 程序,它接受两个字符串向量,并检查第一个元素的 3 个字符是否与第二个元素相等。因此它将输出大小为长度(第一个向量)x 长度(第二个向量)的逻辑矩阵。

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
LogicalMatrix IndicatorMatrix(std::vector<std::string> target, std::vector<std::string> tocheck) {

  int nrows = target.size();
  int ncols = tocheck.size();

  LogicalMatrix ind(nrows, ncols);

  for(int r=0; r<nrows; r++) {
    for(int c=0; c<ncols; c++) {

      bool found = target[r].substr(0,3) == tocheck[c];
      ind(r,c) = found;

    }
  }

  return ind;

}

之后,您可以将此程序源代码化为 R 并使用您的 IndicatorMatrix 函数,就好像它是一个 R 函数对象一样。

library(Rcpp)
sourceCpp("C:/Users/Desktop/indicatorMatrix.cpp")

rep("123456", 15000000) -> x
df <- data.frame(x,x,x,x,x,x,x,x, stringsAsFactors=FALSE)
y <- c("123", "124", "345", "231", "675", "344", "222")


t1 <- Sys.time()
out <- lapply(1:length(df), function(col) {

  res <- IndicatorMatrix(unlist(df[,col]), y)
  res

})
t2 <- Sys.time()
t2-t1

程序在大约 100 秒内在 1500 万行的 8 列数据框中搜索了 8 个 3 个字符的字符串。所以这对你来说可能是正确的方向。

我的评论:

  • apply 将您的数据转换为矩阵
  • 数据框首先是一个列表,而不是矩阵
  • substring() 是矢量化函数(%in% 也是)

所以,我会这样做:

sapply(rawData[11:40], function(var) {
  substring(var, 1, 3) %in% c("295", "296", "297", "298", "299")
})

然后使用 rowSums() 而不是 apply(subResult, 1, sum)