首页 > 解决方案 > 如何在 R data.table 中按行使用 ifelse?

问题描述

我想在一个R data.table基于ifelse()不同列的比较的新列中创建一个新列。但是,我希望ifelse按行应用该语句。我尝试过使用 的组by功能data.table,但它似乎应用了逐行的test条件,但评估了列中所有值的条件,而不是使用条件逐行进行。下面是一个示例和我尝试过的一些解决方案。ifelseyesby

我有一个R data.table这样的:

> set.seed(45)
> DT <- data.table(date = c(rep("2018-01-01", 3), rep("2018-01-02", 3), rep("2018-01-03", 3)), 
+                  id = rep(letters[1:3], 3), 
+                  v1 = sample(x = -20:20, size = 9), 
+                  v2 = sample(x = -20:20, size = 9))
> str(DT)
Classes ‘data.table’ and 'data.frame':  9 obs. of  4 variables:
 $ date: chr  "2018-01-01" "2018-01-01" "2018-01-01" "2018-01-02" ...
 $ id  : chr  "a" "b" "c" "a" ...
 $ v1  : int  5 -8 -11 -6 -7 -10 -13 -2 -14
 $ v2  : int  -20 -6 14 -9 -3 -5 19 12 -16
 - attr(*, ".internal.selfref")=<externalptr> 
> DT
         date id  v1  v2
1: 2018-01-01  a   5 -20
2: 2018-01-01  b  -8  -6
3: 2018-01-01  c -11  14
4: 2018-01-02  a  -6  -9
5: 2018-01-02  b  -7  -3
6: 2018-01-02  c -10  -5
7: 2018-01-03  a -13  19
8: 2018-01-03  b  -2  12
9: 2018-01-03  c -14 -16

我想要以下输出:

> DT_out
         date id  v1  v2  c
1: 2018-01-01  a   5 -20  0
2: 2018-01-01  b  -8  -6  0
3: 2018-01-01  c -11  14 11
4: 2018-01-02  a  -6  -9  0
5: 2018-01-02  b  -7  -3  0
6: 2018-01-02  c -10  -5  0
7: 2018-01-03  a -13  19 13
8: 2018-01-03  b  -2  12  2
9: 2018-01-03  c -14 -16  0 

我尝试过的解决方案:

尝试 #1)没有错误,但会评估 和 中的所有min值。这种行为是意料之中的;但是,即使没有设置或声明 ,它也会按行评估条件,这对我来说很奇怪:v1v2testkeyby

> DT[, c := ifelse(v1 < 0 & v2 > 0, min(-v1, v2), 0)]
> DT
         date id  v1  v2   c
1: 2018-01-01  a   5 -20   0
2: 2018-01-01  b  -8  -6   0
3: 2018-01-01  c -11  14 -20
4: 2018-01-02  a  -6  -9   0
5: 2018-01-02  b  -7  -3   0
6: 2018-01-02  c -10  -5   0
7: 2018-01-03  a -13  19 -20
8: 2018-01-03  b  -2  12 -20
9: 2018-01-03  c -14 -16   0

尝试#2)当我设置key并使用by条件时,没有任何变化,但我收到一条错误消息。

