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.)

Maël
  • 45,206
  • 3
  • 29
  • 67
Josh
  • 1,248
  • 12
  • 25

2 Answers2

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".

deschen
  • 10,012
  • 3
  • 27
  • 50
  • 2
    For this particular case, even `rrapply(d, classes = c("numeric", "data.frame"), how = "flatten")` would work. – tmfmnk Apr 29 '22 at 10:27
  • True. I didn't even try to define several classes. thanks for the hint. – deschen Apr 29 '22 at 10:32
  • 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. – Josh Apr 29 '22 at 12:57
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
Maël
  • 45,206
  • 3
  • 29
  • 67
  • 1
    Should use `FALSE`, not `F`, for safety. – user2554330 Apr 29 '22 at 11:45
  • 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. – Josh Apr 29 '22 at 12:51
  • 1
    Essentially, it's the same principle, yes, but generalized to lists of any depth. – Maël Apr 29 '22 at 13:02
  • Ah good point. Doesn't help me in my particular case (nesting will only go 1-deep) but others may find this very useful! – Josh Apr 29 '22 at 13:29