【问题标题】:Spreading a two column data frame with tidyr使用 tidyr 传播两列数据框
【发布时间】:2016-02-08 15:52:40
【问题描述】:

我有一个如下所示的数据框:

  a b
1 x 8
2 x 6
3 y 3
4 y 4
5 z 5
6 z 6

我想把它变成这样:

  x y z
1 8 3 5
2 6 4 6

但是打电话

library(tidyr)
df <- data.frame(
    a = c("x", "x", "y", "y", "z", "z"),
    b = c(8, 6, 3, 4, 5, 6)
)
df %>% spread(a, b)

返回

   x  y  z
1  8 NA NA
2  6 NA NA
3 NA  3 NA
4 NA  4 NA
5 NA NA  5
6 NA NA  6

我做错了什么?

【问题讨论】:

    标签: r dplyr tidyr


    【解决方案1】:

    像这样?

    df <- data.frame(ind = rep(1:min(table(df$a)), length(unique(df$a))), df)
    df %>% spread(a, b) %>% select(-ind)
      ind x y z
    1   1 8 3 5
    2   2 6 4 6
    

    【讨论】:

    • (1) 你能展示如何生成ind 而不对复制的长度和数量进行硬编码吗? (2) %&gt;% select(-ind) 将在您完成后摆脱指标变量...
    • 您好@BenBolker,感谢您的建议。我编辑了它。想不出更好的方法...
    • 也许df %&gt;% group_by(a) %&gt;% mutate(ind = row_number()) %&gt;% spread(a, b) %&gt;% select(-ind) ?
    • 是的@StevenBeaupré,这很好。但是unstack 版本胜过这一切......
    • 我只是想改善您的答案,因为您提到无法想出更好的方法
    【解决方案2】:

    虽然我知道你在关注tidyr,但base 在这种情况下有一个解决方案:

    unstack(df, b~a)
    

    也快一点:

    Unit: microseconds
    
                    expr     min      lq     mean  median       uq      max neval
     df %>% spread(a, b) 657.699 679.508 717.7725 690.484 724.9795 1648.381   100
      unstack(df, b ~ a) 309.891 335.264 349.4812 341.9635 351.6565 639.738   100
    

    应大众需求,做大事

    我没有包含data.table 解决方案,因为我不确定通过引用传递是否会成为microbenchmark 的问题。

    library(microbenchmark)
    library(tidyr)
    library(magrittr)
    
    nlevels <- 3
    #Ensure that all levels have the same number of elements
    nrow <- 1e6 - 1e6 %% nlevels
    df <- data.frame(a=sample(rep(c("x", "y", "z"), length.out=nrow)),
                     b=sample.int(9, nrow, replace=TRUE))
    
    microbenchmark(df %>% spread(a, b),  unstack(df, b ~ a), data.frame(split(df$b,df$a)), do.call(cbind,split(df$b,df$a)))
    

    即使是 100 万,unstack 也更快。值得注意的是,split 解决方案也非常快。

    Unit: milliseconds
                                  expr       min        lq      mean    median       uq       max neval
                   df %>% spread(a, b) 366.24426 414.46913 450.78504 453.75258 486.1113 542.03722   100
                    unstack(df, b ~ a)  47.07663  51.17663  61.24411  53.05315  56.1114 102.71562   100
         data.frame(split(df$b, df$a))  19.44173  19.74379  22.28060  20.18726  22.1372  67.53844   100
     do.call(cbind, split(df$b, df$a))  26.99798  27.41594  31.27944  27.93225  31.2565  79.93624   100
    

    【讨论】:

    • 很好的答案。漂亮而简单。
    • stack/unstack 通常比较慢。这个基准是基于更大的数据集吗?
    • @akrun 一个很好的观察,我可以用更大的东西试试。
    【解决方案3】:

    您也可以使用 包中的 dcastrowid 执行此操作:

    dat <- dcast(setDT(df), rowid(a) ~ a, value.var = "b")[,a:=NULL]
    

    给出:

    > dat
       x y z
    1: 8 3 5
    2: 6 4 6
    

    旧解决方案:

    # create a sequence number by group
    setDT(df)[, r:=1:.N, by = a]
    # reshape to wide format and remove the sequence variable
    dat <- dcast(df, r ~ a, value.var = "b")[,r:=NULL]
    

    给出:

    > dat
       x y z
    1: 8 3 5
    2: 6 4 6
    

    【讨论】:

      【解决方案4】:

      另一个base 答案(看起来也很快):

      data.frame(split(df$b,df$a))
      

      【讨论】:

      • 是的,快速检查显示您的解决方案在 unstack 解决方案的三分之二时间内完成。
      • @sebastian-c 如果对 data.frame 作为结果对象不感兴趣并且可以使用 matrix do.call(cbind,split(df$b,df$a)) 可以更快。
      【解决方案5】:

      tidyr 1.0.0 开始,您可以使用 pivot_wider(),并且因为 a 没有唯一值,您需要调用 unhop on top :

      
      library(tidyr)
      df <- data.frame(
        a = c("x", "x", "y", "y", "z", "z"),
        b = c(8, 6, 3, 4, 5, 6)
      )
      
      pivot_wider(df, names_from = "a", values_from = "b", values_fn = list(b = list)) %>%
        unchop(everything())
      #> # A tibble: 2 x 3
      #>       x     y     z
      #>   <dbl> <dbl> <dbl>
      #> 1     8     3     5
      #> 2     6     4     6
      

      reprex package (v0.3.0) 于 2019-09-14 创建

      【讨论】:

        猜你喜欢
        • 2016-05-21
        • 1970-01-01
        • 2018-07-23
        • 1970-01-01
        • 2020-10-15
        • 1970-01-01
        • 2015-08-16
        • 1970-01-01
        • 2020-01-19
        相关资源
        最近更新 更多