r-仅当变量存在时才执行突变函数



我有一个函数,它将特定函数应用于数据帧中的多列。这些函数中的每一个都是唯一的,只能应用于该列。

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)
)
}

然而,用户可以输入仅具有这些列的子集的数据帧(例如,仅abc。如果输入的数据帧中存在abc列,则我希望函数为mutate列,而忽略de列。

我试过

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"的向量ℹ输入csd(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)

最新更新