はじめに

このドキュメントについて

このドキュメントは俺の俺による俺のためのRcppドキュメントである。出来るところは積極的にc++11/0xでいきたい。Bye C++ 03!!!そして、Rcppの機能も出来るだけ新しめの奴ばかりを使っていきたい。だって進化速いからね?またこのドキュメントは随時更新予定であり、

からオリジナルのR Markdownファイルが入手可能だ。

Rcppのインスト-ルとロード

Rcppを使うので、何はともあれまずはRcppのインストールが必要だ。
install.packages("Rcpp")

Windowsの環境においてはC++用コンパイラ(GCC)やUNIXコマンドのセットが入っている

も突っ込んでおく必要がある。これは上記のサイトからexeを落っことしてデフォルトの設定でよいので、インストールを実行する。そして、実際に使うためには、R上でRcppライブラリのロードが必要となる。
library(Rcpp)

C++11の導入

以下の設定によりC++11(Windows版はまだC++0xレベル)を使用することが可能になる。 Linux & Macなら以下の部分は"-std=c++11"で良い(はず)。
Sys.setenv("PKG_CXXFLAGS"="-std=c++0x") 

また、package開発の場合、ソースコード(cpp)ファイルの何処かに

// [[Rcpp::plugins(cpp11)]] 

という記述を施しておけば、上記の設定は不要。

全体的に注意したいこと

  • ベクトルやデータフレームなどのデータ構造において、行や列のインデックスは、R内では1から始まる一方、C++内インデックスは0から始まる点に注意

基本的な評価法

evalCpp関数でワンライナーなC++のコード評価ができる。なので、お試し評価みたいな話であればこれでよい。例えば
evalCpp("2 + 3")
## [1] 5

な感じ。その他にもRcppでは

  • cppFunction : C++で書かれたR内で使える関数を作成する
  • sourceCpp : ファイルや文字列(C++)のコードをコンパイルして実行してくれる

が用意されているが、ここではこの1ファイル(R markdownファイル)で全てを完結させたいのでsourceCppは使わないで、もっぱらcppFunctionを使っていく。このやり方が一番いいのかは不明だ。

NumericVectorについて

基本操作

