使用ifelse和group by来突变data.table中的一列

时间:2019-06-12 19:09:21

标签: r dplyr data.table grouping

我有一些dplyr代码要移到data.table,这是我刚遇到的问题。如果b大于或等于c,我希望存储在列a中的3中一行与另一行之间的差异。但是,运行此代码后:

df = data.frame(a = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3), 
                b = c(0, 1, 0, 1, 0, 1, 1, 0, 3, 4, 5))

setDT(df)
df[ , c := ifelse(a >= 3, c(0, diff(b)), b), by = .(a)]

c中的所有元素均为0。为什么?

df 
    a b c
 1: 1 0 0
 2: 1 1 0
 3: 1 0 0
 4: 1 1 0
 5: 2 0 0
 6: 2 1 0
 7: 2 1 0
 8: 3 0 0
 9: 3 3 0
10: 3 4 0
11: 3 5 0

我认为是等效的dplyr:

df = data.frame(a = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3), 
                b = c(0, 1, 0, 1, 0, 1, 1, 0, 3, 4, 5))

df %>% 
      group_by(a) %>% 
      mutate(c = ifelse( a >= 3, c(0, diff(b)), b))

2 个答案:

答案 0 :(得分:3)

ifelse(test, yes, no)的帮助中,它应该返回...

  

具有相同长度和属性(包括尺寸和“类”)的矢量,这些矢量与来自yes或no值的测试和数据值相同。答案的方式将从逻辑上强制为首先容纳从yes获取的任何值,然后容纳从no获取的任何值。

但是:

> df %>% group_by(a) %>% do(print(.$a))
[1] 1 1 1 1
[1] 2 2 2
[1] 3 3 3 3
> data.table(df)[, print(a), by=a]
[1] 1
[1] 2
[1] 3

如帮助页面中所述,由于第一个参数的长度为1,因此如果您为其他部分传递矢量,则仅使用其第一个元素:

> ifelse(TRUE, 1:10, eleventy + million)
[1] 1

在使用常量值(例如...)时,您应该使用if ... else ...

> data.table(df)[, b := if (a >= 3) c(0, diff(b)) else b, by=a]

甚至更好,在这种情况下,您可以分配一个子集:

> data.table(df)[a >= 3, b := c(0, diff(b)), by=a]

关于为什么a的data.table习惯用法的长度为1,请参见常见问题解答“在每个组中,为什么组变量的长度为1?”

答案 1 :(得分:1)

我正在创建一个数据集,该数据集的ba的第一个元素,其值非零,以更好地说明。您先前的数据集全为零,并且c(0,diff(b))也是从零开始的,因此很难区分。

这里发生的是ifelse的输出是长度为1的向量。

library(data.table)

df = data.frame(a = c(1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3), 
                b = c(10, 1, 0, 1, 0, 1, 1, 0, 3, 4, 5))

看下面:

setDT(df)[ , c := ifelse(a >= 3, c(0, diff(b)), b), by = .(a)][]
#>     a  b  c
#>  1: 1 10 10
#>  2: 1  1 10
#>  3: 1  0 10
#>  4: 1  1 10
#>  5: 2  0  0
#>  6: 2  1  0
#>  7: 2  1  0
#>  8: 3  0  0
#>  9: 3  3  0
#> 10: 3  4  0
#> 11: 3  5  0

现在,让我们看看其他示例;这里我使用的是长度为4的简单向量(而不是c(0,diff(b))):

setDT(df)[ , c := ifelse(a >= 3L, c(20,2,3,4), -999), by=a][]
#>     a  b    c
#>  1: 1 10 -999
#>  2: 1  1 -999
#>  3: 1  0 -999
#>  4: 1  1 -999
#>  5: 2  0 -999
#>  6: 2  1 -999
#>  7: 2  1 -999
#>  8: 3  0   20
#>  9: 3  3   20
#> 10: 3  4   20 
#> 11: 3  5   20

您会看到第一个元素仍被分配给c组的a的所有行。

一种变通办法是在diff上使用a来查看何时未更改( ie diff(a)==0),并将其用作伪分组。在其他情况下;如下所示:

setDT(df)[, c := ifelse(a >= 3 & c(F,diff(a)==0), c(0,diff(b)), b)][]
#>     a  b  c
#>  1: 1 10 10
#>  2: 1  1  1
#>  3: 1  0  0
#>  4: 1  1  1
#>  5: 2  0  0
#>  6: 2  1  1
#>  7: 2  1  1
#>  8: 3  0  0
#>  9: 3  3  3
#> 10: 3  4  1
#> 11: 3  5  1