【问题标题】:How do I iterate over two consecutive indexed objects using purrr::map()?如何使用 purrr::map() 遍历两个连续的索引对象?
【发布时间】:2021-01-18 04:45:32
【问题描述】:

我有超过 100 个 csv 文件,它们都具有相同的结构。每个 csv 文件都是系统中所有文档元数据的每日快照。文件名包含快照日期。元数据包含 Document_ID、Status、Author 和其他一些列。每行代表一个文档的元数据。

我想创建一个随时间变化的所有变化的日志。所以我首先将所有文件加载到一个小标题中,使用:

df <- fs::dir_ls(path = "Files") %>% 
  purrr::map_dfr(read_csv, .id = "Filename")

包含快照日期的原始文件名现在位于第一列。这是生成的 df 的简化表示:

library(tidyverse)

df <- tibble(Filename = c(rep("File_2020-09-27", 2), rep("File_2020-09-28", 3), rep("File_2020-09-29", 4), rep("File_2020-09-30", 5)),
             Doc_ID = c(seq(1, 2), seq(1, 3), seq(1, 4), seq(1, 5)),
             Status = c("Finished", "Started", 
                        "Finished", "Started", "Started", 
                        "Finished", "Started", "Finished", "Started",
                        "Finished", "Waiting", "Finished", "Started", "Started"),
             Author = c("John", "John",
                        "John", "Mike", "John",
                        "John", "Mike", "John", "Mike",
                        "John", "Mike", "John", "Mike", "Betty"),
             Other_column = rnorm(14))
df
#> # A tibble: 14 x 5
#>    Filename        Doc_ID Status   Author Other_column
#>    <chr>            <int> <chr>    <chr>         <dbl>
#>  1 File_2020-09-27      1 Finished John          0.319
#>  2 File_2020-09-27      2 Started  John          0.633
#>  3 File_2020-09-28      1 Finished John          2.27 
#>  4 File_2020-09-28      2 Started  Mike          0.302
#>  5 File_2020-09-28      3 Started  John          0.905
#>  6 File_2020-09-29      1 Finished John          0.451
#>  7 File_2020-09-29      2 Started  Mike          1.46 
#>  8 File_2020-09-29      3 Finished John          0.306
#>  9 File_2020-09-29      4 Started  Mike         -0.850
#> 10 File_2020-09-30      1 Finished John         -2.03 
#> 11 File_2020-09-30      2 Waiting  Mike          0.250
#> 12 File_2020-09-30      3 Finished John          0.637
#> 13 File_2020-09-30      4 Started  Mike         -0.207
#> 14 File_2020-09-30      5 Started  Betty        -2.13

reprex package (v0.3.0) 于 2020 年 10 月 2 日创建

请注意,文档永远不会消失,它们只会更改其状态或作者。为了手动创建所需的输出,我首先为每个每日快照创建单独的小标题:

Docs_1 <- df %>% filter(Filename == "File_2020-09-27")
Docs_2 <- df %>% filter(Filename == "File_2020-09-28")
Docs_3 <- df %>% filter(Filename == "File_2020-09-29")
Docs_4 <- df %>% filter(Filename == "File_2020-09-30")

然后,对于每一对连续的每日快照,我会确定第二天的新行或与前一天不同的行。我只对这些感兴趣。 “新”或“不同”与Doc_IDStatusAuthor 的组合有关:

Changes_1_2 <- Docs_2 %>% dplyr::anti_join(Docs_1, by = c("Doc_ID", "Status", "Author"))

导致:

# A tibble: 2 x 5
  Filename        Doc_ID Status  Author Other_column
  <chr>            <int> <chr>   <chr>         <dbl>
1 File_2020-09-28      2 Started Mike          0.807
2 File_2020-09-28      3 Started John          0.336
Changes_2_3 <- Docs_3 %>% dplyr::anti_join(Docs_2, by = c("Doc_ID", "Status", "Author"))

导致:

# A tibble: 2 x 5
  Filename        Doc_ID Status   Author Other_column
  <chr>            <int> <chr>    <chr>         <dbl>
1 File_2020-09-29      3 Finished John         1.48  
2 File_2020-09-29      4 Started  Mike        -0.0407
Changes_3_4 <- Docs_4 %>% dplyr::anti_join(Docs_3, by = c("Doc_ID", "Status", "Author"))

导致:

# A tibble: 2 x 5
  Filename        Doc_ID Status  Author Other_column
  <chr>            <int> <chr>   <chr>         <dbl>
1 File_2020-09-30      2 Waiting Mike         -0.267
2 File_2020-09-30      5 Started Betty        -1.36 

最后,我将所有更改绑定在一起,以在单个 tibble 中获取所有更改的日志:

Changelog <- dplyr::bind_rows(Changes_1_2, Changes_2_3, Changes_3_4)

导致:

# A tibble: 6 x 5
  Filename        Doc_ID Status   Author Other_column
  <chr>            <int> <chr>    <chr>         <dbl>
1 File_2020-09-28      2 Started  Mike         0.807 
2 File_2020-09-28      3 Started  John         0.336 
3 File_2020-09-29      3 Finished John         1.48  
4 File_2020-09-29      4 Started  Mike        -0.0407
5 File_2020-09-30      2 Waiting  Mike        -0.267 
6 File_2020-09-30      5 Started  Betty       -1.36  

