【问题标题】:`levels<-`( What sorcery is this?`levels<-`(这是什么魔法?
【发布时间】:2012-05-14 00:42:54
【问题描述】:

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

显然这是对级别函数的某种调用,但我不知道这里做了什么。这种魔法叫什么名字,如何提升我在这个领域的魔法能力?

【问题讨论】:

  • 还有names&lt;-[&lt;-
  • 另外,我在另一个问题上想知道这一点,但没有问:structure(...) 构造有什么原因,而不仅仅是data.frame(product = c(11L, 11L, ..., 8L))? (如果那里发生了什么魔法,我也想使用它!)
  • 这是对"levels&lt;-"函数的调用:function (x, value) .Primitive("levels&lt;-"),有点像X %in% Y"%in%"(X, Y)的缩写。
  • @dbaupp 对于可重现的示例非常方便:stackoverflow.com/questions/5963269/…
  • 我不知道为什么有人投票结束这个没有建设性? Q 有一个非常明确的答案:示例中使用的语法是什么意思,在 R 中是如何工作的?

标签: r types levels


【解决方案1】:

这里的答案很好,但他们错过了重要的一点。让我试着描述一下。

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

levels(x) <- y

等价于

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

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

这样做的一个结果是,根据上述规则,&lt;- 必须是递归的:

levels(factor(x)) <- y

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

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

这种纯函数转换(直到最后,赋值发生的地方)等同于命令式语言中的赋值,这真是太棒了。如果我没记错的话,函数式语言中的这种结构称为镜头。

但是,一旦你定义了像 levels&lt;- 这样的替换函数,你就会得到另一个意外的收获:你不仅有能力进行分配,你还有一个方便的函数,它可以接受一个因素,然后给出另一个不同水平的因素。真的没有什么“任务”!

因此,您所描述的代码只是利用了levels&lt;- 的另一种解释。我承认levels&lt;- 这个名字有点令人困惑,因为它暗示了一个任务,但这不是正在发生的事情。代码只是建立了一种管道:

  • dat$product开头

  • 将其转换为因子

  • 更改级别

  • 将其存储在res

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

【讨论】:

    【解决方案2】:

    没有魔法,这就是(子)赋值函数的定义方式。 levels&lt;- 有点不同,因为它是(子)分配因子属性的原语,而不是元素本身。这种类型的函数有很多例子:

    `<-`              # 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! ;-)
    

    【讨论】:

    • 你能解释一下什么时候以这种方式而不是通常的方式调用函数是有意义的吗?我正在处理链接问题中@Marek 的示例,但有一个更明确的解释会有所帮助。
    • @DrewSteen:出于代码清晰/可读性的原因,我会说这毫无意义,因为`levels&lt;-`(foo,bar)levels(foo) &lt;- bar 相同。使用@Marek 的示例:`levels&lt;-`(as.factor(foo),bar)foo &lt;- as.factor(foo); levels(foo) &lt;- bar 相同。
    • 不错的清单。你不觉得levels&lt;- 真的只是attr&lt;-(x, "levels") &lt;- value 的简写吗,或者至少在它变成一个原语并交给C 代码之前可能是这样的。
    【解决方案3】:

    “魔术”的原因是“赋值”表单必须有一个真正的变量才能处理。 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 我认为先转换为因子会更干净,然后通过 within()transform() 调用替换级别,返回并分配如此修改的对象。
    • @GavinSimpson - 我同意,我只解释魔法,我不为它辩护;-)
    【解决方案4】:

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

    您展示的用例不同,因为product 不存在于全局环境中,因此它只存在于调用levels&lt;- 的本地环境中,因此您想要进行的更改不会持续存在 -没有重新分配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 ...
    

    我很难看出像您展示的构造在大多数情况下如何有用 - 如果您想更改数据,请更改数据,不要创建另一个副本并更改它(这就是 @987654334 @call 毕竟在做)。

    【讨论】:

      猜你喜欢
      • 2013-02-04
      • 2016-05-15
      • 2023-03-18
      • 1970-01-01
      • 2013-09-24
      • 1970-01-01
      • 2020-07-01
      • 1970-01-01
      相关资源
      最近更新 更多