Я хочу прикрепить вызов функции как attributute. Хотя использование fn() напрямую работает нормально, когда я запускаю его внутри lapply(), он возвращает X[[i]] вместо фактического имени, переданного в параметр d=. Я ожидаю увидеть на выходе df1 и df2, а не X[[i]].
> fn <- \(d, x=x, y=999, ...) {
+ cl <- match.call()
+ fa <- formals(fn)
+ fa[length(fa)] <- NULL
+ ma <- setdiff(names(fa), names(cl))
+ cl[ma] <- fa[ma]
+ cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
+ # cl$d <- quote(d)
+ res <- d[[1]] ## so `res` depends on `d`
+ attr(res, 'call') <- cl
+ return(res)
+ }
>
> z. <- 777
> df1 <- lst$df1
> df2 <- lst$df2
> fn(df1, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = df1, x = 666, z = 777, y = 999)
> lapply(list(df1=df1, df2=df2), fn, x=666, z=z.)
$df1
[1] 1 2 3
attr(,"call")
FUN(d = X[[i]], x = 666, z = 777, y = 999)
$df2
[1] 1 2 3
attr(,"call")
FUN(d = X[[i]], x = 666, z = 777, y = 999)
Это, очевидно, относится к предыдущему вопросу , поэтому я попробовал Map как предложено, однако, видимо, это не дает хорошего обобщения.
> Map(list(df1=df1, df2=df2), f=fn, x=666, z=z.)
$df1
[1] 1 2 3
attr(,"call")
(\(d, x=x, y=999, ...) {
cl <- match.call()
fa <- formals(fn)
fa[length(fa)] <- NULL
ma <- setdiff(names(fa), names(cl))
cl[ma] <- fa[ma]
cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
# cl$d <- quote(d)
res <- d[[1]]
attr(res, 'call') <- cl
return(res)
})(d = dots[[1L]][[2L]], x = 666, z = 777, y = 999)
$df2
[1] 1 2 3
attr(,"call")
(\(d, x=x, y=999, ...) {
cl <- match.call()
fa <- formals(fn)
fa[length(fa)] <- NULL
ma <- setdiff(names(fa), names(cl))
cl[ma] <- fa[ma]
cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
# cl$d <- quote(d)
res <- d[[1]]
attr(res, 'call') <- cl
return(res)
})(d = dots[[1L]][[2L]], x = 666, z = 777, y = 999)
Затем я попытался сделать cl$d <- quote(d), cl$d <- quote(substitute(d)), cl$d <- quote(eval(parse(text=d))), cl$d <- quote(get(d)), как указано выше, но безрезультатно. Вот результат версии cl$d <- quote(d):
> fn(df1, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = d, x = 666, z = 777, y = 999)
> lapply(list(df1=df1, df2=df2), fn, x=666, z=z.)
$df1
[1] 1 2 3
attr(,"call")
FUN(d = d, x = 666, z = 777, y = 999)
$df2
[1] 1 2 3
attr(,"call")
FUN(d = d, x = 666, z = 777, y = 999)
> lapply(list(df1=df1, df2=df2), fn, x=666, z=z.)
$df1
[1] TRUE
attr(,"call")
FUN(d = df1, x = 666, z = 777, y = 999)
$df2
[1] TRUE
attr(,"call")
FUN(d = df2, x = 666, z = 777, y = 999)
Внимание, я ищу решение, используя базу R.
После ответа @MrFlick я вижу, что мне нужно уточнить. На самом деле у меня большой список lst:
> lst <- list(df1=df1, df2=df2)
> lapply(lst, function(x)
+ do.call("fn", list(as.name(x), x=666, z=z.))
+ )
Error in as.vector(x, mode = mode) :
'list' object cannot be coerced to type 'symbol'
@SamR Интересно. Это близко, но дает мне d = list(df1 = df1, df2 = df2) на каждой итерации. Даже с Map при прохождении nm=c('df1', 'df2') и nmm внутри fn().





