R:有效地grep大data.frame行中的字符

jul*_*635 5 r dataframe

我有一个> 1M行长的字符串数据框:

>head(df)
     A    B     C     D
1   S1   S2    U1    U2
2   S1   S2    S2    S1
3   S2   S1    S1    S2
4   S1   M2    U1    S2
5   S1   S1    M2    M1
6   M2   M2    M1    M2
Run Code Online (Sandbox Code Playgroud)

我想确定存在特定字符的所有行(例如,“U”)。到目前为止,我发现的解决方案是有效的,但它们非常慢,例如:

matches <- apply(as.matrix(df), 1, function(x){ sum(grepl("U", x, perl=T)) > 0 })
Run Code Online (Sandbox Code Playgroud)

知道如何改进这个查询吗?谢谢!

Bro*_*ieG 5

编辑:更新地址评论:

以下也非常快(0.31 秒,甚至比以前更快):

rows <- which(
  rowSums(
    `dim<-`(grepl("U", as.matrix(df), fixed=TRUE), dim(df))
  ) > 0
)
Run Code Online (Sandbox Code Playgroud)

并产生与先前答案相同的结果。使用fixed=FALSE大约两倍的时间,但您的示例不需要这样做。

我们在这里所做的是通过应用grepl到矩阵来作弊,尽管我们真正关心的是df变成向量(矩阵就是这样),这as.matrix是实现此目的的更快方法之一。然后我们可以只运行一个grepl命令。最后,我们dim<-grepl向量结果转回矩阵,并用于rowSums检查哪些行匹配。

以下是这比您的版本快得多的原因:

  • 我们调用grepl一次,而不是像您那样调用一百万次,apply因为函数apply适用于每行调用一次;grepl是矢量化的,这意味着您希望尽量减少调用它的次数并利用矢量化
  • 我们使用rowSums而不是进行行匹配计数applyrowSums是一个更快的版本apply(x, 1, sum)(请参阅 文​​档?rowSums)。

以前的答案:

这是一个相对简单的解决方案,它在我的系统上运行 0.35 秒,用于 1MM 行 x 4 列数据帧:

rows <- which(rowSums(as.matrix(df) == "U") > 0)
Run Code Online (Sandbox Code Playgroud)

确认

df[head(rows), ]
Run Code Online (Sandbox Code Playgroud)

产生(每一行都有一个 U):

   a b c d
5  F B D U
8  R S U F
15 U L R P
20 U E E O
21 Y U D I
32 P F U H
Run Code Online (Sandbox Code Playgroud)

和数据:

set.seed(1)
df <- as.data.frame(
  `names<-`(
    replicate(4, sample(LETTERS, 1e6, rep=T), simplify=F),
    letters[1:4]
  )
)
Run Code Online (Sandbox Code Playgroud)