`levels <-` (Что это за колдовство?
В ответе на другой вопрос @Marek опубликовал следующее решение:
qaru.site/info/57284/...
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
Ответы здесь хорошие, но у них отсутствует важный момент. Позвольте мне попытаться описать это.
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<-
немного запутанно, потому что оно предлагает назначение, но это не то, что происходит. Код просто устанавливает своего рода конвейер:
Лично я считаю, что строка кода красива;)
Ответ 2
Причиной этой "магии" является то, что форма "присваивания" должна иметь реальную переменную, над которой нужно работать. И 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 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)
)
Ответ 3
Нет колдовства, это то, как определяются (подчиненные) функции назначения. levels<-
немного отличается, потому что он примитивен (sub) присваивает атрибуты фактора, а не сами элементы. Существует множество примеров такого типа функций:
`<-` # 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! ;-)
Ответ 4
Для пользовательского кода я действительно удивляюсь, почему такие манипуляции с языком используются так? Вы спрашиваете, что это за магия, а другие указали, что вы вызываете функцию замены, которая имеет имя 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<-
вызов делает все).