R语言 “这是什么魔法?”

vq8itlhq  于 2023-07-31  发布在  其他
关注(0)|答案(4)|浏览(139)

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


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

ny6fqffe

ny6fqffe1#

这里的答案很好,但他们忽略了一个重要的问题。让我试着描述一下。
R是一种函数式语言,不喜欢改变它的对象。但它允许赋值语句,使用替换函数:

levels(x) <- y

字符串
相当于

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


诀窍是,这个重写是由<-完成的;它不是由levels<-完成。levels<-只是一个接受输入并给出输出的常规函数;它不会变异任何东西。
根据上面的规则,<-必须是递归的:

levels(x)[1] <- "a"


levels(x) <- `[<-`(levels(x), 1, "a")


x <- `levels<-`(x, `[<-`(levels(x), 1, "a"))


这种纯函数式的转换(直到最后,赋值发生的地方)相当于命令式语言中的赋值,这有点漂亮。在函数式语言中,这种结构被称为lens。在一些编程语言中使用透镜可能会很尴尬,但在R中它们只是工作。
但是,一旦你定义了像levels<-这样的替换函数,你会得到另一个意想不到的意外收获:你不仅有赋值的能力,你还有一个方便的函数,它接受一个因子,并给出另一个不同水平的因子。真的没有什么“任务”!
因此,您所描述的代码只是利用了levels<-的另一种解释。我承认levels<-这个名字有点令人困惑,因为它暗示了一个赋值,但这不是正在发生的事情。代码只是建立了一种管道:

  • dat$product开始
  • 将其转换为因子
  • 更改级别
  • 存储在res

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

fiei3ece

fiei3ece2#

没有魔法,这就是如何定义(子)赋值函数。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! ;-)

htrmnn0y

htrmnn0y3#

之所以会有这种“魔力”,是因为“赋值”形式必须有一个真实的的变量来处理。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)
  )

字符串

liwlm1x9

liwlm1x94#

对于用户代码,我想知道为什么要使用这样的语言操作?您问这是什么魔法,其他人指出您正在调用名为levels<-的替换函数。对于大多数人来说,这是一个魔术,真正的预期用途是levels(foo) <- bar
您展示的用例是不同的,因为product不存在于全局环境中,所以它只存在于调用levels<-的本地环境中,因此您想要进行的更改不会持久-没有重新分配dat
在这些情况下,within()是理想的函数。你自然会想写

levels(product) <- bar

字符串
但是product并不作为对象存在。within()解决了这个问题,因为它设置了您希望运行R代码的环境,并在该环境中计算表达式。因此,将调用的返回对象分配给within()会在正确修改的 Dataframe 中成功。
下面是一个示例(您不需要创建新的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<-调用所做的一切)。

相关问题