对于每个Doc_ID,然后我可以在变更日志中分析其元数据随时间的变化。

鉴于文件和条目的数量庞大,我需要一个更优雅的解决方案来创建变更日志。如何使用迭代实现此过程,最好使用tidyversepurrr::map() 函数?我的问题是每次迭代都针对两个连续的索引对象,我在任何地方都找不到这样的例子。我正在考虑这样的事情(显然这段代码不起作用,只是为了说明而发明了我自己的符号):

Changelog <- df %>% split(.$Date) %>% 
  purrr::map_dfr(df_index+1 %>% dplyr::anti_join(df_index, by = c("Doc_ID", "Status", "Author")))

有人知道如何解决这个问题吗?也许我还应该将 csv 文件的初始加载更改为列表,而不是将它们加载到单个小标题中。

【问题讨论】:

    标签: r iteration tidyverse purrr


    【解决方案1】:

    我认为我们可以分组/嵌套、滞后和比较:

    library(dplyr)
    library(tidyr) # unnest
    set.seed(42) # and then your `df <- tibble(...)`
    
    df %>%
      nest_by(Filename) %>%
      ungroup() %>%
      mutate(lastdata = lag(data)) %>%
      filter(lengths(lastdata) > 0) %>%
      mutate(
        diffs = purrr::map2(data, lastdata, ~ anti_join(.x, .y, by = c("Doc_ID", "Status", "Author")))
      ) %>%
      select(-data, -lastdata) %>%
      tidyr::unnest(diffs)
    # # A tibble: 6 x 5
    #   Filename        Doc_ID Status   Author Other_column
    #   <chr>            <int> <chr>    <chr>         <dbl>
    # 1 File_2020-09-28      2 Started  Mike         0.633 
    # 2 File_2020-09-28      3 Started  John         0.404 
    # 3 File_2020-09-29      3 Finished John        -0.0947
    # 4 File_2020-09-29      4 Started  Mike         2.02  
    # 5 File_2020-09-30      2 Waiting  Mike         1.30  
    # 6 File_2020-09-30      5 Started  Betty       -0.279 
    

    要查看的重要步骤是:

    1. 初始分组/嵌套设置:

      df %>%
        nest_by(Filename) %>%
        ungroup() %>%
        mutate(lastdata = lag(data))
      # # A tibble: 4 x 3
      #   Filename                      data           lastdata
      #   <chr>           <list<tbl_df[,4]>> <list<tbl_df[,4]>>
      # 1 File_2020-09-27            [2 x 4]                [0]
      # 2 File_2020-09-28            [3 x 4]            [2 x 4]
      # 3 File_2020-09-29            [4 x 4]            [3 x 4]
      # 4 File_2020-09-30            [5 x 4]            [4 x 4]
      

      data 列有效地包含您的Docs_1Docs_2,而lastdata 包含data上一个。这意味着对于第二行,data 包含来自09-28 的行,而lastdata 包含来自09-27 的行。

    2. 由于我们无法将 09-27 与前一天进行比较(并且其 lastdata 为空),我们将其过滤掉:

        filter(lengths(lastdata) > 0)
      
    3. 最后,我们遍历列,anti_joining 每对 lastdatadata

        mutate(
          diffs = purrr::map2(data, lastdata, ~ anti_join(.x, .y, by = c("Doc_ID", "Status", "Author")))
        )
      # # A tibble: 3 x 4
      #   Filename                      data           lastdata diffs           
      #   <chr>           <list<tbl_df[,4]>> <list<tbl_df[,4]>> <list>          
      # 1 File_2020-09-28            [3 x 4]            [2 x 4] <tibble [2 x 4]>
      # 2 File_2020-09-29            [4 x 4]            [3 x 4] <tibble [2 x 4]>
      # 3 File_2020-09-30            [5 x 4]            [4 x 4] <tibble [2 x 4]>
      
    4. 通过删除和取消嵌套来清理。

    【讨论】:

    • 你成功了,谢谢!只是出于好奇:是否也可以使用 dplyr 1.0.0 的新 rowwise() 和 summarise() 函数来解决问题?显然,它们可以在某些用例中替换 purrr::map() 函数。
    • 我不认为rowwise 是您想要的,因为您要包含的数据跨越多行。 (而且rowwise 不是新的,它已经有一段时间了。你在想across 吗?它是 dplyr-1.0.0 中的新功能。)
    • 你是对的。我刚刚想到了来自 Hadley 的评论:“结合 rowwise(),summarise() 现在足够强大,可以取代许多以前需要 map() 或 apply() 函数的工作流。”:tidyverse.org/blog/2020/03/dplyr-1-0-0-summarise/#non-summaries
    • 顺便说一句:我在文件中阅读的方式(见上文)是最好的方式吗?我将它们加载到一个单独的 tibble 中,然后再次 nest_by 那个 tibble。我是否应该使用与 map_dfr 不同的 map() 函数来加载文件以简化后续处理?
    • 我觉得没问题。有几种不同的方法,没有更多的想法,我目前没有理由提出替代方案。当然,这似乎并没有完全错。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-28
    • 2020-02-17
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    • 1970-01-01
    • 2018-12-27
    相关资源
    最近更新 更多