这是什么魔法?

在回答另一个问题时,@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)
)

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

这只是一个向量的打印输出; 因此,为了存储它,您可以做一些更令人困惑的事情:

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

显然,这是对 level 函数的某种调用,但我不知道这里在做什么。这种魔法的术语是什么? 我如何在这个领域提高我的魔法能力?

7331 次浏览

没有巫术,这只是如何(子)赋值函数的定义。levels<-稍有不同,因为它是一个原语(sub)赋予一个因子的属性,而不是赋予元素本身。这种类型的函数有很多例子:

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

现在你知道了,这样的事情真的会让你大吃一惊:

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

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

对于用户代码,我很好奇为什么要使用这样的语言操作?您问这是什么魔术,其他人指出您正在调用名为 levels<-的替换函数。对于大多数人来说,这是神奇的,真正的用途是 levels(foo) <- bar

您显示的用例不同,因为 product不存在于全局环境中,所以它只存在于对 levels<-的调用的本地环境中,因此您想要进行的更改不会持续-没有对 dat的重新分配。

在这种情况下,within()是理想的函数

levels(product) <- bar

但是当然 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<-调用所做的)。

这里的答案是好的,但是他们忽略了一个重点。让我试着描述一下。

R 是一种函数式语言,不喜欢对其对象进行变异。但它确实允许使用替换函数的赋值语句:

levels(x) <- y

相当于

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

诀窍在于,这种重写是由 <-完成的; 而不是由 levels<-完成的。levels<-只是一个常规函数,它接受一个输入并给出一个输出; 它不会改变任何东西。

其结果之一是,根据上述规则,<-必须是递归的:

levels(factor(x)) <- y

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

is

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

这种纯函数转换(直到最后,赋值发生的地方)等价于命令式语言中的赋值,这是一种美妙的感觉。如果我没记错的话,函数式语言中的这个结构叫做透镜。

但是,一旦你定义了像 levels<-这样的替换函数,你就会得到另一个意想不到的意外收获: 你不仅有能力进行赋值,你还有一个方便的函数,它接受一个因子,并给出不同级别的另一个因子。真的没有什么“作业”!

所以,您描述的代码只是利用了 levels<-的另一种解释。我承认 levels<-这个名字有点令人困惑,因为它暗示了一个作业,但这不是正在发生的事情。代码只是简单地设置了一种管道:

  • dat$product开始

  • Convert it to a factor

  • 改变等级

  • 存储在 res

就个人而言,我认为这行代码很漂亮;)