为什么嵌套的 ifelse 在 dplyr 0.5.0 mutate 中创建不正确的结果?

Ale*_*lex 2 r dplyr

考虑以下数据框:

(tmp_df <-
structure(list(class = c(0L, 0L, 1L, 1L, 2L, 2L), logi = c(TRUE, 
FALSE, TRUE, FALSE, TRUE, FALSE), val = c(1, 1, 1, 1, 1, 1), 
    taken = c(1.00684931506849, 0.993197278911565, 1.025, 0.975609756097561, 
    1.00826446280992, 0.991803278688525)), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -6L), .Names = c("class", 
"logi", "val", "taken")))
Run Code Online (Sandbox Code Playgroud)

这会创建:

Source: local data frame [6 x 4]

  class  logi   val     taken
  <int> <lgl> <dbl>     <dbl>
1     0  TRUE     1 1.0068493
2     0 FALSE     1 0.9931973
3     1  TRUE     1 1.0250000
4     1 FALSE     1 0.9756098
5     2  TRUE     1 1.0082645
6     2 FALSE     1 0.9918033
Run Code Online (Sandbox Code Playgroud)

我希望按班级分组,如果每个组包含两个成员,则从valif中减去 1,否则,从 中减去该组中logi == FALSE的最小值。如果每个组不包含两个成员,则我们从 中减去零。takenvalval

使用包执行上述操作的代码dplyr可以使用以下方式表示:

tmp_df %>%
    group_by(class) %>%
    mutate(taken_2 = ifelse(n() != 2, 0, 
                              ifelse(logi, min(taken), 1)),
           not_taken = val - taken_2)
Run Code Online (Sandbox Code Playgroud)

但是,这会产生不正确的结果,第二个ifelse 条件始终解析为第一个条件:

Source: local data frame [6 x 6]
Groups: class [3]

  class  logi   val     taken   taken_2   not_taken
  <int> <lgl> <dbl>     <dbl>     <dbl>       <dbl>
1     0  TRUE     1 1.0068493 0.9931973 0.006802721
2     0 FALSE     1 0.9931973 0.9931973 0.006802721
3     1  TRUE     1 1.0250000 0.9756098 0.024390244
4     1 FALSE     1 0.9756098 0.9756098 0.024390244
5     2  TRUE     1 1.0082645 0.9918033 0.008196721
6     2 FALSE     1 0.9918033 0.9918033 0.008196721
Run Code Online (Sandbox Code Playgroud)

如果我们没有第一个ifelse语句,也可以产生正确的结果。

tmp_df %>%
    group_by(class) %>%
    mutate(taken_2 = ifelse(logi, min(taken), 1),
           not_taken = val - taken_2)
Run Code Online (Sandbox Code Playgroud)

生产:

Source: local data frame [6 x 6]
Groups: class [3]

  class  logi   val     taken   taken_2   not_taken
  <int> <lgl> <dbl>     <dbl>     <dbl>       <dbl>
1     0  TRUE     1 1.0068493 0.9931973 0.006802721
2     0 FALSE     1 0.9931973 1.0000000 0.000000000 # correct!
3     1  TRUE     1 1.0250000 0.9756098 0.024390244
4     1 FALSE     1 0.9756098 1.0000000 0.000000000 # correct!
5     2  TRUE     1 1.0082645 0.9918033 0.008196721
6     2 FALSE     1 0.9918033 1.0000000 0.000000000 # correct!
Run Code Online (Sandbox Code Playgroud)

通过检查成功执行类似操作的其他代码片段,我们可以看到这个问题似乎是孤立的mutate和嵌套的:ifelse

tmp_df %>%
    group_by(class) %>%
    mutate(taken_2 = ifelse(n() != 3, 0, 
                            ifelse(logi, min(taken), 1)),
           not_taken = val - taken_2)

tmp_df_2 <-
    tmp_df %>%
    filter(row_number() <= 2)

(tmp_df_2$taken_2 <-
    ifelse(c(0, 0), 0, 
           ifelse(tmp_df_2$logi, min(tmp_df_2$taken), 1)))

## but the following does not work (checks problem is not to do with grouping)
# tmp_df_2 %>%
#     mutate(taken_2 = ifelse(n() != 2, 0, 
#                             ifelse(logi, min(taken), 1)),
#            not_taken = val - taken_2)
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况,我怎样才能获得预期的行为?解决方法是将嵌套ifelse逻辑拆分为多个内联变异:

tmp_df %>%
    group_by(class) %>%
    mutate(taken_2 = ifelse(n() != 2, 0, 1),
           taken_3 = taken_2 * ifelse(logi, min(taken), 1),
           not_taken = val - taken_3)
Run Code Online (Sandbox Code Playgroud)

其他人发现了嵌套 ifelse 的类似问题,但我不知道它是否具有相同的根源: ifelse using dplyr results in NA for some reports

Hug*_*ugh 6

ifelse您是矢量回收的受害者。他们的关键是这一行:

mutate(taken_2 = ifelse(n() != 2, 0, 
                          ifelse(logi, min(taken), 1))
Run Code Online (Sandbox Code Playgroud)

因为n() != 2长度为 1(对于每个组),ifelse因此仅考虑第一个logi并重复/回收该值。

您应该使用if和if_else:

mutate(taken_2 = if (n() != 2) 0 else if_else(logi, min(taken), 1))
Run Code Online (Sandbox Code Playgroud)

我建议永远不要使用ifelse. 这是从一个因为这个错误而差点造成数百万美元错误的人那里得到的。