`níveis <-` (Que feitiçaria é essa?


114

Em resposta a outra pergunta, @Marek postou a seguinte solução: 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)
  )

Que produz como saída:

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

Esta é apenas a impressão de um vetor, então, para armazená-lo, você pode fazer o ainda mais confuso:

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

Claramente, isso é algum tipo de chamada para a função de níveis, mas não tenho ideia do que está sendo feito aqui. Qual é o termo para esse tipo de feitiçaria e como faço para aumentar minha habilidade mágica neste domínio?


1
Também existe names<-e [<-.
huon

1
Além disso, me perguntei sobre isso na outra pergunta, mas não perguntei: há alguma razão para a structure(...)construção em vez de apenas data.frame(product = c(11L, 11L, ..., 8L))? (Se houver alguma mágica acontecendo lá, eu gostaria de usá-la também!)
huon

2
É uma chamada para a "levels<-"função function (x, value) .Primitive("levels<-"):, mais ou menos como X %in% Yé uma abreviatura de "%in%"(X, Y).
BenBarnes

2
@dbaupp Muito útil para exemplos reproduzíveis: stackoverflow.com/questions/5963269/…
Ari B. Friedman

8
Não tenho ideia de por que alguém votou para fechar isso como não construtivo. OQ tem uma resposta muito clara: qual é o significado da sintaxe usada no exemplo e como isso funciona no R?
Gavin Simpson

Respostas:


104

As respostas aqui são boas, mas estão perdendo um ponto importante. Deixe-me tentar descrever.

R é uma linguagem funcional e não gosta de transformar seus objetos. Mas permite instruções de atribuição, usando funções de substituição:

levels(x) <- y

é equivalente a

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

O truque é que essa reescrita é feita por <-; não é feito por levels<-. levels<-é apenas uma função regular que recebe uma entrada e fornece uma saída; não muda nada.

Uma consequência disso é que, de acordo com a regra acima, <-deve ser recursiva:

levels(factor(x)) <- y

é

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

é

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

É lindo que essa transformação puramente funcional (até o final, onde a atribuição acontece) seja equivalente ao que seria uma atribuição em uma linguagem imperativa. Se bem me lembro, essa construção em linguagens funcionais é chamada de lente.

Mas então, depois de definir as funções de substituição como levels<-, você obtém outro ganho inesperado: você não tem apenas a capacidade de fazer atribuições, você tem uma função útil que leva em um fator e fornece outro fator com níveis diferentes. Não há realmente nada de "atribuição" nisso!

Portanto, o código que você está descrevendo está apenas fazendo uso dessa outra interpretação levels<-. Admito que o nome levels<-é um pouco confuso porque sugere uma atribuição, mas não é isso que está acontecendo. O código é simplesmente configurar uma espécie de pipeline:

  • Começar com dat$product

  • Converta em um fator

  • Mudar os níveis

  • Armazene isso em res

Pessoalmente, acho que essa linha de código é linda;)


33

Sem feitiçaria, é assim que as funções de (sub) atribuição são definidas. levels<-é um pouco diferente porque é um primitivo para (sub) atribuir os atributos de um fator, não os próprios elementos. Existem muitos exemplos desse tipo de função:

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

Outros operadores binários também podem ser chamados assim:

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

Agora que você sabe disso, algo como isso deve realmente explodir sua mente:

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

1
Você pode explicar um pouco mais sobre quando faz sentido chamar funções dessa maneira, em vez da maneira usual? Estou trabalhando com o exemplo de @Mark na pergunta vinculada, mas ajudaria se tivesse uma explicação mais explícita.
Drew Steen

4
@DrewSteen: por razões de clareza / legibilidade do código, eu diria que nunca faz sentido porque `levels<-`(foo,bar)é o mesmo que levels(foo) <- bar. Usando o exemplo de @ Marek: `levels<-`(as.factor(foo),bar)é o mesmo que foo <- as.factor(foo); levels(foo) <- bar.
Joshua Ulrich

Boa lista. Você não acha que levels<-é apenas uma abreviatura de attr<-(x, "levels") <- value, ou pelo menos provavelmente era até que se tornasse um primitivo e fosse entregue ao código C.
IRTFM

30

A razão para essa "mágica" é que o formulário de "atribuição" deve ter uma variável real para trabalhar. E factor(dat$product)não foi atribuído a nada.

# 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 Acho que seria mais limpo converter primeiro para fator, depois substituir os níveis por meio de um within()e transform()chamar onde o objeto modificado dessa forma é retornado e atribuído.
Gavin Simpson

4
@GavinSimpson - Concordo, só explico a magia, não a defendo ;-)
Tommy

16

Para o código do usuário, eu me pergunto por que tais manipulações de linguagem são usadas assim? Você pergunta que mágica é essa e outros apontaram que você está chamando a função de substituição que tem o nome levels<-. Para a maioria das pessoas isso é mágico e realmente o uso pretendido é levels(foo) <- bar.

O caso de uso que você mostra é diferente porque productnão existe no ambiente global; portanto, só existe no ambiente local da chamada para, levels<-portanto, a mudança que você deseja fazer não persiste - não houve reatribuição de dat.

Nessas circunstâncias, within() é a função ideal a ser usada. Você naturalmente desejaria escrever

levels(product) <- bar

em R, mas é claro productque não existe como um objeto. within()contorna isso porque configura o ambiente no qual você deseja executar seu código R e avalia sua expressão dentro desse ambiente. Assim, atribuir o objeto de retorno da chamada a within()é bem-sucedido no quadro de dados modificado corretamente.

Aqui está um exemplo (você não precisa criar um novo datX- eu apenas faço isso para que as etapas intermediárias permaneçam no final)

## 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))

Que dá:

> 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 ...

Eu me esforço para ver como construções como a que você mostra são úteis na maioria dos casos - se você quiser alterar os dados, alterar os dados, não crie outra cópia e altere isso (que é tudo o que a levels<-chamada está fazendo, afinal )

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.