Сильно различное поведение между with () и attach () в R? - PullRequest
11 голосов
/ 20 сентября 2011

Люди часто используют функции attach() и detach(), чтобы установить «пути поиска» для имен переменных в R, но поскольку это изменяет глобальное состояние, которое трудно отследить, people рекомендует с использованием with() вместо , который устанавливает временное изменение пути поиска на время одного выражения.

Однако я только что заметил, что в отличие от attach(), with(), по-видимому, не "разрешается" через функции . Например, давайте сначала настроим фиктивную функцию, которая будет обращаться к переменной с именем x:

f <- function { print(x) }

Теперь

with(list(x=42), f())

терпит неудачу, хотя

with(list(x=42), print(x))

и

attach(list(x=42))
f()

оба преуспевают! (

Может кто-нибудь сказать мне, почему? Я бы хотел, чтобы with() вел себя точно так же, как attach(), чтобы я мог эффективно передавать большой список параметров в функцию, устанавливая среду, содержащую значения параметров, с помощью with(). Я считаю, что этот подход имеет несколько преимуществ по сравнению с альтернативами (два, которые я рассмотрел, это: (а) кропотливо передавать все параметры в функцию и (б) явно передавать список / фрейм параметров в качестве аргумента функции и иметь сама функция вызывает with()), но она не работает. Я считаю это несоответствие довольно тревожным, если честно! Любые объяснения / помощь будут оценены.

Я использую R 2.11.1.

Ответы [ 4 ]

8 голосов
/ 20 сентября 2011

Разница между тем, что делает with(list(x = 42), f()), и тем, что вы ожидаете, - это разница между лексической областью видимости (что и используется в R) и динамической областью видимости (которая, кажется, вы ожидаете).

Лексическое определение объема означает, что свободные переменные (например, переменная x в f) ищутся в среде, где f равно , определено , а не в среде f is вызывается с .

f определяется в глобальной среде, поэтому здесь ищется x.

Неважно, что with был вызван для создания новой среды, из которой вызывается f, поскольку среда, из которой его вызывается, не участвует в поиске свободных переменных.

Чтобы заставить это работать так, как вы хотите, создайте копию f и сбросьте ее окружение, поскольку именно это R использует для поиска свободных переменных:

with(list(x = 42), { environment(f) <- environment(); f() })

По общему признанию, это немного обременительно, но вы могли бы несколько упростить его, используя пакет proto, так как proto сбрасывает среду каждой функции, которая явно вставлена ​​в объект proto:

library(proto)
with(proto(x = 42, f = f), f())

ДОБАВЛЕНО:

Обратите внимание, что если ваша цель состоит в объектно-ориентированном программировании (согласно вашему комментарию к другому ответу), то вам может потребоваться заглянуть в прото на домашней странице proto . Например, мы могли бы определить объект прото p и переопределить f, чтобы его метод был p (в этом случае он должен принять объект в аргументе 1) следующим образом:

library(proto)
p <- proto(x = 42, f = function(.) print(.$x))
p$f()

ДОБАВЛЕНО 2:

В присоединенном кейсе, f() сначала просматривает глобальную среду, так как именно здесь определено f. Так как x не найден в глобальной среде, он смотрит на родителя глобальной среды и в этом случае находит его там. Мы можем обнаружить родителя глобальной среды, используя parent.env, и здесь мы видим, что присоединенная среда стала родителем глобальной среды.

> attach(list(x = 42))
> parent.env(.GlobalEnv)
<environment: 0x048dcdb4>
attr(,"name")
[1] "list(x = 42)"

Мы можем просматривать глобальную среду и всех ее предков в следующем порядке:

> search()
 [1] ".GlobalEnv"        "list(x = 42)"      "package:stats"    
 [4] "package:graphics"  "package:grDevices" "package:utils"    
 [7] "package:datasets"  "package:methods"   "Autoloads"        
[10] "package:base"   

Таким образом, "list(x = 42)" является родителем глобальной среды, stats является родителем "list(x = 42)" и т. Д.

7 голосов
/ 20 сентября 2011

I думаю это связано с тем, что вы не определили аргументы для f, и, следовательно, как ищется x, требуемый для print(x).

При обычном использовании f() будет искать x в глобальной среде, если она не предоставлена ​​(и не является, и не может быть, поскольку f не принимает аргументов).

Внутри with() происходит то, что любые аргументы, необходимые для f, будут взяты из аргумента data. Но поскольку ваш f не принимает никаких аргументов, x в списке никогда не используется. Вместо этого R возвращается к обычному поведению и ищет x в среде f, глобальной среде, и, конечно, его там нет. Разница с attach() заключается в том, что он явно добавляет объект, содержащий x, к пути поиска в глобальной среде.

Если вы пишете свою функцию правильно, следуя мантре, которую вы передаете в любом и всех аргументах, которые вы используете внутри функции, тогда все работает так, как вы ожидаете:

> F <- function(x) print(x)
> with(list(x = 42), F(x))
[1] 42
> ls()
[1] "f" "F"

Если у вас уже есть список или аргументы, необходимые для вызова, возможно, рассмотрите do.call(), чтобы настроить вызов вместо использования с. Например:

> do.call(F, list(x = 42))
[1] 42

Вам все еще нужно, чтобы ваша функция была правильно определена с аргументами, так как ваш f не работает:

> do.call(f, list(x = 42))
Error in function ()  : unused argument(s) (x = 42)
3 голосов
/ 20 сентября 2011

Передаваемый список параметров работает для меня:

f <- function(params) with(params, {
    print(x)
})
f(list(x=42))
# [1] 42

Но вы должны учитывать явные ссылки, например:

f <- function(params) {
    print(params$x)
}

Причина в фазе разработки, с большим количеством переменных, это вопросвремя, когда вы делаете что-то вроде:

f <- function(params) with(params, {
    # many lines of code 
    print(x)
})
x <- 7
f(list(y=8))
# [1] 7 # wasn't in params but you got an answer
3 голосов
/ 20 сентября 2011

В описании из ?with говорится:

Оценить выражение R в среде, построенной на основе данных, возможно, изменив исходные данные.

Это означает, чтоФункция запускается в среде, где данные существуют, но среда не находится на пути поиска.Таким образом, любые функции, выполняемые в этой среде, не найдут данные, которые не были переданы им (поскольку они запускаются в их собственной среде), если не указано, что нужно посмотреть на parent.frame.Рассмотрим следующее:

> f <- function() print(x)
> f()
Error in print(x) : object 'x' not found
> with(list(x=42),f())
Error in print(x) : object 'x' not found
> x <- 13
> f()
[1] 13
> with(list(x=42),f())
[1] 13
> f2 <- function(x) print(get("x",parent.frame()))
> f2()
[1] 13
> with(list(x=42),f2())
[1] 42

Чтобы выяснить, как обычно используется with, данные передаются в функцию в среде, создаваемой with, и обычно не называются «глобальной» переменнойвызываемая функция.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...