函数返回data.table时是否复制?

时间:2014-04-01 18:20:04

标签: r data.table

我正在更新一组以前只接受data.frame个对象以使用data.table参数的函数。

我决定使用R的方法调度来实现该函数,以便使用data.frame的旧代码仍然可以使用更新的函数。在我的一个函数中,我接受data.frame作为输入,修改它,然后返回修改后的data.frame。我也创建了data.table实现。例如:

# The functions
foo <- function(d) {
  UseMethod("foo")
}

foo.data.frame <- function(d) {
  <Do Something>
  return(d)
}

foo.data.table <- function(d) {
  <Do Something>
  return(d)
}

我知道data.table无需复制即可进行更改,我在实现foo.data.table的同时牢记这一点。但是,我在函数末尾返回data.table对象,因为我希望我的旧脚本能够使用新的data.table对象。这会复制data.table吗?我怎么检查?根据文档,必须非常明确地创建data.table的副本,但在这种情况下我不确定。

当我不需要使用data.tables时,我想要返回一些内容:

我的旧脚本看起来像这样

someData <- read.table(...)
...
someData <- foo(someData)

我希望脚本能够通过更改数据摄取行来与data.table一起运行。换句话说,我希望只需将someData <- read.table(...)更改为someData <- fread(...)即可使用该脚本。

2 个答案:

答案 0 :(得分:5)

感谢Arun在评论中的回答。我将在他的评论中使用他的例子来回答这个问题。

可以使用tracemem功能跟踪R中的对象来检查是否正在制作副本。从功能的帮助文件?tracemem中,描述说明:

  

此函数标记一个对象,以便在内部代码复制对象时打印一条消息。这是R中难以预测的内存使用的主要原因。

例如:

# Using a data.frame
df <- data.frame(x=1:5, y=6:10)
tracemem(df)
## [1] "<0x32618220>"
df$y[2L] <- 11L
## tracemem[0x32618220 -> 0x32661a98]: 
## tracemem[0x32661a98 -> 0x32661b08]: $<-.data.frame $<- 
## tracemem[0x32661b08 -> 0x32661268]: $<-.data.frame $<- 
df
##   x  y
## 1 1  6
## 2 2 11
## 3 3  8
## 4 4  9
## 5 5 10

# Using a data.table
dt <- data.table(x=1:5, y=6:10)
tracemem(dt)
## [1] "<0x5fdab40>"
set(dt, i=2L, j=2L, value=11L) # No memory output!
address(dt) # Verify the address in memory is the same
## [1] "0x5fdab40"
dt
##    x  y
## 1: 1  6
## 2: 2 11
## 3: 3  8
## 4: 4  9
## 5: 5 10

在更改data.frame中的一个元素时,data.frame对象似乎被复制两次,而data.table在不进行复制的情况下进行了修改!

根据我的问题,我可以跟踪data.tabledata.frame对象d,然后再将其传递给函数foo,以检查是否有任何副本做了。

答案 1 :(得分:3)

不确定这会增加任何内容,但作为警示故事会注意以下行为:

library(data.table)
foo.data.table <- function(d) {
  d[,A:=4]
  d$B <- 1
  d[,C:=1]
  return(d)
}
set.seed(1)
dt     <- data.table(A=rnorm(5),B=runif(5),C=rnorm(5))
dt
#             A         B            C
# 1: -0.6264538 0.2059746 -0.005767173
# 2:  0.1836433 0.1765568  2.404653389
# 3: -0.8356286 0.6870228  0.763593461
# 4:  1.5952808 0.3841037 -0.799009249
# 5:  0.3295078 0.7698414 -1.147657009
result <- foo.data.table(dt)
dt
#    A         B            C
# 1: 4 0.2059746 -0.005767173
# 2: 4 0.1765568  2.404653389
# 3: 4 0.6870228  0.763593461
# 4: 4 0.3841037 -0.799009249
# 5: 4 0.7698414 -1.147657009
result
#    A B C
# 1: 4 1 1
# 2: 4 1 1
# 3: 4 1 1
# 4: 4 1 1
# 5: 4 1 1

因此,显而易见,dt通过引用foo.data.table(...)传递,第一个语句d[,A:=4]通过引用对其进行修改,更改A中的dt列}。

第二个语句d$B <- 1强制创建函数内部d(也称为d)范围的副本。然后,第三个陈述d[,C:=1],通过引用修改 (但不影响dt),然后return(d)返回副本。

如果更改第二个和第三个语句的顺序,则dt上函数调用的效果会有所不同。