当突变是有条件的(取决于某些列值的值)时,是否可以使用突变?
这个例子有助于说明我的意思。
structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4,
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4,
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4,
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA,
8L), class = "data.frame")
a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2
我希望使用 dplyr 包找到我的问题的解决方案(是的,我知道这不是应该工作的代码,但我想它使目的明确)用于创建新列 g:
library(dplyr)
df <- mutate(df,
if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
if (a == 0 | a == 1 | a == 4 | a == 3 | c == 4) {g = 3})
我正在寻找的代码的结果在这个特定的例子中应该有这个结果:
a b c d e f g
1 1 1 6 6 1 2 3
2 3 3 3 2 2 3 3
3 4 4 6 4 4 4 3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7 2
7 5 2 5 2 6 5 2
8 1 6 3 6 3 2 3
有谁知道如何在 dplyr 中做到这一点?这个数据框只是一个例子,我正在处理的数据框要大得多。由于它的速度,我尝试使用 dplyr,但也许还有其他更好的方法来处理这个问题?
dplyr::case_when()
比 ifelse
清晰得多,
使用ifelse
df %>%
mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
ifelse(a == 0 | a == 1 | a == 4 | a == 3 | c == 4, 3, NA)))
已添加 - if_else: 请注意,在 dplyr 0.5 中定义了一个 if_else
函数,因此替代方法是将 ifelse
替换为 if_else
;但是,请注意,由于 if_else
比 ifelse
更严格(条件的两条腿必须具有相同的类型),因此在这种情况下 NA
必须替换为 NA_real_
。
df %>%
mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
if_else(a == 0 | a == 1 | a == 4 | a == 3 | c == 4, 3, NA_real_)))
已添加 - case_when 由于发布了此问题,dplyr 添加了 case_when
,因此另一种选择是:
df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
a == 0 | a == 1 | a == 4 | a == 3 | c == 4 ~ 3,
TRUE ~ NA_real_))
添加 - 算术/na_if 如果值是数字并且条件(末尾的默认值 NA 除外)是互斥的,就像问题中的情况一样,那么我们可以使用算术表达式使得每一项都乘以所需的结果,最后使用 na_if
将 0 替换为 NA。
df %>%
mutate(g = 2 * (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)) +
3 * (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
g = na_if(g, 0))
由于您要求其他更好的方法来处理该问题,这里有另一种使用 data.table
的方法:
require(data.table)
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
请注意,条件语句的顺序颠倒了才能正确获得 g
。即使在第二次分配期间,也没有制作 g
的副本 - 它被就地替换。
在较大的数据上,这将比使用 嵌套 if-else
(作为 it can evaluate both 'yes' and 'no' cases)具有更好的性能,并且嵌套可能更难以阅读/维护恕我直言。
这是相对较大数据的基准:
# NB: benchmark timings are as of R 3.1.0, data.table v1.9.2
require(data.table)
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT)
# [1] 10000000 6
DF <- as.data.frame(DT)
DT_fun <- function(DT) {
DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}
DPLYR_fun <- function(DF) {
mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
BASE_fun <- function(DF) { # R v3.1.0
transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
system.time(ans1 <- DT_fun(DT))
# user system elapsed
# 2.659 0.420 3.107
system.time(ans2 <- DPLYR_fun(DF))
# user system elapsed
# 11.822 1.075 12.976
system.time(ans3 <- BASE_fun(DF))
# user system elapsed
# 11.676 1.530 13.319
identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE
identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE
不确定这是否是您要求的替代方法,但我希望它有所帮助。
DT_fun
正在就地修改其输入,因此基准测试可能不太公平 - 除了没有从第二次迭代中接收到相同的输入(这可能会影响时间,因为 DT$g
已经分配?),结果也会传播回到 ans1
,因此可能(如果 R 的优化器认为有必要?对此不确定...)避免 另一个 复制 DPLYR_fun
和 BASE_fun
需要制作?
data.table
解决方案很棒,而且我在我真正需要速度来处理表和操作的任何地方都使用 data.table
。我不想一直使用 C++。不过,它确实需要非常小心地进行修改!
dplyr 现在有一个函数 case_when
,它提供了一个向量化的 if。与mosaic:::derivedFactor
相比,语法有点奇怪,因为您无法以标准的dplyr方式访问变量,并且需要声明NA的模式,但它比mosaic:::derivedFactor
快得多。
df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L,
a %in% c(0,1,3,4) | c == 4 ~ 3L,
TRUE~as.integer(NA)))
编辑:如果您使用的是软件包 0.7.0 之前的 dplyr::case_when()
,那么您需要在变量名前加上“.$
”(例如,在 case_when
中写入 .$a == 1
)。
基准测试:对于基准测试(重用 Arun 帖子中的函数)并减少样本量:
require(data.table)
require(mosaic)
require(dplyr)
require(microbenchmark)
set.seed(42) # To recreate the dataframe
DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)
DPLYR_case_when <- function(DF) {
DF %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L,
a %in% c(0,1,3,4) | c==4 ~ 3L,
TRUE~as.integer(NA)))
}
DT_fun <- function(DT) {
DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}
DPLYR_fun <- function(DF) {
mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
mosa_fun <- function(DF) {
mutate(DF, g = derivedFactor(
"2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
"3" = (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
.method = "first",
.default = NA
))
}
perf_results <- microbenchmark(
dt_fun <- DT_fun(copy(DT)),
dplyr_ifelse <- DPLYR_fun(copy(DF)),
dplyr_case_when <- DPLYR_case_when(copy(DF)),
mosa <- mosa_fun(copy(DF)),
times = 100L
)
这给出了:
print(perf_results)
Unit: milliseconds
expr min lq mean median uq max neval
dt_fun 1.391402 1.560751 1.658337 1.651201 1.716851 2.383801 100
dplyr_ifelse 1.172601 1.230351 1.331538 1.294851 1.390351 1.995701 100
dplyr_case_when 1.648201 1.768002 1.860968 1.844101 1.958801 2.207001 100
mosa 255.591301 281.158350 291.391586 286.549802 292.101601 545.880702 100
case_when
也可以写成:df %>% mutate(g = with(., case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L, a %in% c(0,1,3,4) | c==4 ~ 3L, TRUE ~ NA_integer_)))
.$
DT[, col1 := case_when(col2 > value ~ TRUE)]
和 DT[col2 > value, col1 := TRUE]
有什么区别?
mosaic
包中的 derivedFactor
函数似乎旨在处理此问题。使用这个例子,它看起来像:
library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
"2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
"3" = (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
.method = "first",
.default = NA
))
(如果您希望结果是数字而不是因子,可以将 derivedFactor
包装在 as.numeric
调用中。)
derivedFactor
也可用于任意数量的条件。
.asFactor = F
选项或使用同一包中的(类似)derivedVariable
函数来防止结果成为因素。
recode
看起来会执行此操作。不过我还没有调查过。请参阅blog.rstudio.org/2016/06/27/dplyr-0-5-0
在以下情况下,case_when
现在是 SQL 风格案例的一个非常干净的实现:
structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4,
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4,
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4,
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA,
8L), class = "data.frame") -> df
df %>%
mutate( g = case_when(
a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 ) ~ 2,
a == 0 | a == 1 | a == 4 | a == 3 | c == 4 ~ 3
))
使用 dplyr 0.7.4
手册:http://dplyr.tidyverse.org/reference/case_when.html
不定期副业成功案例分享
NA
,那么逻辑是什么?mutate(g = ifelse(condition1, 2, ifelse(condition2, 3, g))
if_else()
和ifelse()
之间的区别。您能否正确使用ifelse()
和mutate
函数?是否有任何建议使用dplyr::if_else()
而不是base:ifelse()
- 即使用更严格的if_else()
可以避免下游不必要的问题?