NumericVectorはその名の通り、ベクトルを操作する型で、基本C++のstdに含まれるvectorであるstd::vectorと似たようなもんだ。NumericVectorの初期化法として、一律な値での初期化が可能。
#5個の要素すべてが3.33であるベクトルを作成
evalCpp("NumericVector(5, 3.0)")
## [1] 3 3 3 3 3
あるいはC++11/0xでは初期化リスト{...}による初期化もできる。
cppFunction('
  NumericVector initializeNumericVector1() 
  {
    NumericVector x = {1,2,3,4};
    return x;
  }
')
initializeNumericVector1()
## [1] 1 2 3 4
あるいはstd::generateを用いて以下のように書く事もできる。ラムダ式も使えるぞ!!!
cppFunction('
  NumericVector initializeNumericVector2() 
  {
    int n = 0;
    NumericVector x(10);
    std::generate(x.begin(), x.end(), [&n]{return n++;});
    return x;
  }
')
initializeNumericVector2()
##  [1] 0 1 2 3 4 5 6 7 8 9
NumericVectorでは、当然、ベクトルとして期待される要素ごとの四則演算も可能。
evalCpp("NumericVector(2, 1.0) + NumericVector(2, 5.0)")
## [1] 6 6
evalCpp("NumericVector(2, 2.0) - NumericVector(2, 6.0)")
## [1] -4 -4
evalCpp("NumericVector(2, 3.0) * NumericVector(2, 7.0)")
## [1] 21 21
evalCpp("NumericVector(2, 4.0) / NumericVector(2, 8.0)")
## [1] 0.5 0.5
一方、Rライクなべき乗計算(^2みたいなの)は出来なくて、C言語っぽいpow関数を使う。
#2を3個持つベクトルの各々をexpして2乗したものを計算
evalCpp("pow(exp(NumericVector(3, 2.0)), 2.0)")
## [1] 54.59815 54.59815 54.59815

これら(exp, pow)は

で説明されているように糖衣構文(要するに略記)だ。中では難解なtemplate祭が開催されている。たとえばexp関数だと

だ。ここはありがたく使わせて頂く方向で行っておきたい。

また、R上で定義した名前付ベクトルの受け渡しも可能で、C++内では[]演算子を通してアクセスすることができる。
cppFunction('
  double namedArgumentNV(NumericVector x) 
  {
    double a = x["a"];
    return a;
  }
')
namedArgumentNV(c(x=100, y=123, a=333))
## [1] 333

std::vector v.s. NumericVectorではNumericVectorの方が速げ

当然、通常のC++に慣れているものとしてはNumericVectorなんぞ使わなくとも、std::vectorでいんじゃね?と思って、速度検証してみた。ネタは何をやるにしても必要になるであろう単なるランダムアクセスだ。この結果を見る限りNumericVectorの方が速いようなので、積極的に乗り換えていこう。
sourceCpp(code='
  #include 
  #include 
  using namespace Rcpp;
  // [[Rcpp::export]]
  double rcppVec(NumericVector xs)
  {
    double sum = 0; 
    for(auto x : xs){sum += x;}
    return sum;
  }    
  // [[Rcpp::export]]
  double stdVec(std::vector & xs)
  {
    double sum = 0; 
    for(auto x : xs){sum += x;}
    return sum;
  }    
')
library(rbenchmark)
benchmark(rcppVec(1:10^5), stdVec(1:10^5), order="relative")[,1:4]
##              test replications elapsed relative
## 1 rcppVec(1:10^5)          100    0.14    1.000
## 2  stdVec(1:10^5)          100    0.22    1.571

NumericMatrixについて

行、列を
cppFunction('
  DataFrame createDataFrameFromMatrix()
  {
    NumericMatrix x(4, 5);
    return DataFrame::create(_["X"]=x(_,1));
  }
')
createDataFrameFromMatrix()
##   X
## 1 0
## 2 0
## 3 0
## 4 0
NumericMatrix NumericMatrix xxの4行目にNumericVector xを代入
cppFunction('
  NumericMatrix createNumericMatrixFromNumericVector()
  {
    NumericVector x(2, 10.0);
    NumericMatrix xx(4, 2);
    xx(3,_) = x;
    return xx;
  }
')
createNumericMatrixFromNumericVector()
##      [,1] [,2]
## [1,]    0    0
## [2,]    0    0
## [3,]    0    0
## [4,]   10   10
cppFunction('
  NumericMatrix createNumericMatrixFromNumericVector2()
  {
    NumericMatrix xx(3, 2);
    xx.attr("dimnames") = List::create(
      Rcpp::CharacterVector::create("1", "2", "3"), 
      Rcpp::CharacterVector::create("a", "b"));
    return xx;
  }
')
createNumericMatrixFromNumericVector2()
##   a b
## 1 0 0
## 2 0 0
## 3 0 0
cppFunction('
  NumericMatrix createNumericMatrixFromNumericVector3()
  {
    NumericMatrix xx(3, 2);
    List dimnames = xx.attr("dimnames");
    xx.attr("dimnames") = List::create(
      dimnames[0],
      Rcpp::CharacterVector::create("a", "b"));
    return xx;
  }
')
createNumericMatrixFromNumericVector3()
##      a b
## [1,] 0 0
## [2,] 0 0
## [3,] 0 0
cppFunction('
  NumericMatrix createNumericMatrixFromNumericVector4()
  {
    NumericMatrix xx(3, 2);
    List dimnames = xx.attr("dimnames");
    xx.attr("dimnames") = List::create(
      dimnames[0],
      Rcpp::CharacterVector::create("a", "b"));
    return xx;
  }
')
createNumericMatrixFromNumericVector4()
##      a b
## [1,] 0 0
## [2,] 0 0
## [3,] 0 0

リストについて

まず、RからC++サイドリストを渡す場合、関数の引数としてListクラスを使用する。そして、その要素にアクセスするには[]演算子を用いる。
cppFunction('
  NumericVector namedArgumentL(List x) 
  {
    NumericVector a = x["a"];
    return a;
  }
')
namedArgumentL(list(x=100, y=123, a=1:5))
## [1] 1 2 3 4 5
cppFunction('
  SEXP getFirstElemenOfListFromR(List list){return list[0];}
')
getFirstElemenOfListFromR(list(a=1:10, b=iris))
##  [1]  1  2  3  4  5  6  7  8  9 10

データフレームについて

データフレームの作成

データフレーム(data.frame)を作成するにはDataFrame::create関数を用いる。 以下では7個の正規分布に従う乱数を各列にしたdata.frameを返却している。
cppFunction('
  DataFrame createDataFrame()
  {
    Rcpp::RNGScope scope;
    NumericVector rn = Rcpp::rnorm(7);
    DataFrame df = DataFrame::create(Named("rnorm1")=rn, Named("rnorm2", rn), _["rnorm3"]=rn);
    return df;
  }
')
createDataFrame()
##       rnorm1     rnorm2     rnorm3
## 1 -1.0500452 -1.0500452 -1.0500452
## 2  0.7980083  0.7980083  0.7980083
## 3  0.7675850  0.7675850  0.7675850
## 4 -0.9528517 -0.9528517 -0.9528517
## 5 -0.1034812 -0.1034812 -0.1034812
## 6  0.1509389  0.1509389  0.1509389
## 7  0.5684245  0.5684245  0.5684245
各列の指定は
Named("name", value)
Named("name") = value
_["name"] = value

のどの書き方でもいいけど、最後のがタイプ数的に楽なので、それでいきたい。

データフレームの操作

.push_back関数を使うとデータフレームにデータを追加できるが、

によると、こいつはあまり効率的なもんじゃないので、多用は厳禁。基本はC++での計算結果をそのままDataFrameにして返すだけにしたいところ。遅いなるならC++使う意味ないし、変態以外。
cppFunction('
  DataFrame pushbackDataFrame(DataFrame x)
  {
    DataFrame df1(x);
    DataFrame df2(x);
    for (int i=0;i < df1.length(); ++i)
    {
      df2.push_back(df1(i));
    }
    return df2;
  }
')
pushbackDataFrame(head(iris))
## $Sepal.Length
## [1] 5.1 4.9 4.7 4.6 5.0 5.4
## 
## $Sepal.Width
## [1] 3.5 3.0 3.2 3.1 3.6 3.9
## 
## $Petal.Length
## [1] 1.4 1.4 1.3 1.5 1.4 1.7
## 
## $Petal.Width
## [1] 0.2 0.2 0.2 0.2 0.2 0.4
## 
## $Species
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica
## 
## [[6]]
## [1] 5.1 4.9 4.7 4.6 5.0 5.4
## 
## [[7]]
## [1] 3.5 3.0 3.2 3.1 3.6 3.9
## 
## [[8]]
## [1] 1.4 1.4 1.3 1.5 1.4 1.7
## 
## [[9]]
## [1] 0.2 0.2 0.2 0.2 0.2 0.4
## 
## [[10]]
## [1] setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica

R本体だとrbind/cbindなんて良く使っていたがそれに対応

その他アレコレ

結局R⇔C++間のデータ受け渡しは参照渡なの?値渡なの?

cppFunction('
  void refOrValue(NumericVector x) 
  {
    x[0] = 100;
  }
')
x <- 1:3
refOrValue(x)
x
## [1] 1 2 3

この結果をみると、

  • RからC++に渡したベクトルは値渡しになっているようだ

ということがわかる。たぶん、

Rcppを使用しているその他のパッケージについて

Rcppを使用しているその他のパッケージ、例えば行列計算用のC++ライブラリであるArmadilloを使いたい場合は、そのArmadilloライブラリ自信を明示的にインストールしなくても、単に
install.packages("RcppArmadillo")
して、やればよい。パッケージのインストール先にArmadilloも含まれている。なので、後は通常のRのパッケージ同様
library(RcppArmadillo)
としてやれば、その機能を使用することができて
cppFunction(depends = "RcppArmadillo",includes="#define ARMA_DONT_USE_CXX11", '
  arma::vec exampleArmadillo(arma::vec x) 
  {
    return (x+123);
  }
')
x <- 1:3
exampleArmadillo(x)
##      [,1]
## [1,]  124
## [2,]  125
## [3,]  126

のように、Armadilloを使用したコードを実行することができる。上の関数おいて、

includes="#define ARMA_DONT_USE_CXX11"

はWindowsコンパイラ用の警告消しのおまじないなので、基本なくても良い。

全部のRcpp系ライブラリにおいて、この外部ライブラリは明示的にインストールしなくてもいい形式だとありがたいんだが、どうなっているのだろうか。

内部で使用する関数について

単純に、使う関数から見える位置に書いておけば良さげ。
sourceCpp(code='
  #include   
  using namespace Rcpp;
  NumericVector inner_function(NumericVector x)
  {
    return(x+1);
  }
  //[[Rcpp::export]]
  NumericVector export_function(NumericVector x0)
  {
    return inner_function(x0);
  }
')
export_function(1:10)
##  [1]  2  3  4  5  6  7  8  9 10 11