【问题标题】:How to use a range for columns instead of names for pmax / pmin [duplicate]如何使用列的范围而不是pmax / pmin的名称[重复]
【发布时间】:2020-06-16 20:07:02
【问题描述】:

我想在 pmax/pmin 中使用一系列列,而不是输入所有列的名称。

#sample data
foo <- data.frame(sapply(letters, function(x) x = sample(1:10,5)))

#this works
bar <- foo %>% 
    mutate(maxcol = pmax(a,b,c))

# this does not work
bar <- foo %>% 
    mutate(maxcol = pmax(a:z))

最后我也想要这样的东西

bar <- foo %>% 
    mutate_at(a:z = pmax(a:z))

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

    这是一个对所有行、所有列进行一次一个函数调用的选项。

    foo %>%
      mutate(maxcol = do.call(pmax, subset(., select = a:e)))
    #    a  b c d e  f g  h  i j  k l m  n  o p q  r  s t u  v w  x  y z maxcol
    # 1  1  4 9 2 4  4 1 10  2 3 10 4 7  1 10 9 8  2  8 9 5  1 9  1 10 9      9
    # 2  5  2 5 3 5  2 8  8  5 8  2 3 6 10  9 3 5  8  7 4 6  9 8  5  8 3      5
    # 3 10  9 6 1 7 10 6  4  4 7  6 6 2  7  5 5 4  1 10 7 3 10 5 10  1 7     10
    # 4  8  1 4 8 9  3 3  9 10 1  8 5 8  4  4 8 6 10  5 2 9  5 7  7  3 1      9
    # 5  2 10 2 9 8  9 9  6  7 5  9 2 5  5  7 4 2  5  4 8 4  6 6  2  9 6     10
    

    您可以使用冒号表示法选择部分或全部列,甚至是任意列:

    foo %>%
      mutate(maxcol = do.call(pmax, subset(., select = c(a:e,g))))
    #    a  b c d e  f g  h  i j  k l m  n  o p q  r  s t u  v w  x  y z maxcol
    # 1  1  4 9 2 4  4 1 10  2 3 10 4 7  1 10 9 8  2  8 9 5  1 9  1 10 9      9
    # 2  5  2 5 3 5  2 8  8  5 8  2 3 6 10  9 3 5  8  7 4 6  9 8  5  8 3      8
    # 3 10  9 6 1 7 10 6  4  4 7  6 6 2  7  5 5 4  1 10 7 3 10 5 10  1 7     10
    # 4  8  1 4 8 9  3 3  9 10 1  8 5 8  4  4 8 6 10  5 2 9  5 7  7  3 1      9
    # 5  2 10 2 9 8  9 9  6  7 5  9 2 5  5  7 4 2  5  4 8 4  6 6  2  9 6     10
    

    应该优先于其他答案(通常使用据称惯用的方法)的原因是:

    • 在 Dom 的回答中,max 函数为帧的每一行调用一次;没有使用 R 的矢量化操作,这是低效的,应尽可能避免;
    • 在 akrun 的回答中,pmax 为框架的每一列调用一次,在这种情况下,这听起来可能更糟,但实际上更接近于最好的情况。我的答案与 akrun 的答案最接近,因为我们是 mutate 中的 selecting 数据。

    如果您更喜欢使用dplyr::select 而不是base::subset,则需要将其拆分为

    foo %>%
      mutate(maxcol = select(., a:e, g) %>% do.call(pmax, .))
    

    我认为通过基准测试可以更好地证明这一点。使用提供的 5x26 帧,我们看到了明显的改进:

    set.seed(42)
    foo <- data.frame(sapply(letters, function(x) x = sample(1:10,5)))
    microbenchmark::microbenchmark(
      Dom = {
        foo %>% 
          rowwise() %>% 
          summarise(max= max(c_across(a:z)))
      },
      akr = {
        foo %>%
           mutate(maxcol = reduce(select(., a:z), pmax))
      },
      r2 = {
        foo %>%
          mutate(maxcol = do.call(pmax, subset(., select = a:z)))
      }
    )
    # Unit: milliseconds
    #  expr    min      lq    mean  median      uq     max neval
    #   Dom 6.6561 7.15260 7.61574 7.38345 7.90375 11.0387   100
    #   akr 4.2849 4.69920 4.96278 4.86110 5.18130  7.0908   100
    #    r2 2.3290 2.49285 2.68671 2.59180 2.78960  4.7086   100
    

    让我们尝试使用稍大的 5000x26:

    set.seed(42)
    foo <- data.frame(sapply(letters, function(x) x = sample(1:10,5000,replace=TRUE)))
    microbenchmark::microbenchmark(
      Dom = {
        foo %>% 
          rowwise() %>% 
          summarise(max= max(c_across(a:z)))
      },
      akr = {
        foo %>%
           mutate(maxcol = reduce(select(., a:z), pmax))
      },
      r2 = {
        foo %>%
          mutate(maxcol = do.call(pmax, subset(., select = a:z)))
      }
    )
    # Unit: milliseconds
    #  expr      min       lq      mean    median        uq       max neval
    #   Dom 515.6437 563.6060 763.97348 811.45815 883.00115 1775.2366   100
    #   akr   4.6660   5.1619  11.92847   5.74050   6.50625  293.7444   100
    #    r2   2.9253   3.4371   4.24548   3.71845   4.27380   14.0958   100
    

    最后一个肯定显示了使用rowwise 的结果。 akrun 的答案和这个答案之间的相对性能几乎与 5 行相同,强化了按列比按行更好的前提(并且一次性比两者都快)。

    (如果确实需要,也可以使用purrr::invoke 来完成,尽管它不会加快速度:

    library(purrr)
    foo %>%
      mutate(maxcol = invoke(pmax, subset(., select = a:z)))
    
    ### microbenchmark(...)
    # Unit: milliseconds
    #     expr    min      lq    mean  median      uq      max neval
    #      Dom 7.8292 8.40275 9.02813 8.97345 9.38500  12.4368   100
    #      akr 4.9622 5.28855 8.78909 5.60090 6.11790 309.2607   100
    #   r2base 2.5521 2.74635 3.01949 2.90415 3.21060   4.6512   100
    #  r2purrr 2.5063 2.77510 3.11206 2.93415 3.33015   5.2403   100
    

    【讨论】:

      【解决方案2】:

      您可以使用rowwisec_across(取决于dplyr >= 1.0.0):

      library(dplyr)
      
      foo %>% 
        rowwise() %>% 
        summarise(max= max(c_across(a:z)))
      
      `summarise()` ungrouping output (override with `.groups` argument)
      # A tibble: 5 x 1
          max
        <int>
      1    10
      2    10
      3    10
      4    10
      5    10
      

      【讨论】:

      • rowwise 是否保持与 ​​group_by 一样的意思,下一个操作会按行进行吗?如果它保持不变,如何按行删除?
      • 如果与mutate 一起使用,它将保持按行分组。只需添加 %&gt;% ungroup() 即可摆脱按行分组。
      • 虽然这个答案是 canonical to dplyr::c_across,但我发现有趣的是,文档建议 rowwise 作为第一种方法,尽管与按列 (akrun) 或一次性矢量化(我的回答)。
      • @r2evans 什么文档?你能添加一个参考吗?
      • "the docs" 前七个字提到了“规范”链接。具体来说,它说c_across 旨在与rowwise 一起使用,并且“示例”部分中的最后一段代码使用%&gt;% rowwise() %&gt;% mutate(... sum(c_across(...)))
      【解决方案3】:

      我们 select 从 a 到 z (select(., a:z)) 的列,reducepmax 应用于列的每个对应行后,将其应用于单个向量/列

      library(dplyr)
      library(purrr)
      foo %>%
           mutate(maxcol = reduce(select(., a:z), pmax))
      

      或者另一种选择是拼接(!!! 强制拼接对象列表。

      foo %>% 
           mutate(maxcol = pmax(!!! .))
      

      我们也可以在base R中使用pmaxdo.call

      foo$maxcol <- do.call(pmax, foo)
      

      【讨论】:

      • 您能否补充一些关于reduce 的作用以及!!! 是什么的信息?谢谢!
      • @AshishSinghal 添加了更多信息。 reduce 的作用类似于 base Rdo.call(pmax, foo) 中的 do.call(对于某些功能)
      • 不正确。 purrr::reduce 类似于基本 R 的 Reduce,而 purrr::invoke 类似于 R 的 do.call。最大的区别是invoke/do.call对函数进行一个调用,而reduce/Reducen-1进行调用,其中n是参数的个数它的清单; a:z 是 26 列,所以 pmax 被调用了 25 次。
      • 您对 do.call(pmax, foo) 的使用适用于所有列,但在列子集的 dplyr 管道中效果不佳,这是我从 OP 对 pmax(a,b,c) 的使用中推断出来的。也许我误解了对列子集的渴望。
      • @r2evans 我在考虑 OP 的 # this does not work 和带有完整列的 a:z。无论如何,没关系。您对基准有一些明确的解释。很高兴知道效率。谢谢
      猜你喜欢
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 2016-12-15
      • 2016-10-09
      • 2017-08-22
      • 2018-06-10
      • 1970-01-01
      • 2021-01-01
      相关资源
      最近更新 更多