【问题标题】:R: Pass data.frame by reference to a functionR:通过引用函数传递data.frame
【发布时间】:2016-01-16 03:50:36
【问题描述】:

我将data.frame 作为参数传递给想要更改内部数据的函数:

x <- data.frame(value=c(1,2,3,4))
f <- function(d){
  for(i in 1:nrow(d)) {
    if(d$value[i] %% 2 == 0){
      d$value[i] <-0
    }
  }
  print(d)
}

当我执行f(x) 时,我可以看到里面的data.frame 是如何被修改的:

> f(x)
  value
1     1
2     0
3     3
4     0

但是,我传递的原始data.frame是未修改的:

> x
  value
1     1
2     2
3     3
4     4

通常我已经通过返回修改过的来克服这个问题:

f <- function(d){
  for(i in 1:nrow(d)) {
    if(d$value[i] %% 2 == 0){
      d$value[i] <-0
    }
  }
  d
}

然后调用重新分配内容的方法:

> x <- f(x)
> x
  value
1     1
2     0
3     3
4     0

但是,我想知道这种行为在一个非常大的data.frame 中的影响是什么,是为方法执行而增长的新行为吗?这样做的 R-ish 方式是什么?

有没有办法修改原来的而不在内存中创建另一个?

【问题讨论】:

  • R 按设计复制与修改。使用更大范围的对象(可能使用非标准评估来获取对象的名称)和/或给data.table 一个旋转(它有许多就地习语并且专为更大的数据集而设计)。跨度>
  • r-ish 方法是不使用循环。对你来说什么是“大”
  • 你可能对data.table package感兴趣。

标签: r dataframe pass-by-reference


【解决方案1】:

实际上在 R 中(几乎)每次修改都是在先前数据的副本上执行的(copy-on-writing 行为)。
因此,例如在您的函数内部,当您执行 d$value[i] &lt;-0 时,实际上会创建一些副本。您通常不会注意到它,因为它已经很好地优化了,但是您可以使用 tracemem 函数对其进行跟踪。

话虽如此,如果你的 data.frame 不是很大,你可以坚持使用你的函数返回修改后的对象,因为它毕竟只是一个副本。

但是,如果您的数据集非常大并且每次都进行复制非常昂贵,您可以使用 data.table,它允许就地修改,例如:

library(data.table)
d <- data.table(value=c(1,2,3,4))
f <- function(d){
  for(i in 1:nrow(d)) {
    if(d$value[i] %% 2 == 0){
      set(d,i,1L,0) # special function of data.table (see also ?`:=` )
    }
  }
  print(d)
}

f(d)
print(d)

# results :
> f(d)
   value
1:     1
2:     0
3:     3
4:     0
> 
> print(d)
   value
1:     1
2:     0
3:     3
4:     0

注意

在这种特定情况下,循环可以替换为“矢量化”和更高效的版本,例如:

d[d$value %% 2 == 0,'value'] <- 0

但也许你真正的循环代码更加复杂,不能轻易矢量化。

【讨论】:

  • R 做了一些巧妙的技巧来绕过复制。例如,如果您的函数只添加/删除列,那么执行 df = f(df) 可能不会复制整个数据框。
  • 是的,确实如此,实际上最好在更改代码之前测试是否确实生成了副本。但是我跟踪了循环代码,似乎每次将记录设置为零时都会创建一个副本。所以,如果 data.frame 真的很大,我猜 data.table 能够就地修改对象是要走的路......
  • 实际上在这种特殊情况下,真正的解决方案根本不使用循环。这是非常低效的代码。
  • @digEmAll 现在没有真正的方法可以知道副本是 shallow 还是 deep :-(.df = data.frame(x=1:2, y=3:4); tracemem(df); df$y = 5:6,但它正在浅复制到这里。
  • @Arun:是的,我不认为每次都执行完整的深度复制(否则效率不会很高)。我猜在您的示例中,只是创建了修改后列的新副本,对吗?尽管如此,很遗憾我们无法确切知道哪些是复制的,哪些不是......
猜你喜欢
  • 2012-06-27
  • 1970-01-01
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 2020-10-02
  • 2022-01-11
  • 1970-01-01
  • 2015-04-23
相关资源
最近更新 更多