Як ви використовуєте "<< -" (призначення шкали) у R?


140

Я щойно закінчив читати про те, як взяти участь у вступі R , і мені дуже цікаво про <<-завдання.

Посібник показав один (дуже цікавий) приклад <<-, який я вважаю, що зрозумів. Те, що мені ще не вистачає, це контекст, коли це може бути корисним.

Тому я хотів би прочитати у вас приклади (або посилання на приклади) про те, коли використання <<-може бути цікавим / корисним. Які небезпеки можуть бути використані (легко відстежувати їх) та будь-які поради, які ви можете відчути, поділившись.

Відповіді:


196

<<-є найбільш корисним у поєднанні із закриттями для підтримки стану. Ось розділ недавнього мого документу:

Закриття - це функція, записана іншою функцією. Закриття називається так, оскільки вони охоплюють середовище батьківської функції і можуть отримати доступ до всіх змінних та параметрів у цій функції. Це корисно, оскільки дозволяє нам мати два рівні параметрів. Один рівень параметрів (батьківський) контролює функціонування функції. Інший рівень (дитина) виконує роботу. Наступний приклад показує, як можна використовувати цю ідею для створення сімейства функцій живлення. Батьківська функція ( power) створює дочірні функції ( squareі cube), які насправді виконують важку роботу.

power <- function(exponent) {
  function(x) x ^ exponent
}

square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16

cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

Здатність керувати змінними на двох рівнях також дозволяє підтримувати стан через виклики функції, дозволяючи функції змінювати змінні в середовищі свого батьківського. Ключовим фактором управління змінними на різних рівнях є оператор присвоєння подвійних стрілок <<-. На відміну від звичайного призначення однієї стрілки ( <-), яке завжди працює на поточному рівні, оператор подвійної стрілки може змінювати змінні на батьківських рівнях.

Це дає можливість підтримувати лічильник, який записує, скільки разів викликалася функція, як видно з наступного прикладу. Кожен раз, коли new_counterвін запускається, він створює середовище, ініціалізує лічильник iу цьому середовищі, а потім створює нову функцію.

new_counter <- function() {
  i <- 0
  function() {
    # do something useful, then ...
    i <<- i + 1
    i
  }
}

Нова функція - це закриття, а її середовище - оточуюче середовище. Після закриття counter_oneта counter_twoзапуску кожен з них змінює лічильник у своєму оточуючому середовищі, а потім повертає поточну кількість.

counter_one <- new_counter()
counter_two <- new_counter()

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1

4
Гей, це невирішене завдання R на Rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Ну, це було ...
Karsten W.

1
Чи виникне потреба укласти в одній батьківській функції більше 1 закриття? Я просто спробував один фрагмент, здається, що було виконано лише останнє закриття ...
mckf111

Чи є альтернатива знаку "<< -" альтернативи знаку рівності?
Геном

38

Це допомагає мислити <<-як еквівалент assign(якщо ви встановите для цього inheritsпараметра параметр TRUE). Перевага в assignтому , що вона дозволяє визначити інші параметри (наприклад, навколишнє середовище), тому я вважаю за краще використовувати assignбільш <<-в більшості випадків.

Використання <<-та assign(x, value, inherits=TRUE)означає, що "оточуючі середовища, що надаються в середовищі, що надаються, здійснюються пошук, поки не зустрінеться змінна" x "." Іншими словами, він продовжуватиме проходити через оточення в порядку, поки не знайде змінну з цим ім'ям, і призначить її цьому. Це може бути в межах функції, або в глобальному середовищі.

Для того, щоб зрозуміти, що роблять ці функції, потрібно також зрозуміти R середовища (наприклад, використання search).

Я регулярно використовую ці функції, коли запускаю велике моделювання, і хочу зберегти проміжні результати. Це дозволяє створити об'єкт поза межами заданої функції або applyциклу. Це дуже корисно, особливо якщо ви турбуєтесь про те, що великий цикл несподівано закінчиться (наприклад, відключення бази даних), і в цьому випадку ви можете втратити все в процесі. Це було б рівнозначно запису ваших результатів у базу даних або файл під час тривалого запущеного процесу, за винятком того, що він замість цього зберігає результати в середовищі R.

