【问题标题】:Finding rows containing a value (or values) in any column在任何列中查找包含一个(或多个值)的行
【发布时间】:2015-01-30 10:13:04
【问题描述】:

假设我们有一个表“数据”,其中包含几列中的字符串。我们希望找到包含某个值的所有行的索引,或者更好的是,几个值之一。但是,该列是未知的。

我现在做的是:

apply(df, 2, function(x) which(x == "M017"))

df =

1 04.10.2009 01:24:51   M017  <NA>  <NA>    NA
2 04.10.2009 01:24:53   M018  <NA>  <NA>    NA
3 04.10.2009 01:24:54   M051  <NA>  <NA>    NA
4 04.10.2009 01:25:06   <NA>  M016  <NA>    NA
5 04.10.2009 01:25:07   <NA>  M015  <NA>    NA
6 04.10.2009 01:26:07   <NA>  M017  <NA>    NA
7 04.10.2009 01:26:27   <NA>  M017  <NA>    NA
8 04.10.2009 01:27:23   <NA>  M017  <NA>    NA
9 04.10.2009 01:27:30   <NA>  M017  <NA>    NA
10 04.10.2009 01:27:32   M017  <NA>  <NA>    NA
11 04.10.2009 01:27:34   M051  <NA>  <NA>    NA

如果我们试图找到多个值,这也有效:

apply(df, 2, function(x) which(x %in% c("M017", "M018")))

结果是:

$`1`
integer(0)

$`2`
[1]  1  2 20

$`3`
[1] 16 17 18 19

$`4`
integer(0)

$`5`
integer(0)

但是,处理列表的结果列表相当繁琐。

有没有更有效的方法来查找在 ANY 列中包含一个值(或更多值)的行?

【问题讨论】:

  • 你可以试试which(df == "M017"), arr.ind=TRUE) 它会给出行、列索引,你可以从中提取行。
  • 对于多个值,我认为apply 基于家庭(sapply,lapply)的解决方案可能会更好。一种选择是矢量化。 IE。如果v1 &lt;- c('M017', 'M018'); which(Vectorize(function(x) x %in% v1)(df), arr.ind=TRUE)
  • 谢谢,akrun,我删除了我的评论,因为你在我发布之前回答了我的问题。
  • 您是否有很多要检查的值?如果不是,您可以轻松地将整个事物矢量化为 rowSums(df == "M017" | df == "M018", na.rm = TRUE) &gt; 0L 之类的东西,并避免 apply 一起循环。
  • @DavidArenburg 如果只有两个值,那将是我的选择。

标签: r


【解决方案1】:

怎么样

apply(df, 1, function(r) any(r %in% c("M017", "M018")))

如果第 i 行包含其中一个值,则第 i 个元素将为 TRUE,否则为 FALSE。或者,如果您只需要行号,请将上述语句括在 which(...) 中。

【讨论】:

  • 需要注意的是,如果要找到的值很少,@DavidArenburg 建议rowSums(df == "M017" | df == "M018", na.rm = TRUE) &gt; 0L 会更有效。
  • 是否可以将列表中的行作为输出?而不是 apply 的原始值
【解决方案2】:

这是一个dplyr 选项:

library(dplyr)

# across all columns:
df %>% filter_all(any_vars(. %in% c('M017', 'M018')))

# or in only select columns:
df %>% filter_at(vars(col1, col2), any_vars(. %in% c('M017', 'M018')))                                                                                                     

【讨论】:

  • 您知道如何返回满足您提供的filter_at() 语句条件的布尔值吗?
  • 回答 here 给任何感兴趣的人。
【解决方案3】:

如果您想在向量中查找具有任何值的rows,一种选择是循环向量 (lapply(v1,..)),使用 (==) 创建 (TRUE/FALSE) 的逻辑索引)。使用 Reduce 和 OR (|) 通过检查相应的元素将列表缩减为单个逻辑矩阵。对行求和 (rowSums),双重否定 (!!) 得到任何匹配的行。

indx1 <- !!rowSums(Reduce(`|`, lapply(v1, `==`, df)), na.rm=TRUE)

或者向量化并使用which 和arr.ind=TRUE 获取行索引

