【问题标题】:update a data frame and environment in R更新 R 中的数据框和环境
【发布时间】:2013-08-13 00:13:47
【问题描述】:

我不太清楚为什么数据框对象没有更新

d <- data.frame(titi=c(0))
(function(dataset) {
  dataset[["toto"]] <- 1;
  print(names(dataset)) #has "toto" and "titi"
})(d)
print(names(d)) # no has "toto", only "titi" 

这是怎么回事?

我有一个解决方法,因为在我的代码中我还捕获了变量并使用 &lt;&lt;- 更新捕获的变量,但我想知道机制。

我知道一般突变的危险等等。我只是不明白这里的机制。

编辑

虽然这似乎是一个语言级别的特性的共识,但我不遵循这个论点,好像我使用了一个封闭的结构,数据表,它可以变异:

d <- data.table(titi=c(0))
(function(dataset) {
  dataset[,toto:=1]
  print(names(dataset)) #"titi" "toto"
})(d)
print(names(d)) #"titi" "toto"

【问题讨论】:

  • R 中的函数不会“就地”或“通过引用”更改数据。如果您希望该函数更改“d”,那么您需要将其输出分配给“d”。使用&lt;&lt;- 绕过 R 的这种设计被认为是危险和丑陋的。
  • 所以根据我是否在函数内部,我的 instruction 有不同的含义?
  • 您的函数复制了d(命名为dataset),添加了新列,然后丢弃了结果。正如@Dwin 所说,您需要分配函数输出。
  • 请定义“意义”。它有不同的环境,相同的地方“意义”。
  • 'data.frame' 对象只是列表的一种形式。在特定于具有“data.frame. data.table”类的列表的函数中使用&lt;&lt;-没有任何意义,对象在设计上有所不同,大多数R对象和R函数都不像' data.table' 对象或函数。

标签: r scoping


【解决方案1】:

这里发生了什么?

你还没有读过The introduction to R,尤其是Assignment within functions上的部分或writing your own functions上的部分

两个相关的引号是

请注意,在函数内完成的任何普通赋值都是本地和临时的,并且在退出函数后会丢失。

表达式的值是函数返回的值。

在您的情况下,您的函数调用的最终值为names,它将返回一个字符向量....


范围界定可能是一个复杂的问题,但您的示例是一个简单的案例。

如果您想要更完整的参考,请查看R language definition


但是data.table 做到了....

是的,:= 中的 data.table 通过引用分配。这不是普通的任务

data.table 继承自 data.frame。它们不相同,:= 按引用分配(也称为setattr)按引用分配。这违反了标准R 成语。 这也可能导致问题,请参阅Why does data.table update names(DT) by reference, even if I assign to another variable?

还有其他方法可以绕过它,但标准的R 习惯用法是函数内的普通赋值是局部的和临时的,并且在退出后会丢失

你也可以考虑使用ReferenceClasses(见?setRefClass


不仅限于列表/data.frames。原子向量是一样的

  mydf <- data.frame(a=1)
  mylist <- list(a=1)
  mynumeric <- c(a = 1)

mydf <- data.frame(a=1)
mylist <- list(a=1)
mynumeric <- c(a = 1)

foo <- function(x){x[['b']] <- 1; print(names(x))}

# data.frame
foo(mydf)
# [1] "a" "b"
mydf
#    a
#  1 1

# list
foo(mylist)
# [1] "a" "b"
mylist
# $a
# [1] 1

# atomic
foo(mynumeric)
# [1] "a" "b"
 mynumeric
# a 
#  1 

【讨论】:

  • 感谢您的参考。一个快速的 .Internal(inspect(dataset)) 突出了复制变量的语义和优化的实现之间的区别。
  • 虽然有用且必不可少,但这并不完整。实际上,它似乎并没有在 langage 级别强制执行,而是依赖于数据框的特定实现。例如,如果您使用数据表,则会发生破坏性更新。
  • @nicolas -- data.table 不是 data.frame(它继承,但有重要区别)。给猫剥皮的方法有很多(在本地也可以在全局范围内剥皮)。 data.table 包可以做到这一点(但是当尝试以编程方式做事情时,这可能有它自己的问题,而一般 R 没有。)
  • 您的第二段可以从答案中删除而不影响其正确性...
  • 您可能会争辩说没有什么是“在语言级别强制执行的”,因为您可以调用可以更改内存中对象的 C 代码。
【解决方案2】:

这里的代码实现了您的预期,即使用匿名函数来“通过引用”更改列表参数:

d <- data.frame(titi=c(0))
(function(dataset) { nam <-deparse(substitute(dataset))
   dataset[["toto"]] <- 1; assign(nam ,  dataset, envir=parent.frame() )
   print(names(dataset)) #has "toto" and "titi"
 })(d)
#[1] "titi" "toto"
 print(names(d))
#[1] "titi" "toto"
 d
#  titi toto
#1    0    1

我不认为你会发现它比:

d <- data.frame(titi=c(0))
 addcol <- function(dataset) { 
   dataset[["toto"]] <- 1
   dataset}
d <-addcol(d)
d
#  titi toto
#1    0    1

【讨论】:

  • 当然第二个更好。更实用。显式状态转换。等等。
【解决方案3】:

做类似的事情

foo <- data.frame(x=2)
(function(df) {df[["x"]] <- 1; df})(foo)

没有将返回值赋值给任何东西,结果发现foo没有改变,真的和

x <- 2
(function(n) {n <- 1; n})(x)

发现x 没有改变。为什么你会期待别的东西是个好问题。


我认为这将是关于 R 的更常见(和合理)的问题之一,即:为什么要从 local 修改驻留在 外部 环境中的数据集环境,什么都不做。也就是说,给定以下代码
foo <- data.frame(x=2)
(function() {foo[["x"]] <- 1; foo})()

为什么foo 还没有改变?

在这种情况下,考虑一下可能会有用

df[[x]] <- y

作为语法糖

df <- data.frame(df[names(df)!="x"], x = y)

因为毕竟,在函数式语言中你没有副作用,而修改对象的一部分是一种副作用。换句话说,您实际上是在制作带有所需更改的 df 的新副本。

与任何分配一样,这发生在本地环境中。如果原来的df 也恰好位于此环境中,那么新副本将替换旧副本。如果没有,那么您现在有两个版本的df:一个在本地环境中,一个在其他地方。如果你不给任何东西分配新版本,它会在函数返回时丢失。

【讨论】:

    猜你喜欢
    • 2021-08-01
    • 1970-01-01
    • 2015-12-30
    • 1970-01-01
    • 2019-07-08
    • 1970-01-01
    • 2022-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多