> setkey(DT, date, id)
> DT[, c := ifelse(v1 < 0 & v2 > 0, min(-v1, v2), 0), by = list(date, id)]
Error in `[.data.table`(DT, , `:=`(c, ifelse(v1 < 0 & v2 > 0, min(-v1,  : 
  Type of RHS ('integer') must match LHS ('double'). To check and coerce would impact performance too much for the fastest cases. Either change the type of the target column, or coerce the RHS of := yourself (e.g. by using 1L instead of 1)
> DT
         date id  v1  v2   c
1: 2018-01-01  a   5 -20   0
2: 2018-01-01  b  -8  -6   0
3: 2018-01-01  c -11  14 -20
4: 2018-01-02  a  -6  -9   0
5: 2018-01-02  b  -7  -3   0
6: 2018-01-02  c -10  -5   0
7: 2018-01-03  a -13  19 -20
8: 2018-01-03  b  -2  12 -20
9: 2018-01-03  c -14 -16   0

由于 和 的组合date对于id每一行都是唯一的,因此我更难以理解为什么不对 each 进行评估group,在这种情况下,每一行都是如此。

也许我需要在 中使用.SDcols = .(date, id)and .SDifelse但我不知道如何在.SD中使用ifelse

标签: rdata.table

解决方案


您需要使用pmin而不是min

DT[, c := ifelse(v1 < 0 & v2 > 0, pmin(-v1, v2), 0)]

> DT
         date id  v1  v2  c
1: 2018-01-01  a   5 -20  0
2: 2018-01-01  b  -8  -6  0
3: 2018-01-01  c -11  14 11
4: 2018-01-02  a  -6  -9  0
5: 2018-01-02  b  -7  -3  0
6: 2018-01-02  c -10  -5  0
7: 2018-01-03  a -13  19 13
8: 2018-01-03  b  -2  12  2
9: 2018-01-03  c -14 -16  0

# see also:
?pmin

pmax*() 和 pmin*() 将一个或多个向量作为参数,将它们回收到公共长度并返回一个向量,给出参数向量的“平行”最大值(或最小值)。

[稍后添加]

如果您首先更改列类型,您的原始代码也可以工作:

  DT[, v1:= as.numeric(v1)]   # was integer, converting to 'double'
  DT[, v2:= as.numeric(v2)]   # ---,,---
  DT[, c := ifelse(v1 < 0 & v2 > 0, min(-v1, v2), 0), by = list(date, id)]

据我了解,data.table 的理念不是让 R “隐式”更改列类型,而是该类型将一直保留到显式更改。

手册说:

与 <- 对于 data.frame 不同,(可能很大)LHS 不会被强制匹配(通常很小)RHS 的类型。相反,如果需要,RHS 会被强制匹配 LHS 的类型。如果这涉及将双精度值强制转换为整数列,则会给出警告(无论小数数据是否被截断)。这样做的动机是效率。最好在前面正确地获取列类型并坚持使用它们。更改列类型是可能的,但故意更难:提供一整列作为 RHS。然后将此 RHS 插入该列槽中,我们称之为 plonk 语法,或者如果您愿意,可以替换列语法。通过需要构建一个新类型的全长向量,作为用户的您更了解正在发生的事情,

到现在为止还挺好。但是,当然,原始错误消息令人困惑。

 # To check and coerce would impact performance too much for the fastest cases. 

“对于最快的情况?”。这一定是最快的情况之一,因为数据集在微观上很小,我敢打赌,如果data.table允许隐式类型转换,在这种情况下没有人会注意到性能的影响。所以这个错误消息的主要动机似乎是包作者想要强制执行他认为是好的做法。

这也将起作用(没有类型转换):

 DT[, c := ifelse(v1 < 0 & v2 > 0, as.numeric(min(-v1, v2)), 0), by = list(date, id)]  # 1

或者:

 DT[, c := ifelse(v1 < 0 & v2 > 0, min(-v1, v2), 0L), by = list(date, id)] # 2

但是你不能连续运行最后两行——#1 和#2——c必须先删除该列。DT$c在第一种情况下是数字,在第二种情况下是整数。

一些额外的实验

DT[, c:= NULL] 
DT[, c := ifelse(v1 < 0, v1, 0), by = list(date, id)] 
# error but DT$c col created with first element as NA
# the condition was FALSE for the first element, so numeric 0 became the first element of c
# ... but the next element would be integer, hence the error
DT$c # [1]  0 NA NA NA NA NA NA NA NA
DT[, c:= NULL] 
DT[, c := ifelse(v1 > 0, v1, 0), by = list(date, id)]
# error; DT$c column is integer, with 5 as first element and the rest as NA 
DT$c # [1]  5 NA NA NA NA NA NA NA NA
DT[, c:= NULL] 
DT[, c := ifelse(v1 < 0, as.numeric(v1), 0), by = list(date, id)] 
# works without error but results in numeric DT$c
is.numeric(DT$c) # TRUE
DT[, c := ifelse(v1 < 0, v1, 0L), by = list(date, id)]
# type error, DT$c was numeric and we are trying to add an integer column
DT[, c:= NULL] # deleting the c column again
DT[, c := ifelse(v1 < 0, v1, 0L), by = list(date, id)]
# no error now
is.integer(DT$c) # TRUE

推荐阅读