37

How do I remove the null elements from a list of lists, like below, in R:

lll <- list(list(NULL),list(1),list("a"))

The object I want would look like:

lll <- list(list(1),list("a"))

I saw a similar answer here: How can I remove an element from a list? but was not able to extend it from simple lists to a list of lists.

EDIT

Bad example above on my part. Both answers work on simpler case (above). What if list is like:

lll <- list(list(NULL),list(1,2,3),list("a","b","c"))

How to get:

lll <- list(list(1,2,3),list("a","b","c"))
zx8754
  • 52,746
  • 12
  • 114
  • 209
Chris
  • 3,401
  • 5
  • 33
  • 42

7 Answers7

30

This recursive solution has the virtue of working on even more deeply nested lists.

It's closely modeled on Gabor Grothendieck's answer to this quite similar question. My modification of that code is needed if the function is to also remove objects like list(NULL) (not the same as NULL), as you are wanting.

## A helper function that tests whether an object is either NULL _or_ 
## a list of NULLs
is.NullOb <- function(x) is.null(x) | all(sapply(x, is.null))

## Recursively step down into list, removing all such objects 
rmNullObs <- function(x) {
   x <- Filter(Negate(is.NullOb), x)
   lapply(x, function(x) if (is.list(x)) rmNullObs(x) else x)
}

rmNullObs(lll)
# [[1]]
# [[1]][[1]]
# [1] 1
# 
# 
# [[2]]
# [[2]][[1]]
# [1] "a"

Here is an example of its application to a more deeply nested list, on which the other currently proposed solutions variously fail.

LLLL <- list(lll)
rmNullObs(LLLL)
# [[1]]
# [[1]][[1]]
# [[1]][[1]][[1]]
# [[1]][[1]][[1]][[1]]
# [1] 1
# 
# 
# [[1]][[1]][[2]]
# [[1]][[1]][[2]][[1]]
# [1] "a"
Community
  • 1
  • 1
Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • This is a great function! – mrp Jan 08 '15 at 04:57
  • 1
    @mrp -- Thanks! Glad I was able to pass on this handy template to someone else. – Josh O'Brien Jan 08 '15 at 07:47
  • 1
    fyi, this probably behaves in ways one might not expect if your list elements are primarily data frame, since the `is.list` will capture and then unwind them. Simply adding an additional `&& is.data.frame(x)` should be enough I think. – joran Feb 27 '18 at 22:02
  • @joran -- Thanks. Guessing you mean `&& !is.data.frame(x)`, right? – Josh O'Brien Feb 27 '18 at 22:51
21

Here's an option using Filter and Negate combination

Filter(Negate(function(x) is.null(unlist(x))), lll)
# [[1]]
# [[1]][[1]]
# [1] 1
#
#
# [[2]]
# [[2]][[1]]
# [1] "a"
David Arenburg
  • 91,361
  • 17
  • 137
  • 196
  • 1
    I'm working with lists of S4 objects and this solution was the only one that worked, thanks! – Rafael Jul 27 '16 at 02:41
9

Using purrr

purrr::map(lll, ~ purrr::compact(.)) %>% purrr::keep(~length(.) != 0)
[[1]]
[[1]][[1]]
[1] 1

[[1]][[2]]
[1] 2

[[1]][[3]]
[1] 3


[[2]]
[[2]][[1]]
[1] "a"

[[2]][[2]]
[1] "b"

[[2]][[3]]
[1] "c"
Leni Ohnesorge
  • 716
  • 8
  • 19
7

For this particular example you can also use unlist with its recursive argument.

lll[!sapply(unlist(lll, recursive=FALSE), is.null)]
# [[1]]
# [[1]][[1]]
# [1] 1
#
#
# [[2]]
# [[2]][[1]]
# [1] "a"
Rich Scriven
  • 97,041
  • 11
  • 181
  • 245
6

Since you have lists in lists, you probably need to run l/sapply twice, like:

lll[!sapply(lll,sapply,is.null)]

#[[1]]
#[[1]][[1]]
#[1] 1
#
#
#[[2]]
#[[2]][[1]]
#[1] "a"
thelatemail
  • 91,185
  • 12
  • 128
  • 188
5

There is a new package rlist on CRAN, thanks to Kun Ren for making our life easier.

    list.clean(.data, fun = is.null, recursive = FALSE)

or for recursive removal of NULL:

    list.clean(.data, fun = is.null, recursive = TRUE)
not2qubit
  • 14,531
  • 8
  • 95
  • 135
Raminsu
  • 299
  • 3
  • 3
0

Quick fix on Josh O'Brien's solution. There's a bit of an issue with lists of functions

is.NullOb <- function(x) if(!(is.function(x))) is.null(x) | all(sapply(x, is.null)) else FALSE

## Recursively step down into list, removing all such objects 
rmNullObs <- function(x) {
  if(!(is.function(x))) {
    x = x[!(sapply(x, is.NullOb))]
    lapply(x, function(x) if (is.list(x)) rmNullObs(x) else x)
  }
}
bramtayl
  • 4,004
  • 2
  • 11
  • 18