indx2 <- unique(which(Vectorize(function(x) x %in% v1)(df),
                                     arr.ind=TRUE)[,1])

基准测试

我没有使用@kristang 的解决方案,因为它给了我错误。基于1000x500 矩阵,@konvas 的解决方案是最有效的(迄今为止)。但是,如果行数增加,这可能会有所不同

val <- paste0('M0', 1:1000)
set.seed(24)
df1 <- as.data.frame(matrix(sample(c(val, NA), 1000*500, 
  replace=TRUE), ncol=500), stringsAsFactors=FALSE) 
set.seed(356)
v1 <- sample(val, 200, replace=FALSE)

 konvas <- function() {apply(df1, 1, function(r) any(r %in% v1))}
 akrun1 <- function() {!!rowSums(Reduce(`|`, lapply(v1, `==`, df1)),
               na.rm=TRUE)}
 akrun2 <- function() {unique(which(Vectorize(function(x) x %in% 
              v1)(df1),arr.ind=TRUE)[,1])}


 library(microbenchmark)
 microbenchmark(konvas(), akrun1(), akrun2(), unit='relative', times=20L)
 #Unit: relative
 #   expr       min         lq       mean     median         uq      max   neval
 # konvas()   1.00000   1.000000   1.000000   1.000000   1.000000  1.00000    20
 # akrun1() 160.08749 147.642721 125.085200 134.491722 151.454441 52.22737    20
 # akrun2()   5.85611   5.641451   4.676836   5.330067   5.269937  2.22255    20
 # cld
 #  a 
 #  b
 #  a 

对于ncol = 10,结果略有不同:

expr       min        lq     mean    median        uq       max    neval
 konvas()  3.116722  3.081584  2.90660  2.983618  2.998343  2.394908    20
 akrun1() 27.587827 26.554422 22.91664 23.628950 21.892466 18.305376    20
 akrun2()  1.000000  1.000000  1.00000  1.000000  1.000000  1.000000    20

数据

 v1 <- c('M017', 'M018')
 df <- structure(list(datetime = c("04.10.2009 01:24:51",
"04.10.2009 01:24:53", 
"04.10.2009 01:24:54", "04.10.2009 01:25:06", "04.10.2009 01:25:07", 
"04.10.2009 01:26:07", "04.10.2009 01:26:27", "04.10.2009 01:27:23", 
"04.10.2009 01:27:30", "04.10.2009 01:27:32", "04.10.2009 01:27:34"
), col1 = c("M017", "M018", "M051", "<NA>", "<NA>", "<NA>", "<NA>", 
"<NA>", "<NA>", "M017", "M051"), col2 = c("<NA>", "<NA>", "<NA>", 
"M016", "M015", "M017", "M017", "M017", "M017", "<NA>", "<NA>"
), col3 = c("<NA>", "<NA>", "<NA>", "<NA>", "<NA>", "<NA>", "<NA>", 
"<NA>", "<NA>", "<NA>", "<NA>"), col4 = c(NA, NA, NA, NA, NA, 
NA, NA, NA, NA, NA, NA)), .Names = c("datetime", "col1", "col2", 
"col3", "col4"), class = "data.frame", row.names = c("1", "2", 
"3", "4", "5", "6", "7", "8", "9", "10", "11"))

【讨论】:

  • 谢谢。我为与我的更相似的表添加了基准测试结果(nrow = 10)。结果有些不同。
  • @sebowski 是的,正如我在帖子前面提到的,基准会根据v1、nrow、ncol 等的长度而有所不同。当v1 只有两个值时,以及列数较少的 1e7 数据集,apply 可能效率较低。
  • 我还用 4 个和 10 个字符串运行了测试以找到 (v1),结果几乎与 ncol = 10 相同。
  • @akrun,谢谢你的帖子!我有一个类似的要求,我必须在数据框中创建一个具有与向量匹配的值的新列。我正在使用 indx2,因为我能够比 indx1 理解这段代码(仍在学习 R)。我想检查现在如何在创建新列时使用行和列的返回值来获取各自的值。因此,并非所有行都满足它们应该为空白或 0 的条件。请您提出建议。谢谢!!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
  • 2015-12-21
  • 1970-01-01
  • 2018-05-12
相关资源
最近更新 更多