Моє основне попередження з цього приводу: будьте обережні, оскільки зараз ви працюєте з глобальними змінними, особливо під час використання <<-. Це означає, що ви можете опинитися в ситуаціях, коли функція використовує об'єктне значення з оточення, коли ви очікували, що воно буде використовувати те, яке було надано як параметр. Це одне з головних речей, яких функціональне програмування намагається уникати (див. Побічні ефекти ). Я уникаю цієї проблеми, присвоюючи свої значення унікальним іменам змінних (використовуючи пасту з набором або унікальні параметри), які ніколи не використовуються в межах функції, а просто використовуються для кешування, і якщо мені потрібно буде відновити пізніше (або зробити якийсь мета -аналіз на проміжні результати).


3
Дякую Талю. У мене є блог, хоча я не дуже ним користуюся. Я ніколи не можу закінчити публікацію, тому що я не хочу нічого публікувати, якщо це не ідеально, і просто не маю часу на це ...
Шейн

2
Мудрець одного разу сказав мені, що не важливо бути ідеальним - лише стоячи, - яким ти є, і таким буде і твій пост. Також - іноді читачі допомагають покращити текст за допомогою коментарів (саме це відбувається з моїм блогом). Я сподіваюся, що одного разу ви переглянете :)
Тал Галілі

9

Одне місце, де я використовувався, <<-був у простих графічних інтерфейсах із використанням tcl / tk. Деякі з початкових прикладів мають це - як вам потрібно зробити відмінність між локальними та глобальними змінними для стану стану. Див. Наприклад

 library(tcltk)
 demo(tkdensity)

який використовує <<-. Інакше я згоден з Мареком :) - пошук у Google може допомогти.


Цікаво, що я якось не можу знайти tkdensityв R 3.6.0.
НельсонГон


5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")

11
Це хороший приклад того, де не користуватися <<-. Цикл для циклу був би зрозумілішим у цьому випадку.
Хадлі

4

З цього приводу я хочу зазначити, що <<-оператор буде вести себе дивно, коли застосовується (неправильно) в циклі for (може бути і інші випадки). Дано наступний код:

fortest <- function() {
    mySum <- 0
    for (i in c(1, 2, 3)) {
        mySum <<- mySum + i
    }
    mySum
}

Ви можете очікувати, що функція поверне очікувану суму 6, але натомість вона повертає 0, створюється глобальна змінна mySumі присвоюється значення 3. Я не можу повністю пояснити, що відбувається тут, але, безумовно, тіло цикл - це не новий рівень "рівня". Натомість, здається, що R виглядає поза fortestфункцією, не може знайти mySumзмінну, якій призначити, тому створює її та призначає значення 1, вперше через цикл. При наступних ітераціях РЗС у призначенні має посилатися на (незмінну) внутрішню mySumзмінну, тоді як LHS посилається на глобальну змінну. Тому кожна ітерація переписує значення глобальної змінної на значення ітераціїi , отже, вона має значення 3 при виході з функції.

Сподіваюся, що це комусь допоможе - це мене сьогодні на пару годин наткнуло! (До речі, просто замінити <<-з <-і функціональних робіт , як і очікувалося).


2
у вашому прикладі локальне mySumніколи не збільшується, а лише глобальне mySum. Отже, при кожній ітерації циклу for, глобальний mySumотримує значення 0 + i. Ви можете прослідкувати за цим debug(fortest).
ClementWalter

Це не має нічого спільного з тим, що це петля для циклу; ви посилаєтесь на дві різні сфери. Просто використовуйте <-всюди послідовно в межах функції, якщо ви хочете лише оновити локальну змінну всередині функції.
smci

Або використовувати << - скрізь @smci. Хоча краще уникати глобалів.
Статистика навчання за прикладом

3

<<-Оператор також може бути корисний для посилальних класів при написанні еталонних методів . Наприклад:

myRFclass <- setRefClass(Class = "RF",
                         fields = list(A = "numeric",
                                       B = "numeric",
                                       C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.