ifelse()函数允许通过一系列测试过滤向量中的值,每个测试都会产生不同的操作,以防结果为正。例如,让xx
为data.frame,如下所示:
xx <- data.frame(a=c(1,2,1,3), b=1:4)
xx
a b
1 1
2 2
1 3
3 4
假设您要从 b 列创建新列 c ,但取决于 a 列中的值以下方式:
对于每一行,
使用 ifelse(),解决方案可能是:
xx$c <- ifelse(xx$a==1, xx$b,
ifelse(xx$a==2, xx$b*100,
-xx$b))
xx
a b c
1 1 1
2 2 200
1 3 3
3 4 -4
当测试次数增加时,例如四次测试会出现美学问题:
xx$c <- ifelse(xx$a==1, xx$b,
ifelse(xx$a==2, xx$b*100,
ifelse(xx$a==3, ...,
ifelse(xx$a==4, ...,
...))))
我在this page找到了问题的部分解决方案,其中包括函数 if.else_(),i_(),e _()的定义,如下所示: / p>
library(lazyeval)
i_ <- function(if_stat, then) {
if_stat <- lazyeval::expr_text(if_stat)
then <- lazyeval::expr_text(then)
sprintf("ifelse(%s, %s, ", if_stat, then)
}
e_ <- function(else_ret) {
else_ret <- lazyeval::expr_text(else_ret)
else_ret
}
if.else_ <- function(...) {
args <- list(...)
for (i in 1:(length(args) - 1) ) {
if (substr(args[[i]], 1, 6) != "ifelse") {
stop("All but the last argument, need to be if.then_ functions.", call. = FALSE)
}
}
if (substr(args[[length(args)]], 1, 6) == "ifelse"){
stop("Last argument needs to be an else_ function.", call. = FALSE)
}
args$final <- paste(rep(')', length(args) - 1), collapse = '')
eval_string <- do.call('paste', args)
eval(parse(text = eval_string))
}
通过这种方式,描述中给出的问题可以重写如下:
xx <- data.frame(a=c(1,2,1,3), b=1:4)
xx$c <- if.else_(
i_(xx$a==1, xx$b),
i_(xx$a==2, xx$b*100),
e_(-xx$b)
)
xx
a b c
1 1 1
2 2 200
1 3 3
3 4 -4
四个测试的代码只是:
xx$c <- if.else_(
i_(xx$a==1, xx$b),
i_(xx$a==2, xx$b*100),
i_(xx$a==3, ...), # dots meaning actions for xx$a==3
i_(xx$a==4, ...), # dots meaning actions for xx$a==4
e_(...) # dots meaning actions for any other case
)
给定的代码显然解决了这个问题。然后,我编写了以下测试函数:
test.ie <- function() {
dd <- data.frame(a=c(1,2,1,3), b=1:4)
if.else_(
i_(dd$a==1, dd$b),
i_(dd$a==2, dd$b*100),
e_(-dd$b)
) # it should give c(1, 200, 3, -4)
}
当我尝试测试时:
test.ie()
它吐出以下错误消息:
ifelse错误(dd $ a == 1,dd $ b,ifelse(dd $ a == 2,dd $ b * 100,-dd $ b)):
对象&#39; dd&#39;找不到
由于 if.else _()语法构造函数不应仅从控制台运行,因此有一种方法可以让它知道&#39;来自调用它的函数的变量?
在&#34; Best way to replace a lengthy ifelse structure in R&#34;中,发布了类似的问题。但是,给定的解决方案侧重于使用给定的常量输出值(&#34;然后&#34;或&#34;其他&#34;插槽)构建表格的新列ifelse()函数),而我的案例解决了一个句法问题,其中&#34;然后&#34;或&#34;否则&#34; slot甚至可以是其他data.frame元素或变量的表达式。
答案 0 :(得分:8)
我认为你可以在dplyr::case_when
内使用dplyr::mutate
来实现这一目标。
library(dplyr)
df <- tibble(a=c(1,2,1,3), b=1:4)
df %>%
mutate(
foo = case_when(
.$a == 1 ~ .$b,
.$a == 2 ~ .$b * 100L,
TRUE ~ .$b * -1L
)
)
#> # A tibble: 4 x 3
#> a b foo
#> <dbl> <int> <int>
#> 1 1 1 1
#> 2 2 2 200
#> 3 1 3 3
#> 4 3 4 -4
在即将到来的dplyr 0.6.0
中,你不需要使用.$
的笨拙工作,你可以使用:
df %>%
mutate(
foo = case_when(
a == 1 ~ b,
a == 2 ~ b * 100L,
TRUE ~ b * -1L
)
)
答案 1 :(得分:2)
考虑到MrFlick's建议,我重新编码了 if.else _()函数,如下所示:
if.else_ <- function(...) {
args <- list(...)
for (i in 1:(length(args) - 1) ) {
if (substr(args[[i]], 1, 6) != "ifelse") {
stop("All but the last argument, need to be if.then_ functions.", call. = FALSE)
}
}
if (substr(args[[length(args)]], 1, 6) == "ifelse"){
stop("Last argument needs to be an else_ function.", call. = FALSE)
}
args$final <- paste(rep(')', length(args) - 1), collapse = '')
eval_string <- do.call('paste', args)
eval(parse(text = eval_string), envir = parent.frame())
}
现在 test.ie()功能正常运行
test.ie()
[1] 1 200 3 -4
答案 2 :(得分:1)
在充分尊重OP改进嵌套ifelse()
的卓越努力的情况下,我更喜欢采用不同的方法,我认为这种方法易于编写,简洁,可维护且快速:
xx <- data.frame(a=c(1L,2L,1L,3L), b=1:4)
library(data.table)
# coerce to data.table, and set the default first
setDT(xx)[, c:= -b]
xx[a == 1L, c := b] # 1st special case
xx[a == 2L, c := 100L*b] # 2nd special case, note use of integer 100L
# xx[a == 3L, c := ...] # other cases
# xx[a == 4L, c := ...]
#...
xx
# a b c
#1: 1 1 1
#2: 2 2 200
#3: 1 3 3
#4: 3 4 -4
请注意,对于第二个特殊情况b
乘以整数常量100L
,以确保右侧都是整数类型,以避免类型转换为double。
编辑2:这也可以作为单行以更简洁(但仍可维护)的方式编写:
setDT(xx)[, c:= -b][a == 1L, c := b][a == 2L, c := 100*b][]
data.table
链接在这里工作,因为c
已更新到位,以便后续表达式作用于{em>所有行{{1}即使前一个表达式是对行子集的选择性更新。
编辑1:此方法也可以使用基本R实现:
xx