Однако вы не можете передавать имена переменных lapply вот так. Если вы хотите, чтобы ваша функция записывала вызов, и вас интересуют переменные имена, лучшим решением будет создание вызовов. Здесь мы можем сделать это с помощью do.call.
lapply(c("df1", "df2"), function(x)
do.call("fn", list(as.name(x), x=666, z=z.))
)
# [[1]]
# [1] TRUE
# attr(,"call")
# fn(d = df1, x = 666, z = 777)
#
# [[2]]
# [1] TRUE
# attr(,"call")
# fn(d = df2, x = 666, z = 777)
Спасибо за ваш ответ, выглядит многообещающе, однако у меня возникнут проблемы, если list уже упакован, см. редактирование моего вопроса.
@jay.sf. Потом lapply закончилось names(lst). Но тогда вы фактически не используете значения в списке. «Звонок», который вы создаете, на самом деле не сработает. fn(d = df2, x = 666, z = 777) отличается от fn(d = lst$df2, x = 666, z = 777), и только последний действительно будет использовать данные в списке, и нет никакой гарантии, что lst$df2 соответствует данным в df2. Вызов list() создает ленивую копию.
Мы очень ценим do.call как особенный ключ, +1 за подсказку! Мне было бы любопытно, существует ли этот сбой только в R или на других языках.
Я придумал это решение, которое использует элементы ответа @MrFlick, чтобы использовать do.call(), и из ответа передавать имена в Map() , цитируемые в ОП.
Нам просто нужно вставить одно предложение if в fn(). В вызове fn() заменяем d -- if присутствующий в многоточии -- на определенные параметры и attrибуты, или оставляем как есть.
> fn <- function(d, x=666, y=999, z, ...) {
+ cl <- match.call()
+ fa <- formals(fn)
+ fa[length(fa)] <- NULL
+ ma <- setdiff(names(fa), names(cl))
+ cl[ma] <- fa[ma]
+ cl[-c(1, 2)] <- lapply(cl[-c(1, 2)], eval, envir=parent.frame())
+ if (".name" %in% names(list(...))) {
+ .name <- sprintf('%s$%s', attr(d, 'lnm'), list(...)$.name)
+ cl$d <- str2lang(.name)
+ cl$.name <- NULL
+ }
+ res <- d[[1]]
+ attr(res, 'call') <- cl
+ return(res)
+ }
При вызове lapply мы передаем names в параметре .name, который запускает предложение if в fn(), а также имя списка с помощью deparse(substitute(.)).
> lapply(names(lst), \(x) {
+ do.call("fn",
+ list(
+ d=`attr<-`(
+ get(x, envir=as.environment(lst)), 'lnm', deparse(substitute(lst))
+ ),
+ x=666, y=999, z=z., .name=x)
+ )})
$df1
[1] 1 2 3
attr(,"call")
fn(d = lst$df1, x = 666, y = 999, z = 777)
$df2
[1] 1 2 3
attr(,"call")
fn(d = lst$df2, x = 666, y = 999, z = 777)
Это также работает для одиночных запусков, как и предполагалось. Оба варианта позволяют копировать и выполнять вызов.
> fn(d=df3, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = df3, x = 666, z = 777, y = 999)
>
> fn(d=lst$df1, x=666, z=z.)
[1] 1 2 3
attr(,"call")
fn(d = lst$df1, x = 666, z = 777, y = 999)
Ограничения
Вызов Map не тривиален. Кроме того, необходимо изменить fn(), что не является проблемой для моего варианта использования, поскольку fn() принадлежит мне, но может привести к проблемам, если он взят из пакета. Проблемы могут возникнуть, если он используется в нескольких вложенных вызовах, которые не проверялись.
Данные:
> lst <- replicate(2, data.frame(matrix(1:12, 3, 4)), simplify=FALSE) |>
+ setNames(c('df1', 'df2'))
> z. <- 777
> df3 <- lst$df1
Если
lapply()передается именованный список, вы можете записать имена, используяsys.call(), например: вот . Хотя это некрасиво. Вам придется ввести некоторую логику, чтобы определить, вызывается ли функция изlapply()или напрямую. И я полагаю, что вы столкнетесь с трудностями в более сложных случаях, таких как вложенные вызовыlapply(). Если это вообще возможно, я бы попытался найти способ изменить функцию, чтобы она принимала желаемое имя в качестве аргумента, или создать функцию-оболочку с дополнительным аргументом, которая делает это.