Rcppで全角英数を半角英数に変換するパッケージをつくった
地価のデータとGISのデータを合わせようとしたら数字が半角だったり全角だったりして、
アイエエエ! ナンデ!? 全角ナンデ!?
平成25年地価公示価格(東京都分)
位置参照情報ダウンロードサービス
ってなって、
つくりました。
インストール
library(devtools) install_github("yutannihilation/halfwidthr")
使い方
こんな感じです。地味!
> halfwidthen(c("123", "東京都千代田区千代田1番1号")) [1] "123" "東京都千代田区千代田1番1号"
だがしかし…
などなど、反省点が多数。。C++スキルゼロの私ごときが、文字コードの闇を垣間見ながらやるにはきついやつでした。
速度とか必要ないので、分かりやすさが一番です。Rcppとか使わず、ふつうに↓のやり方にすればよかった。。
Rで全角数字を半角数字に書き換える - StatsBeginner: 初学者の統計学習ノート
まあでもせっかくなのでRcpp触って苦しんだところをメモしておきます。
std::vector<std::string> と Rcpp::CharacterVector の違い
CharacterVector
はRcppのクラスです。文字列のベクターで、std::vector<std::string>
に相当するものです。
文字列のスカラにはString
というクラスが用意されています。std::string
に相当するものです。
NA の扱い
基本的にはどちらもベクターとしての性質を持ちますが、std::string
だとR固有の値を扱えません。たとえば、NA
をstd::string
に変換すると、"NA"
という文字列になってしまいます。
> library(Rcpp) > cppFunction("std::vector<std::string> stdvec(std::vector<std::string> str) { + return str; + }") > cppFunction("CharacterVector chrvec(CharacterVector str) { + return str; + }") > x <- c("a", NA) > chrvec(x) [1] "a" NA > stdvec(x) [1] "a" "NA"
ループの時にNA
だけ飛ばしたいときはis_na()
という関数が各クラスに用意されているので、
for (int i = 0; i < str.size(); ++i) { if( CharacterVector::is_na(str[i]) ){ // do something... } }
のようにします。NA
の扱いについてはHadleyさんのこちらの記事が参考になりました。
変換
std::string
に変換するにはRcpp::as<T>()
という関数を使います。こんな感じ。
as< std::vector<std::string> >(str)
ただ、上述のように変換してしまうとNA
がうまく取り扱えません。
NA
以外の時だけ処理をしたい場合は、以下のようにします。
for( int i = 0; i < str.size(); i++){ if( CharacterVector::is_na(str[i]) ) continue; std::string std_str = as<std::string>(str[i]); // do something... }
roxygen2 との組み合わせ
cppファイルでもroxygen2でドキュメントが書けます。
↓こちらの@teramonagiさんの例が参考になりました。教えていただいてありがとうございました!
rOpenWeatherMap/example.cpp at master · teramonagi/rOpenWeatherMap · GitHub
Rファイルの時は行頭に#'
と書いていましたが、cppファイルの場合は//'
と書きます。
こんな感じ。
//' @useDynLib packagename //' Do something //' //' @param num int //' @description This function does something with a given int. //' @export // [[Rcpp::export]] void func(int num) { // do something...
@export と Rcpp::export
ここで@export
だけでいいのでは?と思ってたんですが、[[Rcpp::export]]
も必要らしいです。その理由はこちら。
If you’re familiar with roxygen2, you might wonder how this relates to
@export
.Rcpp::export
controls whether a function is exported from C++ to R;@export
controls whether a function is exported from a package and made available to the user. (adv-r.had.co.nz/Rcpp.html)
@export
は関数をパッケージにエクスポートするかを、[[Rcpp::export]]
は関数をC++からRにエクスポートするかどうかを、それぞれコントロールしているらしいです。
@useDynLib
@useDynLib
はR以外のライブラリを使うときに指定するアノテーションです。これはcppファイルのどれかに書いておけば大丈夫みたいですが、ヘッダファイルだとだめでした。そらそうか。
C++11
RcppでもC++11を使えます。
First steps in using C++11 with RcppによるとRcpp 0.10.3以降なら以下のように書くだけでいけるはずです。
// [[Rcpp::plugins(cpp11)]]
が、なぜか動きません。。sourceCpp()
で個別にファイルをコンパイルした時は利いてるんですが、RStudioでビルドしたときとかdevtools::install_github
したときは有効にならないみたいです。
たぶん私が分かっていないだけなので、詳しい方教えてください。。
回避策(?)
githubを見回った感じ、Makevarsというファイルで環境変数を設定すると大丈夫らしいです。ので、とりあえずそれを踏襲することにしました。
halfwidthr/Makevars at master · yutannihilation/halfwidthr · GitHub
もうちょっと調べたかったところ
テスト
テストはtestthat
で書いたのでRにエクスポートした関数のみしかテストできません。
エクスポートしないC++の関数も、C++側でユニットテストできる気がするんですが調べるのめんどくさくて諦めました。
@useDynLib を書かない方法
dplyrのコードを検索すると、自動生成された一行しかありません。たぶん明示的に書かなくてもいい方法あるんだろうなーと思いましたが、これも調べるのめんどくさくて諦めました。
まとめ
Rcppはたしかに可能性を感じます。が、C++ド素人がいきなりやるにはつらいなーという感じでした。当たり前ながら。 まあでも便利そう。徐々に慣れていければいいなという感じでした。