Подтвердить что ты не робот

Подцепить по ссылке на вектор в R

Могу ли я использовать суб-назначение по ссылке на атомные векторы?
Конечно, не обертывая его в 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, поскольку он уже один из моих депов.
Я уже проверял, что <- внутри объекта R6 все еще делает копию: gist.

4b9b3361

Ответ 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"

Ответ 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"

Сравнение с базовыми и 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