【问题标题】:Dynamically add column to xts object将列动态添加到 xts 对象
【发布时间】:2016-01-07 11:33:24
【问题描述】:

如果您提前知道列的名称,那么将列添加到 xts 对象很简单。例如,添加一个名为“b”的列:

n <- 5
x <- merge(xts(order.by = as.Date('2015-1-1') + 1:n), a = rnorm(n))
x$b <- rnorm(n)

添加动态命名的列(即名称仅在运行时知道的列)更难:

new.col.name <- 'c' # known only at runtime
x[, new.col.name] <- rnorm(n) # this generates an error

一种方法是添加一个具有临时名称的列,然后重命名它:

stopifnot(!('tmp' %in% names(x)))
x$tmp <- rnorm(n)
names(x)[names(x) == 'tmp'] <- new.col.name

有没有更好的方法来做到这一点? (另外,分配给 xts 对象的names 是否会导致生成对象的副本?因此,例如,如果n 非常大,上述方法是否可以正常工作?)

【问题讨论】:

    标签: r xts


    【解决方案1】:

    最简单/最清晰的做法是在将新列转换为矩阵之后将原始对象与新列合并(这样您就可以设置列名)。

    set.seed(21)
    newData <- rnorm(n)
    x1 <- merge(x, matrix(newData, ncol=1, dimnames=list(NULL, new.col.name)))
    # another way to do the same thing
    dim(newData) <- c(nrow(x), 1)
    colnames(newData) <- new.col.name
    x2 <- merge(x, newData)
    

    回答您的第二个问题:是的,在 xts 对象上分配名称(和 colnames)会创建一个副本。您可以通过使用tracememgc 的输出来查看它。

    > R -q  # new R session
    R> x <- xts::.xts(1:1e6, 1:1e6)
    R> tracemem(x)
    [1] "<0x2892400>"
    R> gc()
              used (Mb) gc trigger (Mb) max used (Mb)
    Ncells  259260 13.9     592000 31.7   350000 18.7
    Vcells 1445207 11.1    4403055 33.6  3445276 26.3
    R> colnames(x) <- "hi"
    tracemem[0x2892400 -> 0x24c1ad0]: 
    tracemem[0x24c1ad0 -> 0x2c62d30]: colnames<- 
    tracemem[0x2c62d30 -> 0x3033660]: dimnames<-.xts dimnames<- colnames<- 
    tracemem[0x3033660 -> 0x3403f90]: dimnames<-.xts dimnames<- colnames<- 
    tracemem[0x3403f90 -> 0x37d48c0]: colnames<- dimnames<-.xts dimnames<- colnames<- 
    tracemem[0x37d48c0 -> 0x3033660]: dimnames<-.xts dimnames<- colnames<- 
    R> gc()
              used (Mb) gc trigger (Mb) max used (Mb)
    Ncells  259696 13.9     592000 31.7   350000 18.7
    Vcells 1445750 11.1    4403055 33.6  3949359 30.2
    R> print(object.size(x), units="Mb")
    7.6 Mb
    

    您可以看到colnames&lt;- 调用导致使用约 4MB 的额外内存(“最大使用量 (Mb)”增加了该数量)。整个 xts 对象约为 8MB,其中一半是coredata,另一半是index。所以使用的4MB额外内存是复制coredata

    如果要避免复制,可以手动设置。但要小心,因为您可能会做一些否则会被colnames&lt;-.xts 中的“检查”发现的事情。

    > R -q  # new R session
    R> x <- xts::.xts(1:1e6, 1:1e6)
    R> tracemem(x)
    [1] "<0x2cc5330>"
    R> gc()
              used (Mb) gc trigger (Mb) max used (Mb)
    Ncells  256397 13.7     592000 31.7   350000 18.7
    Vcells 1440915 11.0    4397699 33.6  3441761 26.3
    R> attr(x, 'dimnames') <- list(NULL, "hi")
    tracemem[0x2cc5330 -> 0x28f4a00]: 
    R> gc()
              used (Mb) gc trigger (Mb) max used (Mb)
    Ncells  256403 13.7     592000 31.7   350000 18.7
    Vcells 1440916 11.0    4397699 33.6  3441761 26.3
    R> print(object.size(x), units="Mb")
    7.6 Mb
    

    【讨论】:

    • 合并比重命名好吗?
    【解决方案2】:

    我相信没有好的选择,但是列名只是一个属性,所以修改起来很便宜,而且不会复制。 (编辑: 呃-哦,刚刚看到我似乎在说与 Joshua 相反的说法。--> 参见 cmets 中的讨论。似乎 dimnames.xts 不仅仅设置一个属性,而且确实涉及复制基础数据,所以要小心。)

    您也可以使用cbind(),它是merge.xts 的同义词,但是(AFAIK)它对您展示的x$b 方法没有任何优势:

    n <- 5
    x <- merge(xts(order.by = as.Date('2015-1-1') + 1:n), a = rnorm(n))
    x$b <- rnorm(n)
    x = cbind(x, c = rnorm(n))
    colnames(x)[3] = "real name"
    

    我还展示了一种更改列名的方法。如果您不知道它是第 3 列,那么通用方法是:colnames(x)[length(colnames(x))] = "real name"

    【讨论】:

    • 正如我在回答中所说,设置 colnames 确实会创建副本。例如:x &lt;- .xts(1,1); tracemem(x); colnames(x) &lt;- "hi"
    • 我正在编辑,而您添加了该评论@JoshuaUlrich 实际的矩阵数据没有被复制是吗?
    • 实际的矩阵数据被复制了,遗憾的是。我在答案中添加了一些细节以进行演示。
    • @JoshuaUlrich 这听起来很疯狂!我找到了setAttrib()github.com/wch/r-source/blob/…,但我看不到任何关于复制属性附加到的基础数据的任何内容。
    • 请注意,我说colnames&lt;- 会为 xts 对象创建一个副本。您是正确的,设置属性不会创建副本,但这并不是colnames&lt;- 在 xts 对象上所做的全部(请参阅dimnames&lt;-.xts)。
    猜你喜欢
    • 1970-01-01
    • 2013-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-25
    • 1970-01-01
    • 2011-01-05
    相关资源
    最近更新 更多