【问题标题】:Is there a function like switch which works inside of dplyr::mutate?dplyr::mutate 内部是否有类似 switch 的功能?
【发布时间】:2015-04-20 00:34:19
【问题描述】:

我不能在mutate 中使用 switch,因为它返回整个向量而不是只返回行。作为一个黑客,我正在使用:

pick <- function(x, v1, v2, v3, v4) {
    ifelse(x == 1, v1,
           ifelse(x == 2, v2,
                  ifelse(x == 3, v3,
                         ifelse(x == 4, v4, NA))))
}

这在mutate 内部有效,目前还可以,因为我通常会在 4 件事中进行选择,但这可能会改变。你能推荐一个替代品吗?

例如:

library(dplyr)
df.faithful <- tbl_df(faithful)
df.faithful$x  <- sample(1:4, 272, rep=TRUE)
df.faithful$y1 <- rnorm(n=272, mean=7, sd=2)
df.faithful$y2 <- rnorm(n=272, mean=5, sd=2)
df.faithful$y3 <- rnorm(n=272, mean=7, sd=1)
df.faithful$y4 <- rnorm(n=272, mean=5, sd=1)

使用pick

mutate(df.faithful, y = pick(x, y1, y2, y3, y4))
Source: local data frame [272 x 8]

   eruptions waiting x        y1        y2       y3       y4        y
1      3.600      79 1  8.439092 5.7753006 8.319372 5.078558 8.439092
2      1.800      54 2 13.515956 6.1971512 6.343157 4.962349 6.197151
3      3.333      74 4  7.693941 6.8973365 5.406684 5.425404 5.425404
4      2.283      62 4 12.595852 6.9953995 7.864423 3.730967 3.730967
5      4.533      85 3 11.952922 5.1512987 9.177687 5.511899 9.177687
6      2.883      55 3  7.881350 1.0289711 6.304004 3.554056 6.304004
7      4.700      88 4  8.636709 6.3046198 6.788619 5.748269 5.748269
8      3.600      85 1  8.027371 6.3535056 7.152698 7.034976 8.027371
9      1.950      51 1  5.863370 0.1707758 5.750440 5.058107 5.863370
10     4.350      85 1  7.761653 6.2176610 8.348378 1.861112 7.761653
..       ...     ... .       ...       ...      ...      ...      ...

如果 x == 1,我们看到我将值从 y1 复制到 y 中,依此类推。这是我想要做的,但我希望能够做到,无论我有 4 列还是 400 列的列表。

尝试使用switch:

mutate(df.faithful, y = switch(x, y1, y2, y3, 4))

