`( What sorcery is this?

In an answer to another question, @Marek posted the following solution: 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)
  )

Which produces as output:

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

This is just the printout of a vector, so to store it you can do the even more confusing:

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

Clearly this is some kind of call to the levels function, but I have no idea what's being done here. What is the term for this kind of sorcery, and how do I increase my magical ability in this domain?


The answers here are good, but they are missing an important point. Let me try and describe it.

R is a functional language and does not like to mutate its objects. But it does allow assignment statements, using replacement functions:

levels(x) <- y

is equivalent to

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

The trick is, this rewriting is done by <- ; it is not done by levels<- . levels<- is just a regular function that takes an input and gives an output; it does not mutate anything.

One consequence of that is that, according to the above rule, <- must be recursive:

levels(factor(x)) <- y

is

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

is

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

It's kind of beautiful that this pure-functional transformation (up until the very end, where the assignment happens) is equivalent to what an assignment would be in an imperative language. If I remember correctly this construct in functional languages is called a lens.

But then, once you have defined replacement functions like levels<- , you get another, unexpected windfall: you don't just have the ability to make assignments, you have a handy function that takes in a factor, and gives out another factor with different levels. There's really nothing "assignment" about it!

So, the code you're describing is just making use of this other interpretation of levels<- . I admit that the name levels<- is a little confusing because it suggests an assignment, but this is not what is going on. The code is simply setting up a sort of pipeline:

  • Start with dat$product

  • Convert it to a factor

  • Change the levels

  • Store that in res

  • Personally, I think that line of code is beautiful ;)


    The reason for that "magic" is that the "assignment" form must have a real variable to work on. And the factor(dat$product) wasn't assigned to anything.

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

    No sorcery, that's just how (sub)assignment functions are defined. levels<- is a little different because it is a primitive to (sub)assign the attributes of a factor, not the elements themselves. There are plenty of examples of this type of function:

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

    Other binary operators can be called like that too:

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

    Now that you know that, something like this should really blow your mind:

    Data <- data.frame(x=1:10, y=10:1)
    names(Data)[1] <- "HI"              # How does that work?!? Magic! ;-)
    
    链接地址: http://www.djcxy.com/p/24888.html

    上一篇: 了解order()函数

    下一篇: (这是什么巫术?