【问题标题】:Converting grouped tibble to named list将分组的小标题转换为命名列表
【发布时间】:2022-01-13 17:15:30
【问题描述】:

我觉得tidyverse 中可能有比for-loop 更好的方法。从一个标准的 tibble/dataframe 开始,创建一个列表,其中列表元素的名称是一列的唯一值 (group_by?),列表元素是另一列的所有值。

  my_data <- tibble(list_names = c("Ford", "Chevy", "Ford", "Dodge", "Dodge", "Ford"),
                    list_values = c("Ranger", "Equinox", "F150", "Caravan", "Ram", "Explorer"))
  
# A tibble: 6 × 2
  list_names list_values
  <chr>      <chr>      
1 Ford       Ranger     
2 Chevy      Equinox    
3 Ford       F150       
4 Dodge      Caravan    
5 Dodge      Ram        
6 Ford       Explorer

这是所需的输出:

  desired_output <- list(Ford = c("Ranger", "F150", "Explorer"),
       Chevy = c("Equinox"),
       Dodge = c("Caravan", "Ram"))

$Ford
[1] "Ranger"   "F150"     "Explorer"

$Chevy
[1] "Equinox"

$Dodge
[1] "Caravan" "Ram" 

这可以通过for-loop 来完成,但我敢打赌有一个tidyverse 函数可以让它更简单/更快,等等。

  desired_output <- list()
  for(i in seq_along(my_data$list_names)) {
    entry <- my_data %>%
      filter(list_names == my_data$list_names[i]) %>%
      pull(list_values)
    desired_output[[my_data$list_names[i]]] <- entry
  }

