【问题标题】:Sub-assign by reference on vector in R通过引用在 R 中的向量上进行子分配
【发布时间】:2015-08-01 09:03:07
【问题描述】:

我能否以某种方式在原子向量上使用通过引用进行子分配?
当然不用将其包装在 1 列 data.table 中以使用:=

library(data.table)
N <- 5e7
x <- sample(letters, N, TRUE)
X <- data.table(x = x)
upd_i <- sample(N, 1L, FALSE)
system.time(x[upd_i] <- NA_character_)
#    user  system elapsed 
#    0.11    0.06    0.17 
system.time(X[upd_i, x := NA_character_])
#    user  system elapsed 
#    0.00    0.00    0.03 

如果 R6 可以提供帮助,我愿意接受 R6 解决方案,因为它已经是我的部门之一。
我已经检查了 &lt;- 内的 R6 对象仍然可以复制:gist

【问题讨论】:

  • 有趣。没听说过R6。看起来令人兴奋。
  • @DavidArenburg R6 是 IMO 最好的参考类和 R 中的 OOP 工具。绝对值得学习且易于学习。
  • 我猜你可以通过引用 Rcpp 进行修改。谷歌让步:stackoverflow.com/q/11300048/1191259 Ari 的问题答案链接有一个简单的功能,它优于其他功能。

标签: r data.table r6


【解决方案1】:

在最新的 R 版本(3.1-3.1.2+ 左右)中,对向量的赋值不会复制。但是,您不会通过运行 OP 的代码看到这一点,原因如下。因为您重用 x 并将其分配给其他对象,所以 R 不会被通知 x 在那时被复制,并且必须假设它不会被复制(在上面的特定情况下,我认为它会最好在data.table::data.table 中更改它并通知R 已制作副本,但这是一个单独的问题-data.frame 遇到同样的问题),因此它在第一次使用时复制x。如果你稍微改变一下命令的顺序,你会发现没有区别:

N <- 5e7
x <- sample(letters, N, TRUE)
upd_i <- sample(N, 1L, FALSE)
# no copy here:
system.time(x[upd_i] <- NA_character_)
#   user  system elapsed 
#      0       0       0 
X <- data.table(x = x)
system.time(X[upd_i, x := NA_character_])
#   user  system elapsed 
#      0       0       0 

# but now R will copy:
system.time(x[upd_i] <- NA_character_)
#   user  system elapsed 
#   0.28    0.08    0.36 

(旧答案,主要留作好奇)

您实际上可以使用 data.table := 运算符来修改您的向量(我认为您需要 R 版本 3.1+ 以避免在 list 中的复制):

modify.vector = function (v, idx, value) setDT(list(v))[idx, V1 := value]

v = 1:5
address(v)
#[1] "000000002CC7AC48"

modify.vector(v, 4, 10)
v
#[1]  1  2  3 10  5

address(v)
#[1] "000000002CC7AC48"

【讨论】:

  • 不能对解决方案达成更多共识,我对其进行了基准测试,它非常快。 @eddi 我认为这是一个很好的 PR 功能。
  • 嗯,我不知道为什么会这样......也就是说,如果你这样做res &lt;- setDT(list(v))[4, V1 := 10],你会得到一个data.table,而不是一个向量。我曾经问过similar question,并被 R-devs 告知这不应该工作
  • @DavidArenburg 之所以起作用,是因为listsetDT 都不会复制基础数据。他们确实创建了新对象,尽管它们正在包装数据,因此 res 是新对象,但其中包含原始向量。
  • @jangorecki 嗯,虽然这个答案可能只适用于 R 3.1+,但实际上对于 R 3.1+ 来说已经过时了,因为我很确定现在没有为常规向量分配制作额外的副本,即 @ 987654337@不会做任何不必要的复制
【解决方案2】:

正如@Frank 所建议的,可以使用Rcpp 来做到这一点。这是一个包含受 Rcpp 的 dispatch.h 启发的宏的版本,它处理所有原子向量类型:

mod_vector.cpp

#include <Rcpp.h>
using namespace Rcpp;

template <int RTYPE>
Vector<RTYPE> mod_vector_impl(Vector<RTYPE> x, IntegerVector i, Vector<RTYPE> value) {
  if (i.size() != value.size()) {
    stop("i and value must have same length.");
  }
  for (int a = 0; a < i.size(); a++) {
    x[i[a] - 1] = value[a];
  }
  return x;
}

#define __MV_HANDLE_CASE__(__RTYPE__) case __RTYPE__ : return mod_vector_impl(Vector<__RTYPE__>(x), i, Vector<__RTYPE__>(value));

// [[Rcpp::export]]
SEXP mod_vector(SEXP x, IntegerVector i, SEXP value) {
  switch(TYPEOF(x)) {
    __MV_HANDLE_CASE__(INTSXP)
    __MV_HANDLE_CASE__(REALSXP)
    __MV_HANDLE_CASE__(RAWSXP)
    __MV_HANDLE_CASE__(LGLSXP)
    __MV_HANDLE_CASE__(CPLXSXP)
    __MV_HANDLE_CASE__(STRSXP)
    __MV_HANDLE_CASE__(VECSXP)
    __MV_HANDLE_CASE__(EXPRSXP)
  }
  stop("Not supported.");
  return x;
}

例子:

x <- 1:20
address(x)
#[1] "0x564e7e8"
mod_vector(x, 4:5, 12:13)
# [1]  1  2  3 12 13  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
address(x)
#[1] "0x564e7e8"

与 base 和 data.table 方法的比较。可以看出速度快了很多:

x <- 1:2e7
microbenchmark::microbenchmark(mod_vector(x, 4:5, 12:13), x[4:5] <- 12:13, modify.vector(x, 4:5, 12:13))
#Unit: microseconds
#                         expr     min       lq        mean    median         uq
#    mod_vector(x, 4:5, 12:13)   5.967   7.3480    15.05259     9.718    21.0135
#              x[4:5] <- 12:13   2.953   5.3610 45722.61334 48122.996 52623.1505
# modify.vector(x, 4:5, 12:13) 954.577 988.7785  1177.17925  1021.380  1361.1210
#        max neval
#     58.463   100
# 126978.146   100
#   1559.985   100

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-18
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    相关资源
    最近更新 更多