`рівні <-` (Що це за чаклунство?


114

У відповіді на інше питання @Marek розмістив таке рішення: https://stackoverflow.com/a/10432263/636656

dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 
                                  7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, -20L), class = "data.frame")

`levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

Що дає результат:

 [1] Generic Generic Bayer   Bayer   Advil   Tylenol Generic Advil   Bayer   Generic Advil   Generic Advil   Tylenol
[15] Generic Bayer   Generic Advil   Bayer   Bayer  

Це лише роздруківка вектора, тому для його зберігання ви можете зробити ще більш заплутаною:

res <- `levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

Ясна річ, що це якийсь виклик функції рівнів, але я поняття не маю, що тут робиться. Що таке термін для такого чаклунства і як я можу збільшити свою магічну здатність у цій галузі?


1
Є також names<-і [<-.
хуно

1
Крім того, я задумався про це в іншому питанні, але не запитав: чи є якась причина для structure(...)конструкції замість просто data.frame(product = c(11L, 11L, ..., 8L))? (Якщо там відбувається якась магія, я теж хотів би її
влаштувати

2
Це дзвінок до "levels<-"функції: на function (x, value) .Primitive("levels<-")зразок подібного X %in% Yє абревіатурою "%in%"(X, Y).
БенБарнес

2
@dbaupp Дуже зручно для відтворюваних прикладів: stackoverflow.com/questions/5963269/…
Арі Б. Фрідман

8
Я поняття не маю, чому хтось проголосував за те, щоб закрити це як не конструктивне? У Q є дуже чітка відповідь: яке значення синтаксису, що використовується в прикладі, і як це працює в R?
Гевін Сімпсон

Відповіді:


104

Відповіді тут хороші, але вони не мають важливого моменту. Дозвольте спробувати і описати.

R - функціональна мова і не любить мутувати свої об'єкти. Але це дозволяє виписки про призначення, використовуючи функції заміни:

levels(x) <- y

еквівалентно

x <- `levels<-`(x, y)

Хитрість полягає в тому, що це переписування робиться <-; це не робиться levels<-.levels<-це просто звичайна функція, яка приймає вхід і дає вихід; він нічого не мутує.

Одним із наслідків цього є те, що, згідно з вищезазначеним правилом, він <-повинен бути рекурсивним:

levels(factor(x)) <- y

є

factor(x) <- `levels<-`(factor(x), y)

є

x <- `factor<-`(x, `levels<-`(factor(x), y))

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

Але потім, коли ви визначили функції заміни, як-от levels<-, ви отримуєте ще одну, несподівану бурю: у вас не просто є можливість складати завдання, у вас є зручна функція, яка приймає фактор, і видає ще один фактор з різними рівнями. Тут насправді немає нічого "доручення"!

Отже, код, який ви описуєте, просто використовує цю іншу інтерпретацію levels<-. Я визнаю, що ім'я levels<-трохи заплутане, оскільки воно передбачає призначення, але це не те, що відбувається. Код просто налаштовує свого роду трубопровід:

  • Починати з dat$product

  • Перетворіть це у фактор

  • Змініть рівні

  • Зберігайте це в res

Особисто я вважаю, що рядок коду прекрасний;)


33

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

`<-`              # assignment
`[<-`             # sub-assignment
`[<-.data.frame`  # sub-assignment data.frame method
`dimnames<-`      # change dimname attribute
`attributes<-`    # change any attributes

Інших двійкових операторів також можна назвати так:

`+`(1,2)  # 3
`-`(1,2)  # -1
`*`(1,2)  # 2
`/`(1,2)  # 0.5

Тепер, коли ви це знаєте, щось подібне має справді підірвати ваш розум:

Data <- data.frame(x=1:10, y=10:1)
names(Data)[1] <- "HI"              # How does that work?!? Magic! ;-)

1
Чи можете ви пояснити трохи більше про те, коли має сенс викликати функції саме таким, а не звичайним способом? Я працюю на прикладі @ Марека в пов'язаному питанні, але це допоможе отримати більш чітке пояснення.
Дрю Стін

4
@DrewSteen: з ясності коду / читабельності я б сказав, що це ніколи не має сенсу, оскільки `levels<-`(foo,bar)це те саме, що levels(foo) <- bar. Використовуючи приклад @ Marek: `levels<-`(as.factor(foo),bar)те саме, що foo <- as.factor(foo); levels(foo) <- bar.
Джошуа Ульріх

Хороший список. Ви не думаєте, що levels<-це насправді просто скорочення attr<-(x, "levels") <- value, або, принаймні, це було, поки він не перетворився на примітив і не передав С-код.
IRTFM

30

Причиною цієї "магії" є те, що форма "призначення" повинна мати реальну змінну, над якою можна працювати. І factor(dat$product)не було призначено ні до чого.

# This works since its done in several steps
x <- factor(dat$product)
levels(x) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
x

# This doesn't work although it's the "same" thing:
levels(factor(dat$product)) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
# Error: could not find function "factor<-"

# and this is the magic work-around that does work
`levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

+1 Я думаю, що було б чистіше спочатку перетворити на фактор, а потім замінити рівні на a within()і transform()виклик, якщо таким чином модифікований об'єкт буде повернутий і призначений.
Гевін Сімпсон

4
@GavinSimpson - Я згоден, я лише пояснюю магію, я не захищаю її ;-)
Томмі

16

Для коду користувача мені цікаво, чому такі мовні маніпуляції використовуються так? Ви запитуєте, що це за магія, а інші вказали, що ви викликаєте функцію заміни, яка має ім'я levels<-. Для більшості людей це магія, і це дійсно призначене використання levels(foo) <- bar.

productВипадковий вигляд використання, який ви показуєте, відрізняється тим, що його немає в глобальному середовищі, тому він існує лише в локальній обстановці заклику, щоб, levels<-таким чином, зміни, які ви хочете внести, не зберігаються - перепризначення не було dat.

У цих умовах within() ідеальна функція для використання. Ви, природно, хочете написати

levels(product) <- bar

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

Ось приклад (вам не потрібно створювати нове datX- я просто роблю це, щоб посередницькі кроки залишалися в кінці)

## one or t'other
#dat2 <- transform(dat, product = factor(product))
dat2 <- within(dat, product <- factor(product))

## then
dat3 <- within(dat2, 
               levels(product) <- list(Tylenol=1:3, Advil=4:6, 
                                       Bayer=7:9, Generic=10:12))

Що дає:

> head(dat3)
  product
1 Generic
2 Generic
3   Bayer
4   Bayer
5   Advil
6 Tylenol
> str(dat3)
'data.frame':   20 obs. of  1 variable:
 $ product: Factor w/ 4 levels "Tylenol","Advil",..: 4 4 3 3 2 1 4 2 3 4 ...

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.