【问题讨论】:

    标签: r tidyverse purrr


    【解决方案1】:

    我们可以使用split

    with(my_data, split(list_values,
         factor(list_names, levels = unique(list_names))))
    $Ford
    [1] "Ranger"   "F150"     "Explorer"
    
    $Chevy
    [1] "Equinox"
    
    $Dodge
    [1] "Caravan" "Ram"   
    

    unstack

    unstack(my_data, list_values ~ list_names)
    $Chevy
    [1] "Equinox"
    
    $Dodge
    [1] "Caravan" "Ram"    
    
    $Ford
    [1] "Ranger"   "F150"     "Explorer"
    

    【讨论】:

    • my_data %&gt;% unstack(list_values ~ list_names 。最不冗长和最直接的回应。
    • @JeffParker split 也很紧凑,如果您不想使用 factor 订购值
    • 好点。就我而言,顺序并不重要。其他人的绝佳参考。
    • 看起来split 确实比unstack 快一点(只是出于好奇做了一个基准测试)。
    • @AndrewGillreath-Brown split 是一个快速函数,而 stack/unstack 很慢
    【解决方案2】:

    这是另一个选项(虽然有点冗长),使用来自tidyversegroup_modifydeframe

    library(tidyverse)
    
    my_data |>
      group_by(list_names)  |>
      group_modify(\(x, ...) tibble(res = list(deframe(x)))) |>
      deframe()
    

    或者另一种选择是使用summarise,然后再次使用deframe

    my_data %>%
      group_by(list_names) %>%
      summarise(named_vec = list(list_values)) %>%
      deframe()
    

    输出

    $Chevy
    [1] "Equinox"
    
    $Dodge
    [1] "Caravan" "Ram"    
    
    $Ford
    [1] "Ranger"   "F150"     "Explorer"
    

    基准测试

    我很想知道这里最快的答案是什么,显然@akrun 的split 是迄今为止最快的,其次是unstack

    bm <- microbenchmark::microbenchmark(
      akrun_split = with(my_data, split(list_values,
                                        factor(list_names, levels = unique(list_names)))),
      akrun_unstack = unstack(my_data, list_values ~ list_names),
      andrew_deframe1 = my_data |>
        group_by(list_names)  |>
        group_modify(\(x, ...) tibble(res = list(deframe(x)))) |>
        deframe(),
      andrew_deframe2 = my_data %>%
        group_by(list_names) %>%
        summarise(named_vec = list(list_values)) %>%
        deframe(),
      paulsmith = my_data %>% 
        group_by(list_names) %>% 
        summarise(list_values = list(list_values)) %>% 
        {set_names(.$list_values, .$list_names)}, 
      times=1000L
    )
    

    【讨论】:

      【解决方案3】:

      另一种可能的解决方案:

      library(tidyverse)
      
      my_data <- tibble(list_names = c("Ford", "Chevy", "Ford", "Dodge", "Dodge", "Ford"),
                        list_values = c("Ranger", "Equinox", "F150", "Caravan", "Ram", "Explorer"))
      
      my_data %>% 
        group_by(list_names) %>% 
        summarise(list_values = list(list_values)) %>% 
        {set_names(.$list_values, .$list_names)}
      
      #> $Chevy
      #> [1] "Equinox"
      #> 
      #> $Dodge
      #> [1] "Caravan" "Ram"    
      #> 
      #> $Ford
      #> [1] "Ranger"   "F150"     "Explorer"
      

      【讨论】:

        【解决方案4】:

        只是为了好玩,用我的 for 循环和 TarJae 的答案添加了基准。

        bm <- microbenchmark::microbenchmark(
          akrun_split = with(my_data, split(list_values,
                                            factor(list_names, levels = unique(list_names)))),
          akrun_unstack = unstack(my_data, list_values ~ list_names),
          andrew_deframe1 = my_data |>
            group_by(list_names)  |>
            group_modify(\(x, ...) tibble(res = list(deframe(x)))) |>
            deframe(),
          andrew_deframe2 = my_data %>%
            group_by(list_names) %>%
            summarise(named_vec = list(list_values)) %>%
            deframe(),
          paulsmith = my_data %>% 
            group_by(list_names) %>% 
            summarise(list_values = list(list_values)) %>% 
            {set_names(.$list_values, .$list_names)},
          jeffs = {
            desired_output <- list()
            for(i in seq_along(my_data$list_names)) {
              entry <- my_data %>%
                filter(list_names == my_data$list_names[i]) %>%
                pull(list_values)
              desired_output[[my_data$list_names[i]]] <- entry
            }
            desired_output},
            TarJae = my_data %>% 
            group_by(list_names) %>% 
            mutate(list_values= paste(list_values, collapse = ", ")) %>% 
            slice(1) %>% 
            group_split() %>% 
            setNames(sort(unique(my_data$list_names))) %>% 
            map(., dplyr::pull, -list_names) %>%
            map(., ~str_split(.x, ", ")[[1]] ), 
          times=100L
        )
        

        我还在两个最快的选项(来自 Akrun)上使用更大的数据集运行基准测试。

        library(nycflights13)
        my_data <- nycflights13::flights %>%
          select(list_names = carrier, list_values = flight)
        

        【讨论】:

          【解决方案5】:

          更新 II: 将 tibble 输出更改为矢量输出。感谢杰夫·帕克!

          更新由于 Jeff Parker 的评论(请参阅 cmets)。我现在更新了代码。问题是将名称设置为未排序,在使用sort 后,我们可以正确使用setNames。然后我添加了map 以应用dplyrs select 删除每个数据帧中的第一列:

          library(dplyr)
          library(purrr)
          
          my_data %>% 
            group_by(list_names) %>% 
            mutate(list_values= paste(list_values, collapse = ", ")) %>% 
            slice(1) %>% 
            group_split() %>% 
            setNames(sort(unique(my_data$list_names))) %>% 
            map(., dplyr::pull, -list_names) %>%
            map(., ~str_split(.x, ", ")[[1]] )
          
          $Chevy
          [1] "Equinox"
          
          $Dodge
          [1] "Caravan" "Ram"    
          
          $Ford
          [1] "Ranger"   "F150"     "Explorer"
          

          【讨论】:

          • 列表元素应该是向量,而不是数据框。此外,您还将雪佛兰与道奇、道奇与福特混为一谈。
          • @JeffParker。非常感谢您的来信。我会清除的。
          • @JeffParker。请看我的更新!
          • 列表元素仍然是小标题,但请参阅我使用dplyr::pullmap(., str_split) 修复的最新答案
          【解决方案6】:

          另一种方法:

          library(tidyverse)
          
          my_data %>%
            group_split(list_names) %>%
            map(~ lst(!!unique(pull(.x, list_names)) := unique(pull(.x, list_values)))) %>%
            flatten()
          #> $Chevy
          #> [1] "Equinox"
          #> 
          #> $Dodge
          #> [1] "Caravan" "Ram"    
          #> 
          #> $Ford
          #> [1] "Ranger"   "F150"     "Explorer"
          

          reprex package (v2.0.1) 于 2022 年 1 月 14 日创建

          数据:

          my_data <- tibble(
            list_names = c("Ford", "Chevy", "Ford", "Dodge", "Dodge", "Ford"),
            list_values = c("Ranger", "Equinox", "F150", "Caravan", "Ram", "Explorer")
          )
          

          【讨论】:

            猜你喜欢
            • 2020-02-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-01-09
            • 1970-01-01
            相关资源
            最近更新 更多