このドキュメントは俺の俺による俺のためのRcppドキュメントである。出来るところは積極的にc++11/0xでいきたい。Bye C++ 03!!!そして、Rcppの機能も出来るだけ新しめの奴ばかりを使っていきたい。だって進化速いからね?またこのドキュメントは随時更新予定であり、
からオリジナルのR Markdownファイルが入手可能だ。
install.packages("Rcpp")
Windowsの環境においてはC++用コンパイラ(GCC)やUNIXコマンドのセットが入っている
も突っ込んでおく必要がある。これは上記のサイトからexeを落っことしてデフォルトの設定でよいので、インストールを実行する。そして、実際に使うためには、R上でRcppライブラリのロードが必要となる。library(Rcpp)
Sys.setenv("PKG_CXXFLAGS"="-std=c++0x")
また、package開発の場合、ソースコード(cpp)ファイルの何処かに
// [[Rcpp::plugins(cpp11)]]
という記述を施しておけば、上記の設定は不要。
1から始まる一方、C++内インデックスは0から始まる点に注意evalCpp関数でワンライナーなC++のコード評価ができる。なので、お試し評価みたいな話であればこれでよい。例えば
evalCpp("2 + 3")## [1] 5
な感じ。その他にもRcppでは
cppFunction : C++で書かれたR内で使える関数を作成するsourceCpp : ファイルや文字列(C++)のコードをコンパイルして実行してくれるが用意されているが、ここではこの1ファイル(R markdownファイル)で全てを完結させたいのでsourceCppは使わないで、もっぱらcppFunctionを使っていく。このやり方が一番いいのかは不明だ。
NumericVectorはその名の通り、ベクトルを操作する型で、基本C++のstdに含まれるvectorであるstd::vectorと似たようなもんだ。NumericVectorの初期化法として、一律な値での初期化が可能。
#5個の要素すべてが3.33であるベクトルを作成
evalCpp("NumericVector(5, 3.0)")## [1] 3 3 3 3 3
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
#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
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
cppFunction('
DataFrame createDataFrameFromMatrix()
{
NumericMatrix x(4, 5);
return DataFrame::create(_["X"]=x(_,1));
}
')
createDataFrameFromMatrix()## X
## 1 0
## 2 0
## 3 0
## 4 0
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
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
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なんて良く使っていたがそれに対応
cppFunction('
void refOrValue(NumericVector x)
{
x[0] = 100;
}
')
x <- 1:3
refOrValue(x)
x## [1] 1 2 3
この結果をみると、
ということがわかる。たぶん、
install.packages("RcppArmadillo")
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