Вопрос по types, r – `level <-` (Что это за колдовство?

105

В ответ на другой вопрос @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)
  )

Ясно, что это какой-то вызов функции уровней, но я понятия не имею, что здесь делается. Как называется этот вид магии, и как мне увеличить свои магические способности в этой области?

Это вызов"levels<-" функция:function (x, value) .Primitive("levels<-")вроде какX %in% Y это сокращение от"%in%"(X, Y). BenBarnes
Кроме того, я задавался вопросом об этом по другому вопросу, но не спрашивал: есть ли причина дляstructure(...) построить вместо простоdata.frame(product = c(11L, 11L, ..., 8L))? (Если там происходит какое-то волшебство, я бы тоже хотел им воспользоваться!) huon
@dbaupp Очень удобно для воспроизводимых примеров:stackoverflow.com/questions/5963269/… Ari B. Friedman
Существует такжеnames<- а также[<-. huon
Понятия не имею, почему кто-то проголосовал за то, чтобы закрыть это как неконструктивное? У Q очень четкий ответ: что означает синтаксис, использованный в примере, и как это работает в R? Gavin Simpson

Ваш Ответ

4   ответа
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 Я думаю, что было бы чище сначала пересчитать в коэффициент, а затем заменить уровни наwithin() а такжеtransform() вызов был изменен таким образом объект возвращается и назначается.
@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<- звонок делает в конце концов).

29

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! ;-)
Хороший список. Не думаешьlevels<- на самом деле просто стенография дляattr<-(x, "levels") <- valueили, по крайней мере, так было до тех пор, пока он не был превращен в примитив и передан в C-код.
Можете ли вы объяснить немного больше о том, когда имеет смысл вызывать функции таким образом, а не обычным способом? Я работаю с примером @ Marek в связанном вопросе, но это поможет получить более четкое объяснение.
@DrewSteen: из соображений ясности и читабельности кода, я бы сказал, что это никогда не имеет смысла, потому что`levels<-`(foo,bar) такой же какlevels(foo) <- bar, Используя пример @ Marek:`levels<-`(as.factor(foo),bar) такой же какfoo <- as.factor(foo); levels(foo) <- bar.
95

но они упускают важный момент. Позвольте мне попытаться описать это.

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<- немного сбивает с толку, потому что он предлагает назначение, но это не то, что происходит. Код просто устанавливает своего рода конвейер:

Start with dat$product

Convert it to a factor

Change the levels

Store that in res

Лично я считаю, что строка кода прекрасна;)

Похожие вопросы