在R中,如何过滤列表列表?

时间:2011-08-01 23:44:19

标签: list r filter

根据手册,Filter适用于矢量,它恰好也适用于列表,例如:

z <- list(a=1, b=2, c=3)
Filter(function(i){
  z[[i]] > 1
}, z)
$b
[1] 2

$c
[1] 3

但是,它不适用于列表列表,例如:

z <- list(z1=list(a=1,b=2,c=3), z2=list(a=1,b=1,c=1), z3=list())
Filter(function(i){
  if(length(z[[i]])>0){
    if(z[[i]]$b > 1)
      TRUE
    else
      FALSE
  }
  else
    FALSE
}, z)
Error in z[[i]] : invalid subscript type 'list'

在不使用嵌套循环的情况下过滤列表列表的最佳方法是什么?它也可以是列表清单......

(我尝试使用嵌套lapply,但无法使其工作。)

第二个例子中的

编辑,这是我想要获得的:

list(z1=list(a=1,b=2,c=3))

即没有z $ z2,因为z $ z2 $ b&lt; 1,没有z $ z3,因为它是空的。

5 个答案:

答案 0 :(得分:3)

我认为你应该使用:

Filter(function(x){length(x)>0 && x[["b"]] > 1},z)

谓词(用于过滤z的函数)适用于z的元素,而不是它们的索引。

答案 1 :(得分:1)

我在你的问题之前从未使用Filter,所以这对早上的第一件事来说是一个很好的练习:)

至少有一些事情正在使你绊倒(我想)。

让我们从你的第一个简单的匿名函数开始,但让它独立,这样更容易阅读:

f <- function(i){
        z[[i]] > 1
     }

它应该向你跳出这个函数接受一个参数i,但它在函数中调用z。这不是很好的“功能性”编程:)

首先将该功能更改为:

f <- function(i){
        i > 1
     }

您会看到Filter实际上会针对列表列表运行:

 z <- list(z1=list(a=1,b=2,c=3), z2=list(a=1,b=1,c=1))
 Filter( f, z)

但它返回:

> Filter( f, z)
$z2
$z2$a
[1] 1

$z2$b
[1] 1

$z2$c
[1] 1


$<NA>
NULL

这不是你想要的。老实说,我不能理解为什么它会返回结果,也许有人可以向我解释。

当他说应该有一个递归的解决方案时,@ DWin正在咆哮着正确的树。我在递归函数上进行了第一次尝试,但你需要改进它:

fancyFilter <- function(f, x){
  if ( is.list( x[[1]] ) ) #only testing the first element... bad practice
    lapply( x, fancyFilter, f=f ) #recursion FTW!!
  else
    return( lapply(x, Filter, f=f ) )
}

fancyFilter查看传递给它的x的第一个元素,如果该元素是一个列表,它会在列表的每个元素上递归调用fancyFilter。但是如果元素#2不是列表呢?这是你应该测试的东西,并梳理出它对你来说是否重要。但是fancyFilter的结果似乎与你所追求的一样:

> fancyFilter(f, z)
$z1
$z1$a
numeric(0)

$z1$b
[1] 2

$z1$c
[1] 3


$z2
$z2$a
numeric(0)

$z2$b
numeric(0)

$z2$c
numeric(0)

您可能需要添加一些逻辑来清理输出,以便FALSE结果不会被{0}}骚扰。而且,很明显,我只使用了您的简单函数,而不是您在第二个示例中使用的更复杂的函数。

答案 2 :(得分:0)

这里没有对美的要求,也没有进行深度搜索:

z2 <- lapply(z, function(x){ if( "b" %in% names(x) && x[["b"]] >1 ) x else {}   } )
z2[unlist(lapply(z2, is.null))] <- NULL

> z2
$z1
$z1$a
[1] 1

$z1$b
[1] 2

$z1$c
[1] 3

编辑:此代码将遍历列表并组合具有'b'&gt;的节点1.正确标记节点需要一些工作。首先是一个嵌套更深的列表:

z <- list(z1=list(a=1,b=2,c=3), z2=list(a=1,b=1,c=1), z3=list(),
          z4 = list(z5=list(a=5,b=6,c=7), z6=list(a=7,b=8,c=9)))

checkbGT1 <- function(ll){ root <- list()
             for(i in seq_along(ll) ) {if ("b" %in% names(ll[[i]]) && ll[[i]]$b >1) {
                                 root <- c(root, ll[[i]]) 
                                 }else{ 
                                 if(  length(ll[[i]]) && is.list(ll[[i]]) ) 
                                    { root <- c(root, list(checkbGT1( ll[[i]] ))) }
                                          } 
                                       } 
                  return(root) }

答案 3 :(得分:0)

按键过滤子列表。 阅读有助于我的答案。

zall<-list(z1=list(list(key=1,b=2,c=3),list(key=2,b=3,c=4)))
zall
#> $z1
#> $z1[[1]]
#> $z1[[1]]$key
#> [1] 1
#> 
#> $z1[[1]]$b
#> [1] 2
#> 
#> $z1[[1]]$c
#> [1] 3
#> 
#> 
#> $z1[[2]]
#> $z1[[2]]$key
#> [1] 2
#> 
#> $z1[[2]]$b
#> [1] 3
#> 
#> $z1[[2]]$c
#> [1] 4
lapply(zall$z1, function(x){ x[intersect(names(x),"key")]  } )
#> [[1]]
#> [[1]]$key
#> [1] 1
#> 
#> 
#> [[2]]
#> [[2]]$key
#> [1] 2
lapply(zall$z1, function(x){ x[setdiff(names(x),"key")]  } )
#> [[1]]
#> [[1]]$b
#> [1] 2
#> 
#> [[1]]$c
#> [1] 3
#> 
#> 
#> [[2]]
#> [[2]]$b
#> [1] 3
#> 
#> [[2]]$c
#> [1] 4

答案 4 :(得分:0)

此问题的现代整洁解决方案是:

library(tidyverse)
z <- list(z1=list(a=1,b=2,c=3), z2=list(a=1,b=1,c=1), z3=list())

然后简单地:

tibble(disc = z, Names = names(z)) %>% 
  hoist(disc, c = "c") %>%
  filter(c == 3) %>%
  unnest_wider(disc) %>% 
  split(.$Names) %>% map(select, -Names) %>% 
  map(as.list)

请注意,这现在非常灵活,可以轻松地进行其他过滤,例如如果a == 1