flatten a list of lists without recursive=FALSE
Asked Answered
T

2

6

I have a list that contains some other lists, but also contains additional objects that aren't lists such as data frames. I want to flatten this to a single list, but the usual advice (see How to flatten a list of lists? or converting a list of lists in a single list) of unlist(..., recursive = FALSE) won't work because it operates on the data.frame as well.

> d <- list(list(a = 1, b = 2), c = data.frame(1:4))
> d
[[1]]
[[1]]$a
[1] 1

[[1]]$b
[1] 2


$c
  X1.4
1    1
2    2
3    3
4    4
> unlist(d, recursive = FALSE)
$a
[1] 1

$b
[1] 2

$c.X1.4
[1] 1 2 3 4

The expected result is that $c will retain the same data.frame structure.

The only solution I have so far is to add an additional layer of lists to all non-lists object before unlisting but its a very unelegant solution.

> unlist(lapply(d, function(x) if(!inherits(x, "list")) list(x) else x), recursive = FALSE)
$a
[1] 1

$b
[1] 2

$c
  X1.4
1    1
2    2
3    3
4    4

This works, but does anyone have a more direct idea? (I'd greatly prefer a base R solution, not tidyverse.)

Tartaric answered 29/4, 2022 at 10:7 Comment(0)
E
4

Admittedly, not necessarily "elegant", but still:

library(rrapply)

c(rrapply(rrapply(d, classes = "list", how = "flatten"), how = "flatten"),
  rrapply(d, f = as.data.frame, classes = "data.frame", how = "flatten"))

$a
[1] 1

$b
[1] 2

$c
  X1.4
1    1
2    2
3    3
4    4

As @tmfmnk mentioned in the comments, in your particular case, even this would work:

rrapply(d, classes = c("numeric", "data.frame"), how = "flatten")

So you basically define what classes should not be flattened further. This particular solution I'd then indeed call "elegant".

Espino answered 29/4, 2022 at 10:24 Comment(3)
For this particular case, even rrapply(d, classes = c("numeric", "data.frame"), how = "flatten") would work.Exobiology
True. I didn't even try to define several classes. thanks for the hint.Espino
Thanks - I'd not come across rrapply before. This looks interesting but I'd prefer to exclude non-base packages, plus both of these are quite a bit slower than my solution.Tartaric
M
3

You can use a recursive function (inspired by this answer).

flattenMixed <- function(x) {
  if (is.data.frame(x)) return(list(x))
  if (!is.list(x)) return(x)
  unlist(lapply(x, flattenMixed), FALSE)
}

flattenMixed(d)

$a
[1] 1

$b
[1] 2

$c
  X1.4
1    1
2    2
3    3
4    4
Model answered 29/4, 2022 at 10:20 Comment(4)
Should use FALSE, not F, for safety.Awoke
This is basically doing the exact same thing as my attempt; adding lists then removing them, right? It also is slower unfortunately, as tested by microbenchmark.Tartaric
Essentially, it's the same principle, yes, but generalized to lists of any depth.Dildo
Ah good point. Doesn't help me in my particular case (nesting will only go 1-deep) but others may find this very useful!Tartaric

© 2022 - 2025 — McMap. All rights reserved.