Error in switch(c(1L, 2L, 4L, 4L, 3L, 3L, 4L, 1L, 1L, 1L, 4L, 3L, 1L,  : 
EXPR must be a length 1 vector

尝试使用list:

mutate(df.faithful, y = list(y1, y2, y3, y4)[[x]])
Error in list(c(8.43909205142925, 13.5159559591257, 7.69394050059568,  : 
recursive indexing failed at level 2

尝试使用c:

mutate(df.faithful, y = c(y1, y2, y3, y4)[x])
Source: local data frame [272 x 8]

   eruptions waiting x        y1        y2       y3       y4         y
1      3.600      79 1  8.439092 5.7753006 8.319372 5.078558  8.439092
2      1.800      54 2 13.515956 6.1971512 6.343157 4.962349 13.515956
3      3.333      74 4  7.693941 6.8973365 5.406684 5.425404 12.595852
4      2.283      62 4 12.595852 6.9953995 7.864423 3.730967 12.595852
5      4.533      85 3 11.952922 5.1512987 9.177687 5.511899  7.693941
6      2.883      55 3  7.881350 1.0289711 6.304004 3.554056  7.693941
7      4.700      88 4  8.636709 6.3046198 6.788619 5.748269 12.595852
8      3.600      85 1  8.027371 6.3535056 7.152698 7.034976  8.439092
9      1.950      51 1  5.863370 0.1707758 5.750440 5.058107  8.439092
10     4.350      85 1  7.761653 6.2176610 8.348378 1.861112  8.439092
..       ...     ... .       ...       ...      ...      ...       ...

没有产生错误,但行为不符合预期。

【问题讨论】:

  • 使用d %&gt;% mutate(y=cbind(y1,y2,y3,y4)[cbind(1:n(),x)])d$y &lt;- as.data.frame(d)[cbind(1:nrow(d),d$x+3)]

标签: r dplyr


【解决方案1】:

对于 OP 来说太晚了,但万一这出现在搜索中......

dplyr v0.5 有recode()switch() 的矢量化版本,所以

data_frame(
  x = sample(1:4, 10, replace=TRUE),
  y1 = rnorm(n=10, mean=7, sd=2),
  y2 = rnorm(n=10, mean=5, sd=2),
  y3 = rnorm(n=10, mean=7, sd=1),
  y4 = rnorm(n=10, mean=5, sd=1)
) %>%
mutate(y = recode(x,y1,y2,y3,y4))

按预期生产:

# A tibble: 10 x 6
       x        y1       y2       y3       y4        y
   <int>     <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
1      2  6.950106 6.986780 7.826778 6.317968 6.986780
2      1  5.776381 7.706869 7.982543 5.048649 5.776381
3      2  7.315477 2.213855 6.079149 6.070598 2.213855
4      3  7.461220 5.100436 7.085912 4.440829 7.085912
5      3  5.780493 4.562824 8.311047 5.612913 8.311047
6      3  5.373197 7.657016 7.049352 4.470906 7.049352
7      2  6.604175 9.905151 8.359549 6.430572 9.905151
8      3 11.363914 4.721148 7.670825 5.317243 7.670825
9      3 10.123626 7.140874 6.718351 5.508875 6.718351
10     4  5.407502 4.650987 5.845482 4.797659 4.797659

(也适用于命名参数,包括字符和因子 x。)

【讨论】:

  • 你太棒了。
【解决方案2】:

x的每个值进行运算。这是data.table 版本,我假设类似的东西可以在dplyr 中完成:

library(data.table)

dt = data.table(x = c(1,1,2,2), a = 1:4, b = 4:7)

dt[, newcol := switch(as.character(x), '1' = a, '2' = b, NA), by = x]
dt
#   x a b newcol
#1: 1 1 4      1
#2: 1 2 5      2
#3: 2 3 6      6
#4: 2 4 7      7

【讨论】:

    【解决方案3】:

    您现在可以将dplyr 的函数case_whenmutate() 一起使用。

    按照您的示例生成数据:

    library(dplyr)
    
    df.faithful <- tbl_df(faithful)
    df.faithful$x  <- sample(1:4, 272, rep=TRUE)
    df.faithful$y1 <- rnorm(n=272, mean=7, sd=2)
    df.faithful$y2 <- rnorm(n=272, mean=5, sd=2)
    df.faithful$y3 <- rnorm(n=272, mean=7, sd=1)
    df.faithful$y4 <- rnorm(n=272, mean=5, sd=1)
    

    现在我们使用case_when定义一个新的pick()函数:

    pick2 <- function(x, v1, v2, v3, v4) {
      out = case_when(
        x == 1 ~ v1,
        x == 2 ~ v2,
        x == 3 ~ v3,
        x == 4 ~ v4
      )
      return(out)
    }
    

    你看你可以在mutate()中完美使用它:

    df.faithful %>% 
      mutate(y = pick2(x, y1, y2, y3, y4))
    

    输出是:

    # A tibble: 272 x 8
       eruptions waiting     x    y1    y2    y3    y4     y
           <dbl>   <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
     1      3.6       79     3  8.73  7.23  8.89  4.04  8.89
     2      1.8       54     3  9.97  4.31  7.06  5.05  7.06
     3      3.33      74     1  6.65  7.23  4.46  6.49  6.65
     4      2.28      62     1  6.40  4.39  5.41  3.49  6.40
     5      4.53      85     4  3.96  8.85  7.43  6.51  6.51
     6      2.88      55     4  6.36  8.08  5.82  5.06  5.06
     7      4.7       88     1  5.91  6.47  6.43  5.88  5.91
     8      3.6       85     1  7.77  4.55  6.56  5.05  7.77
     9      1.95      51     4  5.74  6.46  6.95  4.26  4.26
    10      4.35      85     1  7.04  1.73  5.71  2.53  7.04
    # ... with 262 more rows
    

    【讨论】:

    • 万一其他人不清楚,也可以在mutate中直接使用case_whenpick2 函数不是必需的,但当然可能需要它(例如,如果重复使用)。
    【解决方案4】:

    如果你想在mutate中使用switch,你必须在之前执行rowwise

    iris %>%
      rowwise() %>%
      mutate(
        x = switch(
          as.character(Species),
          'setosa' = 'ss',
          'versicolor' = 'vc',
          'virginica' = 'vg'
        )
      ) %>%
      ungroup()
    

    【讨论】:

    • 谢谢,它确实有效。但为什么? (好的,文档可以提示一个线索:dplyr.tidyverse.org/articles/rowwise.html)。 mutate 似乎会按列正常运行,但 switch 需要单个值,因此需要逐个馈送行。
    【解决方案5】:

    你可以这样修改你的函数:

    map <- data.frame(i=1:2,v=10:11)
    #   i  v
    # 1 1 10
    # 2 2 11
    
    set.seed(1)
    x <- sample(1:3,10,rep=T)
    #  [1] 1 2 2 3 1 3 3 2 2 1
    
    i <- match(x,map$i)
    ifelse(is.na(i),x,map$v[i])
    # [1] 10 11 11  3 10  3  3 11 11 10
    

    想法是将您要查找的值和替换值保留在单独的数据框map 中,然后使用match 匹配xmap

    [更新]

    您可以将此解决方案包装成一个可在mutate 中使用的函数:

    multipleReplace <- function(x, what, by) {
      stopifnot(length(what)==length(by))               
      ind <- match(x, what)
      ifelse(is.na(ind),x,by[ind])
    }
    
    # Create a sample data set
    d <- structure(list(x = c(1L, 2L, 2L, 3L, 1L, 3L, 3L, 2L, 2L, 1L), y = c(1L, 2L, 2L, 3L, 3L, 1L, 3L, 2L, 2L, 1L)), .Names = c("x", "y"), row.names = c(NA, -10L), class = "data.frame")
    
    d %>% 
      mutate(z = multipleReplace(x, what=c(1,3), by=c(101,103)))
    #    x y   z
    # 1  1 1 101
    # 2  2 2   2
    # 3  2 2   2
    # 4  3 3 103
    # 5  1 3 101
    # 6  3 1 103
    # 7  3 3 103
    # 8  2 2   2
    # 9  2 2   2
    # 10 1 1 101
    

    【讨论】:

      【解决方案6】:

      这是使用data.table 的另一种方式。这个想法基本上是用组合创建一个key data.table,然后执行一个join,如下:

      我将使用@eddi 回答中的data.table。

      require(data.table)
      key = data.table(x = 1:2, col = c("a", "b"))
      
      setkey(dt, x)
      dt[key, new_col := get(i.col), by=.EACHI]
      #    x a b new_col
      # 1: 1 1 4       1
      # 2: 1 2 5       2
      # 3: 2 3 6       6
      # 4: 2 4 7       7
      

      join 是在列x 上执行的。对于key的每一行,在dt中找到对应的匹配行。例如:来自 keyx = 1dt 的行 12 匹配。在这些行上,我们访问存储在 key's col 中的列,即“a”。 get("a") 为那些匹配的行返回列 a 的值,即 12。希望这会有所帮助。

      by=.EACHI 确保为key 中的每一行计算表达式new_col := get(i.col)。你可以通过here了解更多。

      【讨论】:

      • join 方法对我来说似乎是最好的 (+1) - 但令人惊讶的是,它以 data.table 的特定答案呈现。可以使用dplyr::left_join 或简单地使用matchmerge 来完成。
      • 我真的不明白你在我的回答下评论的意义。您的意思是我必须在我的答案中添加所有可能的解决方案吗?除了您提出的解决方案之外,还将创建一个全新的 data.frame,同时通过引用更新原始 data.table :-O.
      • 评论的重点是希望任何阅读此答案的人都意识到,除了在data.table 中工作(正如您很好地展示的那样)之外,同样的通用方法可以在 base 和 @ 987654337@。我认为 join 是解决 OPs 问题的最自然的方法,但是当 OP 标记为 dplyr 并请求 dplyr 解决方案时,我觉得奇怪的是,使用 join 方法的唯一答案没有 i> 使用dplyr。我也宁愿留下评论而不是添加新的答案——这个问题已经太多了,使用不同包的语法使用相同的方法似乎没有足够的不同。
      • 我当然不是说你必须为你的答案添加所有可能的解决方案——我也不想就dplyrdata.table 的相对优点进行辩论。如果问题本身不是那么集中在dplyr 上,我根本不会说什么。如果您愿意,我会进行友好的编辑,以使用dplyr 语法添加相同技术的演示。
      • 我没有辩论。我已经使用 join+update 提供了答案,因为我发现它最适合 Q 并认为它与您的建议完全不同(首先将所有内容加入新对象并更新)。随意将您自己的添加到列表中。
      【解决方案7】:

      另一种(涉及更多的)路线涉及使用tidyr

      df %>%
        mutate(row = row_number()) %>%
        gather(n, y, y1:y4) %>%
        mutate(n = as.integer(str_extract(n, "[0-9]+"))) %>%
        filter(x == n) %>%
        arrange(row) %>%
        select(-c(row, n))
      

      【讨论】:

        【解决方案8】:

        我有点晚了,但这是我使用 mapply 的解决方案。

        vswitch <- function(x, ...) {
          mapply(FUN = function(x, ...) {
                   switch(x, ...)
                 }, x, ...)
        }
        
        mutate(df.faithful, y = vswitch(x, y1, y2, y3, y4))
        

        【讨论】:

          【解决方案9】:

          比 user6702291 建议的解决方案更复杂的版本是使用 map 函数,例如 map_dbl()。它更复杂,但我认为值得分享,因为它更适用于您尝试使用的函数没有矢量化版本的其他情况。

          在这种情况下,它会像这样工作。

           tibble.faithful %>% 
            mutate(y = map_dbl(seq_along(x), ~switch(x[.x], y1, y2, y3, y4)[1]))
          

          我实际上不确定,为什么需要“[1]” - 但我还是想分享它作为建议。

          【讨论】:

            猜你喜欢
            • 2015-11-21
            • 2012-08-01
            • 2012-04-04
            • 2023-04-07
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多