Chapter 17 Rのオブジェクトを扱う際の注意点
17.1 ベクトル同士の代入
ベクトルや行列同士で代入する際に、単純に v2 = v1
で代入すると v1 の要素の値が v2 にコピーされるのではなく、v1 と v2 は同じオブジェクトに対する別名となります。そのため v1 の値を変更すると v2 の値も変更されてしまいます。それを避けるためには clone()
関数を使用します。すると v1 の値を変更しても、v2 の値は変更されません。
C++ に詳しい人のために説明すると、Rcpp のデータ型は内部にオブジェクトの値そのものではなく、オブジェクトへのポインタを保持しています。そのため、単純に v2 = v1
で代入すると v1 が指し示すオブジェクトへのポインターの値がコピーされるので v1 と v2 は同じオブジェクトを指し示す結果となります。これをシャロー(浅い)コピーと呼びます。それに対して、v2 = clone(v1)
を用いた場合には、v1
が持つポインタが指し示すオブジェクトの値を複製して、新たに別のオブジェクトを作成します。これをディープ(深い)コピーと呼びます。
下のコード例では、浅いコピーと深いコピーの後に代入した元のベクトルの値を変更した時の結果の違いを示します。
NumericVector v1 = {1,2,3};
NumericVector v2 = v1; // v2 は単純な代入(浅いコピー)
NumericVector v3 = clone(v1); // v3 は clone() で代入(深いコピー)
v1[0] = 100; // v1 の値を変更します
// 値を確認すると v2 には v1 への変更が影響していますが
// v3 には影響していないことがわかります
Rcout << "v1 = " << v1 << endl; // 100 2 3
Rcout << "v2 = " << v2 << endl; // 100 2 3
Rcout << "v3 = " << v3 << endl; // 1 2 3
C++に詳しい人のために説明すると、Rcppのデータ型は内部にオブジェクトの値そのものではなく、オブジェクトへのポインタを保持しています。そのため、単純に v2 = v1
で代入するとすると内部のポインターの値がコピーされてしまうので、このような現象が起きる。
17.2 要素番号の型
32 bit システムやバージョン 2 以前の R ではベクトルの要素番号には int
型が使われていたため、ベクトルの要素数の最大値は 2^31 - 1 となっていました。しかし、現在一般的となっている 64 bit システムにおけるバージョン3以降のRではこれよりも要素数の大きいベクトル(Long Vector)を扱うことができます。Rcpp で Long Vector をサポートするためには、要素数や要素番号を変数として保持する場合に int
型ではなく R_xlen_t
型を用います。64 bit システムでも要素番号として int
型を用いることもできますが、その場合には長さが 2^31 - 1 を超えるベクトルを渡された時に処理ができなくなります。
17.3 []演算子の返値
[]
や ()
演算子でベクトルの要素へアクセスした時の返値は、Vector
そのものではなく Vector::Proxy
という型となっています。そのため、v[]
を他の関数の引数として与えるとコンパイルエラーになることがあります。その場合には、as<T>()
を用いて、目的の型 T
(NumericVector
など) に変換します。
NumericVector v {1,2,3,4,5};
IntegerVector i {1,3};
// これはコンパイルエラーとなる
//double x1 = sum(v[i]);
// 変数として保持する
NumericVector vi = v[i];
double x2 = sum(vi);
// as<T>() で変換する
double x3 = sum(as<NumericVector>(v[i]));
#四則演算と比較演算