R语言 如何使用包含“if”语句的函数和shift函数有条件地为data.table中的列赋值?

5gfr0r5j  于 2023-06-03  发布在  其他
关注(0)|答案(2)|浏览(182)

我想使用data. table根据另外两列中的值有条件地生成一列中的输出。这是我想使用一个包含'if'语句的函数来实现的,因为我只是想学习如何使用data. table。if语句包含shift(),我认为这可能是我的问题的原因。
到目前为止,我所尝试的是以下内容:

library(data.table)

#Data

DT <- data.table(V1 = sample(LETTERS[1:3], 20, replace = TRUE),
                 V2 = sample(1:5, 20, replace = TRUE))

#function

fun1 <- function(x, y){
  if(x == "C" & shift(y,  type = "lead") > y){
    return("Greater")
  } else if(x == "C" & shift(y,  type = "lead") < y){
    return("Lesser")
  } else{
    return(NA)
  }
}

#function implementation

DT.v1 <- DT[, V3 := mapply(fun1, x = V1, y = V2)]

如果我运行上面的代码,我会得到错误:

Error in if (x == "C" & shift(y, type = "lead") > y) { : 
  missing value where TRUE/FALSE needed

我有一个暗示,这个错误可能是由于在最后一次迭代中与NA进行比较而导致的,因为shift(y, type = "lead")将等于NA。在“if”语句中添加条件!is.na(shift(y, type = "lead"))确实阻止了错误的出现,但导致仅生成NULL值。
我已经能够生成我想要的输出(见下面的脚本),但也想学习如何在函数中使用“if”语句来实现这一点。

DT.v2 <- DT[V1 == "C" & shift(V2, type = "lead") > V2, V3 := "Greater"][
            V1 == "C" & shift(V2, type = "lead") < V2, V3 := "Lesser"]

#or an alternative way to generate the desired output:

DT.v3 <- DT[, V3 := ifelse(V1 == "C" & shift(V2, type = "lead") > V2, "Greater",
                           ifelse(V1 == "C" & shift(V2, type = "lead") < V2, "Lesser", NA))]

有人能帮助我理解如何以正确的方式实现函数吗?感谢您抽出时间来帮助!

z9smfwbn

z9smfwbn1#

mapply的使用包含了shift查看正在处理的行周围数据的功能。
顺便说一句,我将if语句中的单个-&替换为&&,您永远不应该在那里使用&,除非它是聚合的,例如,在sumanyall等中。(有关差异的讨论,请参见Difference between Boolean operators && and & and between || and | in R。)
一种方法是将移位后的数据作为参数传递给函数:

set.seed(42)
DT <- data.table(V1 = sample(LETTERS[1:3], 20, replace = TRUE),
                 V2 = sample(1:5, 20, replace = TRUE))

fun2 <- function(x, y, shifty) {
  if (x == "C" && isTRUE(shifty > y)) {
    return("Greater")
  } else if (x == "C" && isTRUE(shifty < y)) {
    return("Lesser")
  } else{
    return(NA)
  }
}

DT[, V3 := mapply(fun2, x = V1, y = V2, shifty = shift(V2, type="lead"))]
#         V1    V2      V3
#     <char> <int>  <char>
#  1:      A     4    <NA>
#  2:      A     5    <NA>
#  3:      A     5    <NA>
#  4:      A     5    <NA>
#  5:      B     4    <NA>
#  6:      B     2    <NA>
#  7:      B     4    <NA>
#  8:      A     3    <NA>
#  9:      C     2  Lesser
# 10:      C     1 Greater
# 11:      A     2    <NA>
# 12:      A     3    <NA>
# 13:      B     2    <NA>
# 14:      B     4    <NA>
# 15:      B     4    <NA>
# 16:      C     2 Greater
# 17:      C     5  Lesser
# 18:      A     4    <NA>
# 19:      A     5    <NA>
# 20:      C     4    <NA>
#         V1    V2      V3

在这个fun2中使用isTRUE是为了说明shifty将是NA的条件;避免这种情况的另一种方法是使用shifty=shift(V3, type="lead", fill=0),其中0是对数据和分析的上下文有意义的数字。
如果你不需要使用函数,另一个选择是使用fcase

DT[, V5 := fcase(
    V1 == "C" & shift(V2, type="lead") > V2, "Greater",
    V1 == "C" & shift(V2, type="lead") < V2, "Lesser" )]

使用fcase与基本if语句相比有一个有趣的事情:对于if,如果任何一个操作数是NA,并且您没有显式地考虑到这一点,那么条件本身将是NA,从而导致if语句失败(参见Error in if/while (condition) {: missing Value where TRUE/FALSE needed)。但对于fcase,情况并非如此:

if (NA == 1) 2 else 3
# Error in if (NA == 1) 2 else 3 : missing value where TRUE/FALSE needed
fcase(NA == 1, 2, TRUE, 3)
# [1] 3

相关地,虽然ifelse不会失败,但它也不一定能像我们想要的那样工作,而fifelse为我们提供了显式处理NA条件的选项:

ifelse(NA == 1, 2, 3)
# [1] NA
fifelse(NA == 1, 2, 3)
# [1] NA
fifelse(NA == 1, 2, 3, 4)
# [1] 4

这是由格式建议的(并记录在文档中):

formals(ifelse)
# $test
# $yes
# $no
formals(fifelse)
# $test
# $yes
# $no
# $na
# [1] NA
flvlnr44

flvlnr442#

最主要的问题,在NA问题之上,你正确地指出了if语句需要长度为1的条件

a <- 1:10
> if(a>5)
+ { print("hello")}
Error in if (a > 5) { : the condition has length > 1

fun1正在传递一个向量- update fun 1没有传递一个向量,请参见下面的注解。
在我看来,你的第二种选择是做这项工作的正确方法。

DT.v2 <- DT[V1 == "C" & shift(V2, type = "lead") > V2, V3 := "Greater"][
            V1 == "C" & shift(V2, type = "lead") < V2, V3 := "Lesser"]

这是不太容易出错的。如果你真的需要使用if else语句,我建议检查?fcase

相关问题