【问题标题】:How to automatically full_join list elements across lists如何跨列表自动full_join列表元素
【发布时间】:2021-02-01 22:41:25
【问题描述】:

我的数据的简化版本如下所示:

primary_list <- list(secondary_list = list(df1 = data.frame(df_name = "hello", time = c(1,2,3,6,7,9,12,20), v1 = sample(0:1, 8, replace = T)),
                                           df2 = data.frame(df_name = "world", time = c(1,5,6,8,9,12,15,18,19,20), v1 = sample(0:1, 10, replace = T))),
                                           
                     secondary_list2 = list(df1 = data.frame(df_name = "hello", time = c(1,5,6,8,9,12,15,18,19,20), v1 = sample(0:1, 10, replace = T)),
                                            df2 = data.frame(df_name = "world", time = c(4,6,7,8,12), v1 = sample(0:1, 5, replace = T))))

我的目标是 full_joinsecondary_lists 中的所有数据帧(即具有共同元素名称和 df_name 标识符的数据帧)按“时间”分隔数据帧。目标类似于 question,但使用单独的数据框作为 full_join 的参数。

作为一个手动示例,我正在尝试实现这样的目标:

time_df <- data.frame(time = 1:25)

expected_output <- list(
  secondary_list = list(time_df, primary_list$secondary_list$df1, primary_list$secondary_list2$df1) %>% reduce(full_join, by = "time"),
  secondary_list2 = list(time_df, primary_list$secondary_list$df2, primary_list$secondary_list2$df2) %>% reduce(full_join, by = "time")
)

### Ideally I'm aiming for something cleaned up like this:                

ideal_output <- lapply(expected_output, function(x){
  x <- x %>% select(time,v1.x,v1.y)
  colnames(x) <- c("time", "v1", "v2")
  x
})

### I can probably handle that after

我正在尝试制作一个适用于更大列表结构并为所有 secondary_list 元素计算 full_join 的解决方案

我已尝试将列表 like this 展平,但在索引列表元素共享相同名称时遇到了一些复杂问题。还尝试rbindlist 将所有辅助列表压缩到一个数据框中,但还没有任何乐趣。

解决方案不必是dplyr,只需要适用于包含 n 个不同长度数据帧的 n 个辅助列表即可。如果输出不重复df_name.xdf_name.y,那么对于奖励点,那将是很好的,因为只需要一个标识符列,后跟变量(请参阅ideal_output),但我可以在之后很容易地清理它。

提前致谢!

更新 这个问题我得到了三个很好的答案。

  • Ronak 在 tidyverse 流水线工作流程中表现出色

  • ekoam 是一个单一的班轮,我完全赞成简洁的代码

  • neon_ninja 在微基准测试中获胜

由于我有大量数据,所以速度很重要,所以我会接受 neon_ninja 的回答。谢谢大家!

mbm = microbenchmark(
#####
ronak = primary_list %>%
  purrr::transpose() %>%
  map(~{
    .x$time_df <- time_df
    .x %>%
      reduce(full_join, by = 'time') %>% 
      select(time, df_name.x, starts_with("v")) ### Edited select fo n dfs
  }),

#####
ekoam = lapply(primary_list, compose(~select(., time, v1 = v1.x, v2 = v1.y), reduce), full_join, .init = time_df, by = "time"),
#####
neon_ninja = lapply(names(primary_list[[1]]), function(df_name) {
  time_df["df_name"] = df_name ### I added this to get a column of names
  time_df = data.frame(time = 1:25)
  for (sec_list_name in names(primary_list)) {
    time_df[sec_list_name] = NA
    df = primary_list[[sec_list_name]][[df_name]]
    for (i in 1:length(df$time)) {
      time_df[time_df$time == df$time[i], sec_list_name] = df$v1[i]
    }
  }
  time_df
})

)

#Unit: milliseconds
#       expr       min        lq      mean    median        uq      max neval
#      ronak 11.572526 12.507490 15.132911 13.491643 16.531374 28.11236   100
#      ekoam 10.710731 11.464450 13.665370 12.450013 14.204986 26.83166   100
# neon_ninja  1.116033  1.258137  1.784428  1.345762  1.643547 13.89803   100

