`levels <-`(这是什么法术?


114

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

显然,这是对level函数的某种调用,但是我不知道在这里正在做什么。这种魔术的术语是什么,我如何在这方面增加我的魔法能力?


1
还有names<-[<-
休恩

1
另外,我在另一个问题上对此感到疑惑,但没有问:structure(...)构造的原因有什么,而不仅仅是data.frame(product = c(11L, 11L, ..., 8L))?(如果那里发生了一些魔术,我也想使用它!)
休恩

2
它是对"levels<-"函数的调用:function (x, value) .Primitive("levels<-"),有点像是X %in% Y的缩写"%in%"(X, Y)
BenBarnes 2012年

2
@dbaupp对于可重现的示例非常方便:stackoverflow.com/questions/5963269/…–
Ari B. Friedman

8
我不知道为什么有人投票决定关闭它不是建设性的?Q有一个非常明确的答案:示例中使用的语法的含义是什么,它在R中如何工作?
加文·辛普森

Answers:


104

这里的答案是好的,但是它们缺少重要的一点。让我尝试描述一下。

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<-有点令人困惑,因为它暗示了一项任务,但是事实并非如此。该代码只是建立了一种管道:

  • 从...开始 dat$product

  • 将其转换为因子

  • 改变水平

  • 存放在 res

我个人认为代码行很漂亮;)


33

没有法宝,这就是(子)分配功能的定义方式。 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! ;-)

1
您能否解释一下何时以这种方式而不是通常的方式调用函数有意义?我正在通过链接问题中的@Marek示例进行工作,但是有一个更明确的解释将有所帮助。
德鲁·斯蒂恩

4
@DrewSteen:出于代码清晰/易读性的原因,我想说它永远都没有道理,因为`levels<-`(foo,bar)与相同levels(foo) <- bar。使用@Marek的example:`levels<-`(as.factor(foo),bar)与相同foo <- as.factor(foo); levels(foo) <- bar
约书亚·乌尔里希

不错的清单。您难道不认为levels<-它真的只是的简写attr<-(x, "levels") <- value,或者至少直到它变成原始并交给C代码之前。
IRTFM

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我认为先转换为因子会更干净,然后通过a within()并替换水平,然后transform()返回并分配如此修改的对象。
加文·辛普森

4
@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<-调用所做的工作) )。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.