#function #r #variables
#функция #r #переменные
Вопрос:
Кажется, обычно считается плохой практикой программирования использовать имена переменных, у которых есть функции в base R с тем же именем.
Например, возникает соблазн написать:
data <- data.frame(...)
df <- data.frame(...)
Теперь функция data
загружает наборы данных, в то время как функция df
вычисляет функцию плотности f.
Аналогично, возникает соблазн написать:
a <- 1
b <- 2
c <- 3
Это считается дурным тоном, потому что функция c
будет комбинировать свои аргументы.
Но: В этой рабочей лошадке функций R, lm
для вычисления линейных моделей, data
используется в качестве аргумента. Другими словами, data
становится явной переменной внутри lm
функции.
Итак: Если основная команда R может использовать идентичные имена для переменных и функций, что останавливает нас, простых смертных?
Ответ не в том, что R запутается. Попробуйте следующий пример, где я явно присваиваю переменной имя c
. R вообще не путается с разницей между переменной и функцией:
c("A", "B")
[1] "A" "B"
c <- c("Some text", "Second", "Third")
c(1, 3, 5)
[1] 1 3 5
c[3]
[1] "Third"
Вопрос: В чем именно проблема с наличием переменной с тем же именем, что и у базовой функции R?
Комментарии:
1. Отличный вопрос. Увидев, что это упоминалось несколько раз в последнее время, я собирался спросить об этом сам.
Ответ №1:
На самом деле такой нет. R обычно не выполняет поиск объектов (нефункциональных объектов) при поиске функции:
> mean(1:10)
[1] 5.5
> mean <- 1
> mean(1:10)
[1] 5.5
> rm(mean)
> mean(1:10)
[1] 5.5
Примеры, показанные @Joris и @Sacha, показывают, где плохое кодирование бросается в глаза. Один из лучших способов записи foo
— это:
foo <- function(x, fun) {
fun <- match.fun(fun)
fun(x)
}
Которое при использовании дает:
> foo(1:10, mean)
[1] 5.5
> mean <- 1
> foo(1:10, mean)
[1] 5.5
Бывают ситуации, когда это вас застанет врасплох, и примером @Joris с na.omit
является тот, который IIRC, происходит из-за стандартной нестандартной оценки, используемой в lm()
.
В нескольких ответах также была объединена проблема с T
vs TRUE
с проблемой маскировки функций. As T
и TRUE
не являются функциями, что немного выходит за рамки вопроса @Andrie’s.
Комментарии:
1. Хороший пример того, как исправить
foo
функцию с помощьюmatch.fun
. Это функция, которую мне, вероятно, следует использовать чаще.2. Во всех этих ответах — многие из них отличные — я удивлен, что никто не предложил возможность предупреждения R, когда вы присваиваете имя функции, не являющееся функцией. Такое предупреждение может быть одним из множества уже существующих вариантов, который я, вероятно, использовал бы. Аналогичным образом, например, в Perl есть «use strict» и «use warnings», которые предупреждают об опасных, но не незаконных конструкциях. Если это вообще возможно, язык должен упрощать работу пользователя, а не наоборот. И я не понимаю, почему это было бы особенно сложно реализовать или негативно сказалось бы на производительности.
3. @c-urchin одной из причин может быть то, что пользователь мог бы сделать
foo <- function(x) x 1
и использовать это некоторое время, а затем сделатьfoo <- "bar"
и использовать это некоторое время и т.д. Почему язык должен предупреждать вас в этой ситуации? Пользовательские функции являются первоклассными гражданами в R, и то, что вы предлагаете, вероятно, помешало бы бесперебойной работе этого.
Ответ №2:
Проблема не столько в компьютере, сколько в пользователе. В общем, отлаживать код может стать намного сложнее. Опечатки делаются очень легко, так что если вы сделаете :
c <- c("Some text", "Second", "Third")
c[3]
c(3)
Вы получаете правильные результаты. Но если вы пропустите что-то в коде и введете c(3)
вместо c[3]
, найти ошибку будет не так-то просто.
Определение области действия также может привести к появлению очень запутанных отчетов об ошибках. Возьмите следующую некорректную функцию :
my.foo <- function(x){
if(x) c <- 1
c 1
}
> my.foo(TRUE)
[1] 2
> my.foo(FALSE)
Error in c 1 : non-numeric argument to binary operator
С более сложными функциями это может привести вас к отладке, которая никуда не приведет. Если вы замените c
на x
в приведенной выше функции, ошибка будет гласить « object 'x' not found
«. Это намного быстрее приведет к вашей ошибке кодирования.
Кроме того, это может привести к довольно запутанному коду. Подобный код c(c c(a,b,c))
требует от мозга большего, чем c(d c(a,b,d))
. Опять же, это тривиальный пример, но он может иметь значение.
И, очевидно, вы также можете получать ошибки. Когда вы ожидаете функцию, вы ее не получите, что может привести к очередному набору раздражающих ошибок :
my.foo <- function(x,fun) fun(x)
my.foo(1,sum)
[1] 1
my.foo(1,c)
Error in my.foo(1, c) : could not find function "fun"
Более реалистичный (и реальный) пример того, как это может вызвать проблемы :
x <- c(1:10,NA)
y <- c(NA,1:10)
lm(x~y,na.action=na.omit)
# ... correct output ...
na.omit <- TRUE
lm(x~y,na.action=na.omit)
Error in model.frame.default(formula = x ~ y, na.action = na.omit,
drop.unused.levels = TRUE) : attempt to apply non-function
Попробуйте выяснить, что здесь не так, если na.omit <- TRUE
встречается в вашем коде на 50 строк выше…
Ответ отредактирован после комментария @Andrie, чтобы включить пример запутанных отчетов об ошибках
Комментарии:
1. Хороший пример определения области видимости, но, на мой взгляд, это отвечает на другую очень важную проблему — определение области видимости, а не столько именование переменных. Ваш пример приведет к запутанным результатам, даже если вы не использовали имя базовой функции R в качестве имени переменной.
2. @Andrie : это реальный опыт. Ошибка «объект ‘x’ не найден» немедленно сообщает мне, что не так. Ошибка «нечисловой аргумент двоичного оператора» может привести вас к долгой отладке, которая ни к чему не приведет.
Ответ №3:
R очень устойчив к этому, но вы можете придумать способы его устранения. Например, рассмотрим эту функцию:
foo <- function(x,fun) fun(x)
Которое просто применяется fun
к x
. Не самый приятный способ сделать это, но вы можете столкнуться с этим в чьем-то скрипте или около того. Это работает для mean()
:
> foo(1:10,mean)
[1] 5.5
Но если я присвою новое значение, означающее, что оно прерывается:
mean <- 1
foo(1:10,mean)
Error in foo(1:10, mean) : could not find function "fun"
Это случается очень редко, но может случиться. Людей также очень смущает, если одно и то же означает две вещи:
mean(mean)
Поскольку тривиально использовать любое другое имя, которое вы хотите, почему бы не использовать другое имя, чем у базовых функций R? Кроме того, для некоторых переменных R это становится еще более важным. Подумайте о переназначении ' '
функции! Другим хорошим примером является переназначение T
и F
, которое может нарушить работу стольких сценариев.
Комментарии:
1. Очень хороший комментарий об использовании
T
иF
. Я считаю, что использовать эти сочетания клавиш для указания TRUE или FALSE — плохая практика. Всегда используйте TRUE и FALSE в своем коде — это зарезервированные слова в R и их нельзя переназначить.2. Да, я делаю это тоже сейчас, после того, как на собственном горьком опыте узнал, как чей-то скрипт может сломать мой 🙂
3. Однажды я потратил половину ночи на поиск ошибки, прежде чем понял, что вызывается мой обучающий набор,
T
и я используюT
в какой-то функции вместоTRUE
. Это было в прошлый раз, когда я использовалT
asTRUE
.4. Это только нарушает работу, потому что ваш
foo()
плохо закодирован. Если вы хотите делать подобные вещи, то вам нужно сделатьfun <- match.fun(fun)
в функции. В противном случае R завершает работу, правильно сообщая вам, что функции нет1
(IIRC). R устойчив к маскировке функций пользовательскими объектами (в любом случае, нефункциональными объектами); он выполняет поиск только функций для поиска соответствия, а не всех объектов.5. Да, я согласен, однако, к сожалению, вы довольно часто сталкиваетесь с плохо закодированными вещами.
Ответ №4:
Я думаю, проблема в том, что люди используют эти функции в глобальной среде и могут вызвать разочарование из-за какой-то неожиданной ошибки, которую вы не должны получать. Представьте, что вы только что запустили воспроизводимый пример (возможно, довольно длинный), в котором перезаписана одна из функций, которые вы используете в своем моделировании, и требуется много времени, чтобы добраться туда, куда вы хотите, а затем внезапно она выходит из строя с забавной ошибкой. Использование уже существующих имен функций для переменных в закрытой среде (например, функции) удаляется после закрытия функции и не должно причинять вреда. Предполагая, что программист осведомлен обо всех последствиях такого поведения.
Комментарии:
1. Но смысл моего примера в том, что присвоение имени переменной не перезаписывает функцию. Ваша функция безопасна, если вы явно не измените функцию.
2. @Andrie : нет, это не так. Смотрите конструкцию @Sacha en, на которую я ссылаюсь.
Ответ №5:
Ответ прост. Ну, вроде того.
Суть в том, что вам следует избегать путаницы. Технически нет причин присваивать вашим переменным собственные имена, но это облегчает чтение вашего кода.
Представьте, что у вас есть строка кода, содержащая что-то вроде data()[1]
или похожее (эта строка, вероятно, не имеет смысла, но это всего лишь пример): хотя теперь вам ясно, что вы используете здесь данные функции, читатель, который заметил, что там есть data.frame с именем data, может быть сбит с толку.
И если вы не склонны к альтруизму, помните, что читатель мог бы быть вами через полгода, пытаясь выяснить, что вы делали с «этим старым кодом».
Послушайте человека, который научился использовать длинные имена переменных и соглашения об именовании: это окупается!
Ответ №6:
Я согласен с @Gavin Simpson и @Nick Sabbe, что на самом деле проблемы нет, но что это скорее вопрос удобочитаемости кода. Следовательно, как и многое в жизни, это вопрос соглашения и консенсуса.
И я думаю, что это хорошее соглашение, чтобы дать общий совет: не называйте свои переменные как базовые функции R!
Этот совет работает так же, как и другие хорошие советы. Например, мы все знаем, что не должны пить слишком много выпивки и не есть слишком много нездоровой пищи, но время от времени мы не можем следовать этим советам и напиваться, съедая слишком много нездоровой пищи.
То же самое верно и для этого совета. Очевидно, имеет смысл назвать аргумент data data
. Но гораздо меньше смысла называть вектор данных mean
. Хотя могут быть ситуации, в которых даже это кажется уместным. Но постарайтесь избегать таких ситуаций для ясности.
Ответ №7:
Хотя некоторые языки могут это допускать, IF IF THEN THEN ELSE ELSE
приходят на ум. В целом это считается очень плохой практикой. Дело не в том, что мы не хотим дать вам возможность продемонстрировать свои продвинутые знания языка, дело в том, что однажды нам придется иметь дело с этим кодом, а мы всего лишь смертные.
Поэтому избавьте свои приемы программирования от нарушения ночных сборок и дайте своим переменным разумные имена с последовательным корпусом, если вы чувствуете себя особенно тепло и нечетко.