dplyr::mutate 中是否有类似 switch 的功能?
Is there a function like switch which works inside of dplyr::mutate?
我不能在 mutate
内部使用 switch,因为它 returns 整个向量而不只是行。作为黑客,我正在使用:
pick <- function(x, v1, v2, v3, v4) {
ifelse(x == 1, v1,
ifelse(x == 2, v2,
ifelse(x == 3, v3,
ifelse(x == 4, v4, NA))))
}
这在 mutate
内部有效,目前还不错,因为我通常会在 4 个选项中进行选择,但这可能会改变。你能推荐一个替代方案吗?
例如:
library(dplyr)
df.faithful <- tbl_df(faithful)
df.faithful$x <- sample(1:4, 272, rep=TRUE)
df.faithful$y1 <- rnorm(n=272, mean=7, sd=2)
df.faithful$y2 <- rnorm(n=272, mean=5, sd=2)
df.faithful$y3 <- rnorm(n=272, mean=7, sd=1)
df.faithful$y4 <- rnorm(n=272, mean=5, sd=1)
使用pick
:
mutate(df.faithful, y = pick(x, y1, y2, y3, y4))
Source: local data frame [272 x 8]
eruptions waiting x y1 y2 y3 y4 y
1 3.600 79 1 8.439092 5.7753006 8.319372 5.078558 8.439092
2 1.800 54 2 13.515956 6.1971512 6.343157 4.962349 6.197151
3 3.333 74 4 7.693941 6.8973365 5.406684 5.425404 5.425404
4 2.283 62 4 12.595852 6.9953995 7.864423 3.730967 3.730967
5 4.533 85 3 11.952922 5.1512987 9.177687 5.511899 9.177687
6 2.883 55 3 7.881350 1.0289711 6.304004 3.554056 6.304004
7 4.700 88 4 8.636709 6.3046198 6.788619 5.748269 5.748269
8 3.600 85 1 8.027371 6.3535056 7.152698 7.034976 8.027371
9 1.950 51 1 5.863370 0.1707758 5.750440 5.058107 5.863370
10 4.350 85 1 7.761653 6.2176610 8.348378 1.861112 7.761653
.. ... ... . ... ... ... ... ...
我们看到如果 x == 1,我将值从 y1 复制到 y,依此类推。这是我想要做的,但我希望能够做到,无论我有 4 列还是 400 列的列表。
正在尝试使用 switch
:
mutate(df.faithful, y = switch(x, y1, y2, y3, 4))
Error in switch(c(1L, 2L, 4L, 4L, 3L, 3L, 4L, 1L, 1L, 1L, 4L, 3L, 1L, :
EXPR must be a length 1 vector
正在尝试使用 list
:
mutate(df.faithful, y = list(y1, y2, y3, y4)[[x]])
Error in list(c(8.43909205142925, 13.5159559591257, 7.69394050059568, :
recursive indexing failed at level 2
正在尝试使用 c
:
mutate(df.faithful, y = c(y1, y2, y3, y4)[x])
Source: local data frame [272 x 8]
eruptions waiting x y1 y2 y3 y4 y
1 3.600 79 1 8.439092 5.7753006 8.319372 5.078558 8.439092
2 1.800 54 2 13.515956 6.1971512 6.343157 4.962349 13.515956
3 3.333 74 4 7.693941 6.8973365 5.406684 5.425404 12.595852
4 2.283 62 4 12.595852 6.9953995 7.864423 3.730967 12.595852
5 4.533 85 3 11.952922 5.1512987 9.177687 5.511899 7.693941
6 2.883 55 3 7.881350 1.0289711 6.304004 3.554056 7.693941
7 4.700 88 4 8.636709 6.3046198 6.788619 5.748269 12.595852
8 3.600 85 1 8.027371 6.3535056 7.152698 7.034976 8.439092
9 1.950 51 1 5.863370 0.1707758 5.750440 5.058107 8.439092
10 4.350 85 1 7.761653 6.2176610 8.348378 1.861112 8.439092
.. ... ... . ... ... ... ... ...
没有产生错误,但行为与预期不符。
你可以这样修改你的函数:
map <- data.frame(i=1:2,v=10:11)
# i v
# 1 1 10
# 2 2 11
set.seed(1)
x <- sample(1:3,10,rep=T)
# [1] 1 2 2 3 1 3 3 2 2 1
i <- match(x,map$i)
ifelse(is.na(i),x,map$v[i])
# [1] 10 11 11 3 10 3 3 11 11 10
想法是将您要查找的值和替换值保存在单独的数据框中 map
,然后使用 match
来匹配 x
和 map
.
[更新]
您可以将此解决方案打包成一个函数,以便在 mutate
:
中使用
multipleReplace <- function(x, what, by) {
stopifnot(length(what)==length(by))
ind <- match(x, what)
ifelse(is.na(ind),x,by[ind])
}
# Create a sample data set
d <- structure(list(x = c(1L, 2L, 2L, 3L, 1L, 3L, 3L, 2L, 2L, 1L), y = c(1L, 2L, 2L, 3L, 3L, 1L, 3L, 2L, 2L, 1L)), .Names = c("x", "y"), row.names = c(NA, -10L), class = "data.frame")
d %>%
mutate(z = multipleReplace(x, what=c(1,3), by=c(101,103)))
# x y z
# 1 1 1 101
# 2 2 2 2
# 3 2 2 2
# 4 3 3 103
# 5 1 3 101
# 6 3 1 103
# 7 3 3 103
# 8 2 2 2
# 9 2 2 2
# 10 1 1 101
对x
的各个值进行运算。这是 data.table
版本,我假设可以在 dplyr
:
中完成类似的操作
library(data.table)
dt = data.table(x = c(1,1,2,2), a = 1:4, b = 4:7)
dt[, newcol := switch(as.character(x), '1' = a, '2' = b, NA), by = x]
dt
# x a b newcol
#1: 1 1 4 1
#2: 1 2 5 2
#3: 2 3 6 6
#4: 2 4 7 7
另一种(更复杂的)路线涉及使用 tidyr
:
df %>%
mutate(row = row_number()) %>%
gather(n, y, y1:y4) %>%
mutate(n = as.integer(str_extract(n, "[0-9]+"))) %>%
filter(x == n) %>%
arrange(row) %>%
select(-c(row, n))
这是使用 data.table
的另一种方法。这个想法基本上是用这些组合创建一个 key data.table,然后执行一个 join,如下所示:
我将使用@eddi 的回答中的data.table。
require(data.table)
key = data.table(x = 1:2, col = c("a", "b"))
setkey(dt, x)
dt[key, new_col := get(i.col), by=.EACHI]
# x a b new_col
# 1: 1 1 4 1
# 2: 1 2 5 2
# 3: 2 3 6 6
# 4: 2 4 7 7
在列 x
上执行 join。对于 key 的每一行,找到 dt 中对应的匹配行。例如:来自 key 的 x = 1
匹配行 1 和 2 of dt。在这些行上,我们访问存储在 键的 col
中的列,即 "a"。 get("a")
returns 匹配行的 a
列的值,即 1 和 2。希望这有帮助。
by=.EACHI
确保对 key
中的每一行计算表达式 new_col := get(i.col)
。您可以了解更多 here.
我有点晚了,但这是我使用 mapply 的解决方案。
vswitch <- function(x, ...) {
mapply(FUN = function(x, ...) {
switch(x, ...)
}, x, ...)
}
mutate(df.faithful, y = vswitch(x, y1, y2, y3, y4))
OP 为时已晚,但万一这出现在搜索中...
dplyr v0.5 有 recode()
,switch()
的矢量化版本,所以
data_frame(
x = sample(1:4, 10, replace=TRUE),
y1 = rnorm(n=10, mean=7, sd=2),
y2 = rnorm(n=10, mean=5, sd=2),
y3 = rnorm(n=10, mean=7, sd=1),
y4 = rnorm(n=10, mean=5, sd=1)
) %>%
mutate(y = recode(x,y1,y2,y3,y4))
如预期的那样产生:
# A tibble: 10 x 6
x y1 y2 y3 y4 y
<int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2 6.950106 6.986780 7.826778 6.317968 6.986780
2 1 5.776381 7.706869 7.982543 5.048649 5.776381
3 2 7.315477 2.213855 6.079149 6.070598 2.213855
4 3 7.461220 5.100436 7.085912 4.440829 7.085912
5 3 5.780493 4.562824 8.311047 5.612913 8.311047
6 3 5.373197 7.657016 7.049352 4.470906 7.049352
7 2 6.604175 9.905151 8.359549 6.430572 9.905151
8 3 11.363914 4.721148 7.670825 5.317243 7.670825
9 3 10.123626 7.140874 6.718351 5.508875 6.718351
10 4 5.407502 4.650987 5.845482 4.797659 4.797659
(也适用于命名参数,包括字符和因子 x。)
您现在可以将 dplyr
的函数 case_when
与 mutate()
一起使用。
按照您的示例生成数据:
library(dplyr)
df.faithful <- tbl_df(faithful)
df.faithful$x <- sample(1:4, 272, rep=TRUE)
df.faithful$y1 <- rnorm(n=272, mean=7, sd=2)
df.faithful$y2 <- rnorm(n=272, mean=5, sd=2)
df.faithful$y3 <- rnorm(n=272, mean=7, sd=1)
df.faithful$y4 <- rnorm(n=272, mean=5, sd=1)
现在我们定义一个新的 pick()
函数使用 case_when
:
pick2 <- function(x, v1, v2, v3, v4) {
out = case_when(
x == 1 ~ v1,
x == 2 ~ v2,
x == 3 ~ v3,
x == 4 ~ v4
)
return(out)
}
你看你可以在 mutate()
:
内完美地使用它
df.faithful %>%
mutate(y = pick2(x, y1, y2, y3, y4))
输出为:
# A tibble: 272 x 8
eruptions waiting x y1 y2 y3 y4 y
<dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 3.6 79 3 8.73 7.23 8.89 4.04 8.89
2 1.8 54 3 9.97 4.31 7.06 5.05 7.06
3 3.33 74 1 6.65 7.23 4.46 6.49 6.65
4 2.28 62 1 6.40 4.39 5.41 3.49 6.40
5 4.53 85 4 3.96 8.85 7.43 6.51 6.51
6 2.88 55 4 6.36 8.08 5.82 5.06 5.06
7 4.7 88 1 5.91 6.47 6.43 5.88 5.91
8 3.6 85 1 7.77 4.55 6.56 5.05 7.77
9 1.95 51 4 5.74 6.46 6.95 4.26 4.26
10 4.35 85 1 7.04 1.73 5.71 2.53 7.04
# ... with 262 more rows
如果你想在mutate
中使用switch
你必须在
之前执行rowwise
iris %>%
rowwise() %>%
mutate(
x = switch(
as.character(Species),
'setosa' = 'ss',
'versicolor' = 'vc',
'virginica' = 'vg'
)
) %>%
ungroup()
比 user6702291 建议的解决方案更复杂的版本是使用映射函数,例如 map_dbl()。它更复杂,但我认为它值得分享,因为它更适用于您尝试使用的函数还没有矢量化版本的其他情况。
在这种情况下它会像这样工作。
tibble.faithful %>%
mutate(y = map_dbl(seq_along(x), ~switch(x[.x], y1, y2, y3, y4)[1]))
我实际上不确定,为什么需要“[1]” - 但我还是想分享它作为建议。
我不能在 mutate
内部使用 switch,因为它 returns 整个向量而不只是行。作为黑客,我正在使用:
pick <- function(x, v1, v2, v3, v4) {
ifelse(x == 1, v1,
ifelse(x == 2, v2,
ifelse(x == 3, v3,
ifelse(x == 4, v4, NA))))
}
这在 mutate
内部有效,目前还不错,因为我通常会在 4 个选项中进行选择,但这可能会改变。你能推荐一个替代方案吗?
例如:
library(dplyr)
df.faithful <- tbl_df(faithful)
df.faithful$x <- sample(1:4, 272, rep=TRUE)
df.faithful$y1 <- rnorm(n=272, mean=7, sd=2)
df.faithful$y2 <- rnorm(n=272, mean=5, sd=2)
df.faithful$y3 <- rnorm(n=272, mean=7, sd=1)
df.faithful$y4 <- rnorm(n=272, mean=5, sd=1)
使用pick
:
mutate(df.faithful, y = pick(x, y1, y2, y3, y4))
Source: local data frame [272 x 8]
eruptions waiting x y1 y2 y3 y4 y
1 3.600 79 1 8.439092 5.7753006 8.319372 5.078558 8.439092
2 1.800 54 2 13.515956 6.1971512 6.343157 4.962349 6.197151
3 3.333 74 4 7.693941 6.8973365 5.406684 5.425404 5.425404
4 2.283 62 4 12.595852 6.9953995 7.864423 3.730967 3.730967
5 4.533 85 3 11.952922 5.1512987 9.177687 5.511899 9.177687
6 2.883 55 3 7.881350 1.0289711 6.304004 3.554056 6.304004
7 4.700 88 4 8.636709 6.3046198 6.788619 5.748269 5.748269
8 3.600 85 1 8.027371 6.3535056 7.152698 7.034976 8.027371
9 1.950 51 1 5.863370 0.1707758 5.750440 5.058107 5.863370
10 4.350 85 1 7.761653 6.2176610 8.348378 1.861112 7.761653
.. ... ... . ... ... ... ... ...
我们看到如果 x == 1,我将值从 y1 复制到 y,依此类推。这是我想要做的,但我希望能够做到,无论我有 4 列还是 400 列的列表。
正在尝试使用 switch
:
mutate(df.faithful, y = switch(x, y1, y2, y3, 4))
Error in switch(c(1L, 2L, 4L, 4L, 3L, 3L, 4L, 1L, 1L, 1L, 4L, 3L, 1L, :
EXPR must be a length 1 vector
正在尝试使用 list
:
mutate(df.faithful, y = list(y1, y2, y3, y4)[[x]])
Error in list(c(8.43909205142925, 13.5159559591257, 7.69394050059568, :
recursive indexing failed at level 2
正在尝试使用 c
:
mutate(df.faithful, y = c(y1, y2, y3, y4)[x])
Source: local data frame [272 x 8]
eruptions waiting x y1 y2 y3 y4 y
1 3.600 79 1 8.439092 5.7753006 8.319372 5.078558 8.439092
2 1.800 54 2 13.515956 6.1971512 6.343157 4.962349 13.515956
3 3.333 74 4 7.693941 6.8973365 5.406684 5.425404 12.595852
4 2.283 62 4 12.595852 6.9953995 7.864423 3.730967 12.595852
5 4.533 85 3 11.952922 5.1512987 9.177687 5.511899 7.693941
6 2.883 55 3 7.881350 1.0289711 6.304004 3.554056 7.693941
7 4.700 88 4 8.636709 6.3046198 6.788619 5.748269 12.595852
8 3.600 85 1 8.027371 6.3535056 7.152698 7.034976 8.439092
9 1.950 51 1 5.863370 0.1707758 5.750440 5.058107 8.439092
10 4.350 85 1 7.761653 6.2176610 8.348378 1.861112 8.439092
.. ... ... . ... ... ... ... ...
没有产生错误,但行为与预期不符。
你可以这样修改你的函数:
map <- data.frame(i=1:2,v=10:11)
# i v
# 1 1 10
# 2 2 11
set.seed(1)
x <- sample(1:3,10,rep=T)
# [1] 1 2 2 3 1 3 3 2 2 1
i <- match(x,map$i)
ifelse(is.na(i),x,map$v[i])
# [1] 10 11 11 3 10 3 3 11 11 10
想法是将您要查找的值和替换值保存在单独的数据框中 map
,然后使用 match
来匹配 x
和 map
.
[更新]
您可以将此解决方案打包成一个函数,以便在 mutate
:
multipleReplace <- function(x, what, by) {
stopifnot(length(what)==length(by))
ind <- match(x, what)
ifelse(is.na(ind),x,by[ind])
}
# Create a sample data set
d <- structure(list(x = c(1L, 2L, 2L, 3L, 1L, 3L, 3L, 2L, 2L, 1L), y = c(1L, 2L, 2L, 3L, 3L, 1L, 3L, 2L, 2L, 1L)), .Names = c("x", "y"), row.names = c(NA, -10L), class = "data.frame")
d %>%
mutate(z = multipleReplace(x, what=c(1,3), by=c(101,103)))
# x y z
# 1 1 1 101
# 2 2 2 2
# 3 2 2 2
# 4 3 3 103
# 5 1 3 101
# 6 3 1 103
# 7 3 3 103
# 8 2 2 2
# 9 2 2 2
# 10 1 1 101
对x
的各个值进行运算。这是 data.table
版本,我假设可以在 dplyr
:
library(data.table)
dt = data.table(x = c(1,1,2,2), a = 1:4, b = 4:7)
dt[, newcol := switch(as.character(x), '1' = a, '2' = b, NA), by = x]
dt
# x a b newcol
#1: 1 1 4 1
#2: 1 2 5 2
#3: 2 3 6 6
#4: 2 4 7 7
另一种(更复杂的)路线涉及使用 tidyr
:
df %>%
mutate(row = row_number()) %>%
gather(n, y, y1:y4) %>%
mutate(n = as.integer(str_extract(n, "[0-9]+"))) %>%
filter(x == n) %>%
arrange(row) %>%
select(-c(row, n))
这是使用 data.table
的另一种方法。这个想法基本上是用这些组合创建一个 key data.table,然后执行一个 join,如下所示:
我将使用@eddi 的回答中的data.table。
require(data.table)
key = data.table(x = 1:2, col = c("a", "b"))
setkey(dt, x)
dt[key, new_col := get(i.col), by=.EACHI]
# x a b new_col
# 1: 1 1 4 1
# 2: 1 2 5 2
# 3: 2 3 6 6
# 4: 2 4 7 7
在列 x
上执行 join。对于 key 的每一行,找到 dt 中对应的匹配行。例如:来自 key 的 x = 1
匹配行 1 和 2 of dt。在这些行上,我们访问存储在 键的 col
中的列,即 "a"。 get("a")
returns 匹配行的 a
列的值,即 1 和 2。希望这有帮助。
by=.EACHI
确保对 key
中的每一行计算表达式 new_col := get(i.col)
。您可以了解更多 here.
我有点晚了,但这是我使用 mapply 的解决方案。
vswitch <- function(x, ...) {
mapply(FUN = function(x, ...) {
switch(x, ...)
}, x, ...)
}
mutate(df.faithful, y = vswitch(x, y1, y2, y3, y4))
OP 为时已晚,但万一这出现在搜索中...
dplyr v0.5 有 recode()
,switch()
的矢量化版本,所以
data_frame(
x = sample(1:4, 10, replace=TRUE),
y1 = rnorm(n=10, mean=7, sd=2),
y2 = rnorm(n=10, mean=5, sd=2),
y3 = rnorm(n=10, mean=7, sd=1),
y4 = rnorm(n=10, mean=5, sd=1)
) %>%
mutate(y = recode(x,y1,y2,y3,y4))
如预期的那样产生:
# A tibble: 10 x 6
x y1 y2 y3 y4 y
<int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 2 6.950106 6.986780 7.826778 6.317968 6.986780
2 1 5.776381 7.706869 7.982543 5.048649 5.776381
3 2 7.315477 2.213855 6.079149 6.070598 2.213855
4 3 7.461220 5.100436 7.085912 4.440829 7.085912
5 3 5.780493 4.562824 8.311047 5.612913 8.311047
6 3 5.373197 7.657016 7.049352 4.470906 7.049352
7 2 6.604175 9.905151 8.359549 6.430572 9.905151
8 3 11.363914 4.721148 7.670825 5.317243 7.670825
9 3 10.123626 7.140874 6.718351 5.508875 6.718351
10 4 5.407502 4.650987 5.845482 4.797659 4.797659
(也适用于命名参数,包括字符和因子 x。)
您现在可以将 dplyr
的函数 case_when
与 mutate()
一起使用。
按照您的示例生成数据:
library(dplyr)
df.faithful <- tbl_df(faithful)
df.faithful$x <- sample(1:4, 272, rep=TRUE)
df.faithful$y1 <- rnorm(n=272, mean=7, sd=2)
df.faithful$y2 <- rnorm(n=272, mean=5, sd=2)
df.faithful$y3 <- rnorm(n=272, mean=7, sd=1)
df.faithful$y4 <- rnorm(n=272, mean=5, sd=1)
现在我们定义一个新的 pick()
函数使用 case_when
:
pick2 <- function(x, v1, v2, v3, v4) {
out = case_when(
x == 1 ~ v1,
x == 2 ~ v2,
x == 3 ~ v3,
x == 4 ~ v4
)
return(out)
}
你看你可以在 mutate()
:
df.faithful %>%
mutate(y = pick2(x, y1, y2, y3, y4))
输出为:
# A tibble: 272 x 8
eruptions waiting x y1 y2 y3 y4 y
<dbl> <dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 3.6 79 3 8.73 7.23 8.89 4.04 8.89
2 1.8 54 3 9.97 4.31 7.06 5.05 7.06
3 3.33 74 1 6.65 7.23 4.46 6.49 6.65
4 2.28 62 1 6.40 4.39 5.41 3.49 6.40
5 4.53 85 4 3.96 8.85 7.43 6.51 6.51
6 2.88 55 4 6.36 8.08 5.82 5.06 5.06
7 4.7 88 1 5.91 6.47 6.43 5.88 5.91
8 3.6 85 1 7.77 4.55 6.56 5.05 7.77
9 1.95 51 4 5.74 6.46 6.95 4.26 4.26
10 4.35 85 1 7.04 1.73 5.71 2.53 7.04
# ... with 262 more rows
如果你想在mutate
中使用switch
你必须在
rowwise
iris %>%
rowwise() %>%
mutate(
x = switch(
as.character(Species),
'setosa' = 'ss',
'versicolor' = 'vc',
'virginica' = 'vg'
)
) %>%
ungroup()
比 user6702291 建议的解决方案更复杂的版本是使用映射函数,例如 map_dbl()。它更复杂,但我认为它值得分享,因为它更适用于您尝试使用的函数还没有矢量化版本的其他情况。
在这种情况下它会像这样工作。
tibble.faithful %>%
mutate(y = map_dbl(seq_along(x), ~switch(x[.x], y1, y2, y3, y4)[1]))
我实际上不确定,为什么需要“[1]” - 但我还是想分享它作为建议。