Rcppを試してみる

この前紹介したGoogle Summer of Codeのやつですが、メンターがDirk Eddelbuettelさんで、この人はRcppというパッケージを作ってる人で僕はこのパッケージを使ったことがなかったので、使ってみようじゃないかというお話。以下を参考にしつつ。

対象はたぶんC拡張書いたことある人or書いてみたい人。

RのC拡張はめんどい&Rcppを使うと楽ができる

RのC拡張(Writing R Extentionを印刷して枕の横に置きましょう)を書くときの典型例として、例えばこんな感じのコードがよくある。

SEXP ab;
PROTECT(ab = allocVector(REALSXP, 2));
REAL(ab)[0] = 123.45;
REAL(ab)[1] = 67.89;
UNPROTECT(1);

Rさんが勝手にメモリ解放しないようにPROTECTしないといけないけど、それを忘れがちだったり、PROTECTした数を数えてUNPROTECTしないといけなかったりで、なかなか面倒。Rcppを使うと以下のように簡潔に書ける。

Rcpp::NumericVector ab(2);
ab[0] = 123.45;
ab[1] = 67.89;

他の型でも同様に簡単に書ける。Rcppを使わないでcharacter型のvectorを作ろうとすると

SEXP ab; 
PROTECT(ab = allocVector(STRSXP, 2)); 
SET_STRING_ELT( ab, 0, mkChar("foo") ); 
SET_STRING_ELT( ab, 1, mkChar("bar") ); 
UNPROTECT(1); 

だが、Rcppを使うと

CharacterVector ab(2) ; 
ab[0] = "foo" ; 
ab[1] = "bar" ; 

という感じで簡単に書ける。

C++からRへの変換 => wrap関数

まず、変換のルールから確認しよう。

C++での型 Rでの型
プリミティブな型(intとかdouble) 対応するRのvector
std::string character型のvector
STLのコンテナ。std::vectorやstd::listなど Tがラップ可能なら、対応するもののlist
std::map Tがラップ可能ならlistのnameにmapのキーの名前が入ったRのリストになる

例として以下のようなC++のデータ構造は

std::vector< std::map<std::string,int> > v; 
std::map< std::string, int > m1; 
std::map< std::string, int > m2; 
m1["foo"] = 1; m1["bar"] = 2; 
m2["foo"] = 1; m2["bar"] = 2; m2["baz"] = 3; 
v.push_back( m1); 
v.push_back( m2); 
Rcpp::wrap( v );

Rだと

list( c( bar = 2L, foo = 1L), c( bar = 2L, baz = 3L, foo = 1L) )

のように変換される。

RからC++への変換 => as関数

Rcpp::Environment global = Rcpp::Environment::global_env();
std::vector<double> vx = global["x"]; 
std::map<std::string,std::string> map;
map["foo"] = "oof"; 
map["bar"] = "rab";
global["y"] = map ; 

参考