3

is there a simple way to unlist a list and keep the names of one level as a new variable?

Working example:

# Input list
my_list <- list(Ticker1 = list(date = seq.Date(as.Date('2021-01-01'), as.Date('2021-01-10'), by = 'day'), Value = 1:10),
                Ticker2 = list(date = seq.Date(as.Date('2021-01-01'), as.Date('2021-01-10'), by = 'day'), Value = 11:20),
                Ticker3 = list(date = seq.Date(as.Date('2021-01-01'), as.Date('2021-01-10'), by = 'day'), Value = 21:30))

# Desired data frame
my_df_goal <- as.data.frame(list(Ticker = c(rep("Ticker1", 10), rep("Ticker2", 10), rep("Ticker3", 10)),
                                 date = rep(seq.Date(as.Date('2021-01-01'), as.Date('2021-01-10'), by = 'day'), 3),
                                 Value = 1:30))

There are similar questions regarding unlisting on stackoverflow, but I wasn't able to solve my problem.

One way would probably be

my_df <- as.data.frame(rlist::list.flatten(my_list))

# Same result:
my_df2 <- as.data.frame(unlist(my_list,recursive = F))

#Edit: just noticed that you do not need to unlist first to get the same result:
my_df3 <- as.data.frame(my_list)

and then try to extract the Ticker from the new variable name. But I hope there is an easier solution.

I also tried my_df4 <- reshape2::melt(my_list), but this is not helpful. I have a feeling that the purrr package may have this functionality, but I am not familiar with the package.

I am happy about any help!

Thanks a lot

KidLu
  • 163
  • 8

3 Answers3

3

Use data.table::rbindlist:

data:table::rbindlist(my_list, idcol = "Ticker")

Or, in the tidyverse:

library(tidyverse)

my_list %>% 
  enframe() %>% 
  unnest_wider(value) %>% 
  unnest(cols = c(date, Value))

#  A tibble: 30 x 3
   name    date       Value
   <chr>   <date>     <int>
 1 Ticker1 2021-01-01     1
 2 Ticker1 2021-01-02     2
 3 Ticker1 2021-01-03     3
 4 Ticker1 2021-01-04     4
 5 Ticker1 2021-01-05     5
 6 Ticker1 2021-01-06     6
 7 Ticker1 2021-01-07     7
 8 Ticker1 2021-01-08     8
 9 Ticker1 2021-01-09     9
10 Ticker1 2021-01-10    10
# ... with 20 more rows
Maël
  • 45,206
  • 3
  • 29
  • 67
  • 1
    Unfortunately I can only accept one answer, and I prefer the dplyr package. But thank you! – KidLu Apr 14 '22 at 11:55
3

dplyrs bind_rows should do the trick:

library(dplyr)

bind_rows(my_list, .id = "Ticker")

This returns

# A tibble: 30 x 3
   Ticker  date       Value
   <chr>   <date>     <int>
 1 Ticker1 2021-01-01     1
 2 Ticker1 2021-01-02     2
 3 Ticker1 2021-01-03     3
 4 Ticker1 2021-01-04     4
 5 Ticker1 2021-01-05     5
 6 Ticker1 2021-01-06     6
 7 Ticker1 2021-01-07     7
 8 Ticker1 2021-01-08     8
 9 Ticker1 2021-01-09     9
10 Ticker1 2021-01-10    10
# ... with 20 more rows
Martin Gal
  • 16,640
  • 5
  • 21
  • 39
0

Once we realise that sub lists for each ticker is just 2 lists with equal lengths, then we can think of it as a list of 3 dataframes with 2 columns each, and we could simply call rowbind:

do.call(rbind, lapply(my_list, data.frame))
#                  date Value
# Ticker1.1  2021-01-01     1
# Ticker1.2  2021-01-02     2
# Ticker1.3  2021-01-03     3
# Ticker1.4  2021-01-04     4
# Ticker1.5  2021-01-05     5
# ...

But we want list names as a new column, we need extra step:

do.call(rbind, 
        lapply(names(my_list), function(i){
          data.frame(Ticker = i, my_list[[ i ]])
        }))
#     Ticker       date Value
# 1  Ticker1 2021-01-01     1
# 2  Ticker1 2021-01-02     2
# 3  Ticker1 2021-01-03     3
# 4  Ticker1 2021-01-04     4
# 5  Ticker1 2021-01-05     5
# ...

See other options to get ID as a new column after rowbinding multiple dataframes:

zx8754
  • 52,746
  • 12
  • 114
  • 209