【问题标题】:In-place list modification without for loop in RR中没有for循环的就地列表修改
【发布时间】:2016-03-22 05:27:58
【问题描述】:

我想知道是否有一种方法可以在不使用for 循环的情况下对列表中的对象进行就地修改。这将很有用,例如,如果列表中的单个对象又大又复杂,那么我们希望避免制作整个对象的临时副本。例如,考虑以下代码,该代码创建三个数据帧的列表,然后为一列数据计算所有三个数据帧的最大值向量,然后将该向量分配给每个原始数据帧。 (在 ggplot2 中对齐绘图时需要这样的代码。)

data_list <- lapply(1:3, function(x) data.frame(x=rnorm(10), y=rnorm(10), z=rnorm(10)))

max_x <- do.call(pmax, lapply(data_list, function(d){d$x}))

for( i in 1:length(data_list))
{
  data_list[[i]]$x <- max_x
}

有没有办法在没有for 循环的情况下编写最后一部分?

我收到的一些问题的答案:

  1. 是什么让我认为会制作副本?我不确定是否会制作副本。我正在处理的实际场景涉及整个 ggplot 图(参见例如here)。由于它们相当大且复杂,因此不得复制。

  2. for 循环有什么问题?我宁愿直接遍历一个列表,也不愿引入一个计数器。我不喜欢柜台。

  3. 为什么不使用data.table?因为我实际上是在操作 ggplot 图,而不是数据框。此处提供的代码只是一个简化示例。

【问题讨论】:

  • 是什么让您认为正在复制整个对象?
  • @MichaelChirico 循环内的第一个赋值似乎是复制:library(data.table); for( i in 1:length(data_list)) { print(address(data_list)); data_list[[i]]$x &lt;- max_x; print(address(data_list)) }
  • @Claus Wilke:使用for 循环有什么问题?
  • R 将仅复制包含 data.frame 和修改后的列的列表结构(3 个单元格)。不需要复制未修改的列——它们是共享的。即不会复制yz 列:library(data.table); for( i in 1:length(data_list)) { print(address(data_list[i]$y)); data_list[[i]]$x &lt;- max_x; print(address(data_list[i]$y)) }
  • 没有 for 循环:sapply(1:3,function(y){ data_list[[y]]$x&lt;&lt;-max_x })。如果我使用 Roland 的方法,似乎没有复制完成,但我不是 100% 确定。

标签: r


【解决方案1】:

Base R 数据结构在修改时复制并共享。以具有三个数字列的 data.frame 为例。每个 data.frame 是一个长度为 3 的“列表”向量,每个包含对基础列的数字向量的引用。如果我们修改/替换第一列,R 会创建一个新的长度为 3 的 data.frame “列表”,其中包含对新(已修改)列和其他两个未修改列的引用。

让我们看看使用address函数*

set.seed(1)
data_list <- lapply(1:3, function(x) data.frame(x=rnorm(10), y=rnorm(10), z=rnorm(10)))

before <- rapply(data_list,address)

现在你想用

替换第一列
max_x <- do.call(pmax, lapply(data_list, function(d){d$x}))

如何做到这一点并不重要,但这是一种没有显式循环与计数器的方法

data_list <- lapply(data_list,`[<-`,"x",value=max_x)    

after <- rapply(data_list,address)

现在比较之前和之后的地址。请注意yz 列的地址没有改变。此外,所有“之后”x 列都有相同的地址——max_x 的地址!

address(max_x)
[1] "05660600"

cbind(before,after)

  before     after     
x "0565F530" "05660600"
y "0565F400" "0565F400"
z "05660AC0" "05660AC0"
x "05660A28" "05660600"
y "05660990" "05660990"
z "05660860" "05660860"
x "056607C8" "05660600"
y "05660730" "05660730"
z "05660698" "05660698"

这意味着您不必像可能考虑对大型数据结构进行更改那样担心。一般来说,只有修改过的部分和数据结构的骨架需要被替换。在此示例中,无论如何都必须创建max_x 向量,因此唯一的开销是创建一个新的 3 个单元格 data.frame “列表”并使用 3 个引用填充它**。但是,如果您迭代地“敲打”更改或使用子向量而不是整个列,这可能会开始变得低效。这些是data.table 的用例,不适用于本示例。


* 这里使用的address函数是从data.table包中导出的。

** 当然,在这个例子中,3 个单元格的外部列表“列表”包含 3 个 data.frames 本身。

【讨论】:

  • 可能值得注意的是,x 列仅与max_x 具有相同的地址,直到它们被更改。然后显然必须进行复制。
  • 直到它们再次被更改?该示例是 max_x 的更改,因此不需要复制,只需更改对max_x 的引用。如果没有其他内容指向旧的 x 列,那么它们将受到垃圾回收。
  • 我的意思是这样的:x &lt;- 1:2; y &lt;- x 如果你这样做 x[1] &lt;- 3y[1] &lt;- 3 显然需要一个副本。
猜你喜欢
  • 2016-10-16
  • 2021-08-11
  • 1970-01-01
  • 2023-01-25
  • 1970-01-01
  • 2018-10-18
  • 2018-12-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多