Arthur Yip
Arthur Yip

Reputation: 6210

how to remove elements in list of lists of nested lists?

How can I go from

x <- list(p1 = list(type='A',score=list(c1=10,c2=8,c3=data.frame(a=1, b=3, c=5))),
       p2 = list(type='B',score=list(c1=9,c2=9,c3=data.frame(a=2, b=2))),
       p3 = list(type='B',score=list(c1=9,c2=7,c3=data.frame(a=2, b=2))))

to a list without the "c3" elements that are data frames?

Preferably in a tidyverse-friendly way or something I can put in the middle of a pipeline.

I've already tried list.remove, nested lapply, rapply, Filter, but can't seem to get them to work... and I don't want to unlist my nested list structure.

(Edit: sorry, I had a typo in the sample data in my original question (see below), but great if your solution works in both cases!)

x <- list(p1 = list(type='A',score=list(c1=10,c2=8,c3=data.frame(a=1, b=3, c=5))),
       p2 = list(type='B',score=list(c1=9,c2=9,c3=data.frame(a=2, b=2)),
       p3 = list(type='B',score=list(c1=9,c2=7,c3=data.frame(a=2, b=2)))))

Upvotes: 3

Views: 2178

Answers (3)

Joris C.
Joris C.

Reputation: 6234

Here is another alternative using rrapply() in the rrapply-package that works for arbitrary levels of nesting:

library(rrapply)

x1 <- rrapply(x, condition = function(x, .xparents) !any(.xparents == "c3"), how = "prune")
str(x1)  
#> List of 3
#>  $ p1:List of 2
#>   ..$ type : chr "A"
#>   ..$ score:List of 2
#>   .. ..$ c1: num 10
#>   .. ..$ c2: num 8
#>  $ p2:List of 2
#>   ..$ type : chr "B"
#>   ..$ score:List of 2
#>   .. ..$ c1: num 9
#>   .. ..$ c2: num 9
#>  $ p3:List of 2
#>   ..$ type : chr "B"
#>   ..$ score:List of 2
#>   .. ..$ c1: num 9
#>   .. ..$ c2: num 7

Upvotes: 2

Calum You
Calum You

Reputation: 15062

This is the right scenario to use modify_depth, which functions as a shortcut for chains of modify to access deep nested lists. modify has an advantage over map in this problem because it will preserve the type of the input instead of coercing everything to lists, which may be relevant if you have vector elements of your list structure.

Using your given input (with a p3 element inside rather than on the same level as p2), the dataframe elements at the second and third levels are discarded as below. In order to search all levels of the nested list, we can set up a while loop to iterate through the levels, discarding dataframes as we go. We need .ragged = TRUE to deal with errors with list depth. This version searches bottom up but you could change it to search top down as well.

library(tidyverse)
x <- list(
  p1 = list(type = "A", score = list(c1 = 10, c2 = 8, c3 = data.frame(a = 1, b = 3, c = 5))),
  p2 = list(
    type = "B", score = list(c1 = 9, c2 = 9, c3 = data.frame(a = 2, b = 2)),
    p3 = list(type = "B", score = list(c1 = 9, c2 = 7, c3 = data.frame(a = 2, b = 2)))
  )
)

remove_dataframes <- function(input_list) {
  current_list <- input_list
  current_depth <- vec_depth(current_list)
  # current_depth <- max_depth
  while (current_depth > 1) {
    current_list <- modify_depth(
      .x = current_list,
      .depth = current_depth,
      .f = ~ discard(., is.data.frame),
      .ragged = TRUE
    )
  current_depth <- current_depth - 1
  }
  return(current_list)
}

x %>%
  remove_dataframes %>%
  glimpse
#> List of 2
#>  $ p1:List of 2
#>   ..$ type : chr "A"
#>   ..$ score:List of 2
#>   .. ..$ c1: num 10
#>   .. ..$ c2: num 8
#>  $ p2:List of 3
#>   ..$ type : chr "B"
#>   ..$ score:List of 2
#>   .. ..$ c1: num 9
#>   .. ..$ c2: num 9
#>   ..$ p3   :List of 2
#>   .. ..$ type : chr "B"
#>   .. ..$ score:List of 2

Created on 2019-02-20 by the reprex package (v0.2.1)

Upvotes: 2

Onyambu
Onyambu

Reputation: 79208

You can write your own function to do this:

check = function(x,name){
  m = names(x)%in% name
  x = if(any(m)) x[!m] else x
  if(is.list(x)) sapply(x,check,name)
  else x
}

dput(check(x,'c3'))
list(p1 = list(type = "A", score = list(c1 = 10, c2 = 8)), p2 = list(
    type = "B", score = list(c1 = 9, c2 = 9), p3 = list(type = "B", 
        score = list(c1 = 9, c2 = 7))))

This is also vecotized in that it can discard all that you need. eg try check(x,c('c1','c3'))

Upvotes: 3

Related Questions