我有一个函数,它将特定函数应用于数据帧中的多列。这些函数中的每一个都是唯一的,只能应用于该列。
convert_columns <- function(df) {
df %>% mutate(
a = convert_a(a),
b = convert_b(b),
c = convert_c(c),
d = convert_d(d),
e = convert_e(e)
)
}
然而,用户可以输入仅具有这些列的子集的数据帧(例如,仅a
、b
和c
。如果输入的数据帧中存在a
、b
和c
列,则我希望函数为mutate
列,而忽略d
和e
列。
我试过
convert_columns <- function(df) {
df %>% mutate(across(any of(),
a = convert_a(a),
b = convert_b(b),
c = convert_c(c),
d = convert_d(d),
e = convert_e(e)
))
}
和
convert_columns <- function(df) {
df %>% mutate(across(any of(
a = convert_a(a),
b = convert_b(b),
c = convert_c(c),
d = convert_d(d),
e = convert_e(e)
)))
}
这些不起作用。在tidyverse
语法中有没有一种简单的方法来完成我想要做的事情?在我的实际用例中,我有大约150列要进行更改。
由于函数对每个变量都是唯一的,并且如果其中一列失败,您希望返回剩余值,因此无法找到比在单个列上使用tryCatch
更好的解决方案。
library(dplyr)
convert_columns <- function(df) {
df %>%
mutate(
a = tryCatch(convert_a(a),error = function(z) return(NA)),
b = tryCatch(convert_b(b),error = function(z) return(NA)),
c = tryCatch(convert_c(c),error = function(z) return(NA)),
#...
#...
)
}
这可以使用以下mtcars
示例进行测试:
这是有效的-
mtcars %>%
mutate(a = n_distinct(cyl),
b = mean(mpg),
c = sd(am))
现在,如果我们删除其中一个列,上面的失败:
mtcars %>%
select(-am) %>%
mutate(a = n_distinct(cyl),
b = mean(mpg),
c = sd(am))
错误:
mutate()
输入c
出现问题。x不能将类型"闭包"强制为类型"double"的向量ℹ输入c
为sd(am)
。
现在使用tryCatch
mtcars %>%
select(-am) %>%
mutate(a = tryCatch(n_distinct(cyl), error = function(e) return(NA)),
b = tryCatch(mean(mpg), error = function(e) return(NA)),
c = tryCatch(sd(am), error = function(e) return(NA)))
# mpg cyl disp hp drat wt qsec vs gear carb a b c
#1 21 6 160 110 3.9 2.6 16 0 4 4 3 20 NA
#2 21 6 160 110 3.9 2.9 17 0 4 4 3 20 NA
#3 23 4 108 93 3.9 2.3 19 1 4 1 3 20 NA
#4 21 6 258 110 3.1 3.2 19 1 3 1 3 20 NA
#....
您可以使用switch()
来获取基于列名的特定函数。例如,在这里,列a、b和c可以根据列名进行相加、相减或相乘。我们必须使用dplyr::cur_column()
来获得跨中的列名(deparse(substitute())
只返回"col"
(。
因此,使用下面的方法,您可以只为across()
提供一个函数,但将特定的函数应用于每列,同时获得any_of()
的好处
library(dplyr)
ex <- function(x) {
arg <- cur_column()
fn <- switch(arg,
a = `+`,
b = `-`,
c = `*`)
fn(x, x)
}
df <- data.frame(a = c(1,2),
b = c(3,4))
mutate(df, across(any_of(c("a", "b", "c")), ex))
#> a b
#> 1 2 0
#> 2 4 0
使用data.table
:
existing_cols <- c("a", "b", "c", "d") %>% intersect(names(df))
setDT(df)
if(length(existing_cols) > 0)
df[,
(existing_cols) := map2(.SD, str_c("convert_", existing_cols), ~do.call(.y, list(.x))),
.SDcols = existing_cols
]
这在基R中是直接的。必须有某种方法将函数与列名相关联,所以让我们假设我们有一个函数或函数名的命名向量funs
。然后循环遍历数据帧列,在funs
中查找列名,并将相应的函数应用于每一列。
convert_coiumns
的第一个自变量是数据帧,第二个自变量是函数(或函数名(的命名向量,第三个自变量是要转换的列的字符向量。最后一个参数默认为funs
中有函数的所有列。如果每个列都必须有相应的函数,那么最后一个参数的默认值可以简化为names(data)
。
在内部,match.fun
取一个函数或函数名,即字符串,并在每种情况下返回函数,允许函数包含函数、函数名或混合。
convert_columns <- function(data, funs,
nms = intersect(names(data), names(funs))) {
for(nm in nms) data[[nm]] <- match.fun(funs[[nm]])(data[[nm]])
data
}
# example 1 - uses built in BOD data frame
funs <- c(Time = sqrt, demand = mean)
convert_columns(BOD, funs)
# example 2 - same but use function names rather than functions themselves
funs2 <- c(Time = "sqrt", demand = "mean")
convert_columns(BOD, funs2)
# example 3 - DF does not have column b
funs3 <- c(a = sqrt, b = sum, c = mean)
DF <- data.frame(a = 1:3, c = 3:1)
convert_columns(DF, funs3)
# example 4 - grab functions from global environment - same DF
convert_a <- sum; convert_b <- prod; convert_c <- sqrt
funs4 <- mget(ls(pattern = "^convert_"))
names(funs4) <- sub("convert_", "", names(funs4)) # remove convert_ from names
convert_columns(DF, funs4)
# example 5 - similar to 4
funs5 <- setNames(paste("convert", names(DF), sep = "_"), names(DF))
convert_columns(DF, funs5)