为data.table对象编写函数(过程)
在“用于数据分析的软件:用R编程的软件”一书中,约翰·钱伯斯强调函数通常不应写为副作用; 相反,函数应该返回一个值,而不需要在其调用环境中修改任何变量。 相反,使用data.table对象编写好的脚本应特别避免使用带<-
的对象分配,通常用于存储函数的结果。
首先,是一个技术问题。 设想一个名为proc1
的R函数,它接受一个data.table
对象x
作为它的参数(除了可能还有其他参数)。 proc1
返回NULL,但使用:=
修改x
。 根据我的理解, proc1
调用proc1(x=x1)
只是因为承诺的工作方式而制作了x1
的副本。 但是,如下所示,原始对象x1
仍由proc1
修改。 为什么/这是怎么回事?
> require(data.table)
> x1 <- CJ(1:2, 2:3)
> x1
V1 V2
1: 1 2
2: 1 3
3: 2 2
4: 2 3
> proc1 <- function(x){
+ x[,y:= V1*V2]
+ NULL
+ }
> proc1(x1)
NULL
> x1
V1 V2 y
1: 1 2 2
2: 1 3 3
3: 2 2 4
4: 2 3 6
>
此外,似乎使用proc1(x=x1)
不会比直接在x上执行过程慢,这表明我对承诺的模糊理解是错误的,并且它们以逐行引用的方式工作:
> x1 <- CJ(1:2000, 1:500)
> x1[, paste0("V",3:300) := rnorm(1:nrow(x1))]
> proc1 <- function(x){
+ x[,y:= V1*V2]
+ NULL
+ }
> system.time(proc1(x1))
user system elapsed
0.00 0.02 0.02
> x1 <- CJ(1:2000, 1:500)
> system.time(x1[,y:= V1*V2])
user system elapsed
0.03 0.00 0.03
因此,假设将一个data.table参数传递给一个函数不会增加时间,那么就可以为data.table对象编写过程,同时包含data.table的速度和函数的普遍性。 但是,根据约翰钱伯斯的说法,这些函数不应该有副作用,在R中编写这种类型的过程编程真的是“好的”吗? 他为什么认为副作用是“坏”? 如果我要忽视他的建议,我应该注意哪些陷阱? 我能做些什么来写出“好”的data.table程序?
是的,在data.table
添加,修改和删除列是通过reference
完成的。 从某种意义上说,这是一件好事,因为data.table
通常包含大量数据,并且每次对其进行更改时都会重新分配所有数据,这会非常耗费内存和时间。 另一方面,这是一件坏事,因为它违背了R通过默认使用pass-by-value
来促进的no-side-effect
函数编程方法。 无副作用编程时,调用函数时几乎不用担心:您可以放心,您的输入或环境不会受到影响,并且您可以专注于函数的输出。 这很简单,因此很舒服。
如果你知道你在做什么,当然可以不考虑约翰钱伯斯的建议。 关于编写“好”的data.tables程序,这里有几条规则我会考虑如果我是你,作为一种限制复杂性和副作用数量的方法:
do.something.to(table)
而不是table <- do.something.to(table)
。 如果函数有另一个(“真实”)输出,那么当调用result <- do.something.to(table)
,很容易想象如何将注意力集中在输出上,并忘记调用函数有一个副作用在你的桌子上。 虽然“一个输出/无副作用”函数是R中的标准,但上述规则允许“一个输出或副作用”。 如果你同意副作用是某种形式的输出,那么你会同意我不会太松懈地遵守R的单输出函数式编程风格。 允许函数具有多种副作用会稍微延长一点; 不是你不能这样做,但我会尽可能地避免它。
文件可以改进(建议非常受欢迎),但这里是目前的情况。 也许它应该说“即使在功能内”?
In ?":="
:
data.tables不会通过:=,setkey或任何其他set *函数进行复制。 请参阅副本。
DT通过引用进行修改,并返回新值。 如果您需要复印件,请先复印一份(使用DT2 =复印件(DT))。 回想一下,这个包适用于大数据(混合列类型,多列键),其中更新引用可能比复制整个表快许多数量级。
并在?copy
(但我意识到这是与setkey混淆):
输入被引用修改,并返回(不可见),因此可以在复合语句中使用; 例如setkey(DT,a)[J(“foo”)]。 如果您需要复印件,请先复印一份(使用DT2 =复印件(DT))。 copy()在以下情况下有时也会有用:=用于通过引用将其分配给列。 见?复制。 请注意,setattr也位于包位中。 这两个包只是在C级暴露R的内部setAttrib函数,但返回值不同。 bit :: setattr返回NULL(无形)以提醒您该函数用于其副作用。 data.table :: setattr返回已更改的对象(不可见),用于复合语句。
有趣的是,关于bit::setattr
的最后两个句子与flodel的第2点有关。
另请参阅以下相关问题:
确切地了解data.table何时是对另一个data.table的引用(vs副本)
按引用传递:data.table包中的:=运算符
data.table 1.8.1 .:“DT1 = DT2”与DT1 = copy(DT2)不一样?
我非常喜欢你这个问题的一部分:
这使得为data.table对象编写过程成为可能,同时结合data.table的速度和函数的普遍性。
是的,这绝对是其中的一个意图。 考虑数据库的工作方式:通过引用(插入/更新/删除)数据库中的一个或多个(大)表来更改大量不同的用户/程序。 这在数据库领域工作得很好,更像data.table的想法。 因此,主页上的svSocket视频以及insert
和delete
(仅供参考,仅使用动词,副作用函数)。
上一篇: Writings functions (procedures) for data.table objects
下一篇: Add multiple columns to R data.table in one function call?