【问题讨论】:

    标签: r list dataframe data-structures dplyr


    【解决方案1】:

    这个怎么样?

    c(list(time_df), unlist(primary_list, False, False)) %>% reduce(full_join, by = "time")
    

    更新

    lapply(primary_list, compose(~select(., time, v1 = v1.x, v2 = v1.y), reduce), full_join, .init = time_df, by = "time")
    

    输出

    $secondary_list
       time v1 v2
    1     1  1  1
    2     2  0 NA
    3     3  1 NA
    4     4 NA NA
    5     5 NA  1
    6     6  1  0
    7     7  1 NA
    8     8 NA  1
    9     9  0  0
    10   10 NA NA
    11   11 NA NA
    12   12  0  0
    13   13 NA NA
    14   14 NA NA
    15   15 NA  0
    16   16 NA NA
    17   17 NA NA
    18   18 NA  0
    19   19 NA  0
    20   20  1  1
    21   21 NA NA
    22   22 NA NA
    23   23 NA NA
    24   24 NA NA
    25   25 NA NA
    
    $secondary_list2
       time v1 v2
    1     1  1 NA
    2     2 NA NA
    3     3 NA NA
    4     4 NA  1
    5     5  0 NA
    6     6  0  1
    7     7 NA  1
    8     8  1  1
    9     9  1 NA
    10   10 NA NA
    11   11 NA NA
    12   12  1  1
    13   13 NA NA
    14   14 NA NA
    15   15  1 NA
    16   16 NA NA
    17   17 NA NA
    18   18  1 NA
    19   19  0 NA
    20   20  0 NA
    21   21 NA NA
    22   22 NA NA
    23   23 NA NA
    24   24 NA NA
    25   25 NA NA
    

    【讨论】:

    • 谢谢!这非常接近,但返回 1 个列表,我期望 2 个(所有 df1s 的列表和 df2s 的另一个列表)我将编辑问题以包含更完整的预期输出:)
    • 天才!效果很好,欣赏它:)
    【解决方案2】:

    您可以使用purrr::transpose 在一个列表中获取所有相似的命名数据框,然后将time_df 作为新数据框包含在每个列表中并执行full_join

    library(dplyr)
    library(purrr)
    
    primary_list %>%
      purrr::transpose() %>%
      map(~{
        .x$time_df <- time_df
        .x %>%
            reduce(full_join, by = 'time') %>% 
            select(time, v1 = v1.x, v2 = v1.y)
        })
    

    【讨论】:

    • 嘿,谢谢,我认为这与我所追求的非常接近,但我确实需要 full_join 成为一个单独的数据框并保留变量(v1.x,v1.y ...)作为列。我会稍微编辑一下这个问题:)。
    • @QAsena 所以你不想在输出中出现df_name?如果这是你想要的,你能检查我更新的答案吗?
    • 是的@Ronak Shah 更近了!但是您在手动示例中看到,full_join 使用单独的time_df 作为所有连接的第一个参数?我只是想看看在哪里可以将它作为不变的参数插入map。 @neon_ninja 答案似乎有效。
    • @QAsena 好的...我完全错过了time_df 部分。我已将其包含在上面的答案中。
    • 太棒了!一些新的语法让我在那里学习。非常感谢!
    【解决方案3】:

    我会这样解决这个问题:

    lapply(names(primary_list[[1]]), function(df_name) {
      time_df = data.frame(time = 1:25)
      for (sec_list_name in names(primary_list)) {
        time_df[sec_list_name] = NA
        df = primary_list[[sec_list_name]][[df_name]]
        for (i in 1:length(df$time)) {
          time_df[time_df$time == df$time[i], sec_list_name] = df$v1[i]
        }
      }
      time_df
    })
    

    如果您需要了解您的时间范围,这里有一个方法可以做到这一点:

    range(unlist(lapply(unlist(primary_list, F, F), function(df) { df$time })))

    同样,如果您需要找出 df 名称:

    unique(unlist(lapply(primary_list, names)))

    【讨论】:

      猜你喜欢
      • 2019-06-19
      • 2013-06-07
      • 2022-01-22
      • 2021-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-12
      • 1970-01-01
      相关资源
      最近更新 更多