【问题标题】:Why does nested ifelse create incorrect results in dplyr 0.5.0 mutate?为什么嵌套 ifelse 会在 dplyr 0.5.0 变异中产生不正确的结果?
【发布时间】:2017-03-14 06:33:35
【问题描述】:

考虑以下数据框:

(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")))

创建:

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

我希望按类分组,如果每个组包含两个成员,则如果logi == FALSE,则从val 中减去1,否则,从val 中减去该组中taken 的最小值。如果每个组不包含两个成员,那么我们从val 中减去零。

使用dplyr包做上面的代码可以用:

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

但是,这会产生不正确的结果,第二个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

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

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

制作:

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!

通过检查其他成功执行类似操作的代码片段,我们可以看到这个问题似乎与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)

为什么会发生这种情况,我怎样才能获得预期的行为?一种解决方法是将嵌套的 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)

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

【问题讨论】:

    标签: r dplyr


    【解决方案1】:

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

    mutate(taken_2 = ifelse(n() != 2, 0, 
                              ifelse(logi, min(taken), 1))
    

    因为n() != 2 的长度为1(对于每个组),所以ifelse 只考虑第一个logi 并重复/循环该值。

    你应该使用ifif_else

    mutate(taken_2 = if (n() != 2) 0 else if_else(logi, min(taken), 1))
    

    我建议从不使用ifelse。从一个几乎由于这个确切的错误而导致数百万美元错误的人那里得到它。

    【讨论】:

    • 感谢您的推荐。 ifelse 是在这些函数可用之前进行条件变异的唯一方法。
    • 确实如此。但是现在if_else 可用,你应该使用它——感谢它如此挑剔!
    • 还有,和我对@Weihuang的评论一样,你怎么知道第一个ifelse的结果是长度一?
    • 您说得对,可以改进文档。但从?n 开始,标题是“当前组中的 观察数”。所以只能有一个观察结果。
    • 近数百万美元的错误听起来是个好故事。
    【解决方案2】:

    来自?ifelse

    ‘ifelse’ returns a value with the same shape as ‘test’

    并且由于 n() != 2 返回一个长度为 1 的向量,并且始终为真,第二个 ifelse 始终返回一个长度为 1 的向量,但会被回收以适应组的形状。一种解决方案是将组长度的向量输入第一个ifelse

    tmp_df %>%
        group_by(class) %>%
        mutate(taken_2 = ifelse(rep(n() != 2, n()), 0, 
                                  ifelse(logi, min(taken), 1)),
               not_taken = val - taken_2)
    # 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
    # 3     1  TRUE     1 1.0250000 0.9756098 0.024390244
    # 4     1 FALSE     1 0.9756098 1.0000000 0.000000000
    # 5     2  TRUE     1 1.0082645 0.9918033 0.008196721
    # 6     2 FALSE     1 0.9918033 1.0000000 0.000000000
    

    【讨论】:

    • 谢谢。我认为n() 会为每一行产生一个值,但显然它只有在明确分配时才会这样做,例如num_in_row = n()。我试图在不创建额外变量的情况下逃脱,但它让我失望了。
    • 你怎么知道n() = 2总是返回一个长度为1的向量,因为n()的文档非常简洁。
    • 你说得对,我其实不知道n() == 2返回一个长度为1的向量;我是从它的行为中推断出来的。
    • 是的,n() 返回一个长度为一的向量:mtcars %&gt;% summarise(x = length(n())) 所以n() == 2 的长度为一。如果将 n() 分配给变量,它会被回收,例如mtcars %&gt;% mutate(x = n())
    • 感谢@alistaire,这改变了我现在对n() 的看法。
    猜你喜欢
    • 1970-01-01
    • 2015-04-22
    • 2019-02-04
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多