Rでは規格化された形式でコード本体、テストコード、ドキュメント、データセットなどのその他のリソースをまとめ、メタ情報を付与したパッケージを、モジュール(もしくはadd-on)の配布単位としています。これによって、多種多様なパッケージを巨大アーカイブCRANにまとめあげ1、エンドユーザーが容易にモジュールを入手できるようになりました。皆さんinstall.packageでCRANから様々なパッケージをダウンロードして利用されていると思います。
このパッケージと言う仕組みはCRANにアップロードしなくても、モジュールの共有には有用な仕組みです。組織でのデータ分析では、コードをパッケージ化しているところもあります。決まったディレクトリに決まった種類のファイルを配置して、メタ情報を記したファイルを作ればパッケージになるので、パッケージ作成支援ツールもあることもあり、簡単に作ることができます。
1 パッケージ作成支援ツール
最近はRStudioの機能を使って作ることが一般的になっているようです。毎日作るものでもないですから、GUIの対話型I/Fの方が適していますね。RStudioを使わない場合は、
usethis::create_packageutils::package.skeletonRcpp::Rcpp.package.skeleton
を使う方法が広く紹介されています。これらのツールは、ディレクトリを作って雛形となるファイルを配置してくれるので、メタファイル(i.e. DESCRIPTION,
NAMESPACE)の雛形を書き換え、ファイルを追加し、ドキュメント(i.e. man/*.Rd,
vignettes)を書き、コマンドラインでR CMD builtをして完成になります。
こう書くと大変そうですが、自動生成されたリポジトリにファイルを追加配置して、DESCRIPTION,
NAMESPACE,
man/*.Rdの一部を書き替えたら未完成でも動きます。インハウスで使うのであれば、完璧にする必要はありません。なので、だらっと作っていきましょう。
2 パッケージを作ってみる
試しに.CでC言語の関数を呼ぶ最小公約数を求める関数をパッケージ化してみましょう。RからC言語の関数を呼ぼうのコードを流用します。
2.1 雛形を支援ツールにつくってもらう
現在ではほとんど推奨されていないようですが、package.skeletonを使ってパッケージの雛形をつくります。この関数は、指定したRのオブジェクトもしくはファイル内で定義されているオブジェクトをもとにパッケージの雛形をつくってくれるものです。
まず、example_openmp.cをコンパイルし、example_openmp.Rを実行しましょう。これでRの作業メモリーにオブジェクトがリストされるので、
とすると、gcdパッケージのリポジトリーの雛形がディレクトリ./gcd作成されます。
Dynamic Link
Library(以下、DLL)を呼ぶ部分が含まれませんが、DLLをロードする作業は後述するNAMESPACEファイルに指定しておくと、パッケージをロードしたときに自動的にDLLもロードしてくれます。
2.1.1 パッケージ名の制約
パッケージ名はASCII文字列に限られており、さらに空白や特殊文字の利用やUNIXの特殊ファイル名と被る名前は禁じられています。,",*,:,/,<,>,?,\,|は使用禁止で、\,
[, ( ,), {,
}, [, ], $,
~, \,
]は非推奨です。アルファベットと数字とアンダーバーとハイフンとドットぐらいしか使えないと理解しましょう。
2.2 リポジトリにファイルを追加配置する
./gcdの下にsrcディレクトリを作り、ソースコードexample_openmp.cと、コンパイラ設定ファイルMakevarsを配置します。
.gcd/src/example_openmp.c
.gcd/src/Makevars
Makevarsの中身は、%R_USER%/.R/Makevars.win(もしくは~/.R/Makevars)と同じにします。
CFLAGS = $(SHLIB_OPENMP_CFLAGS)
FCFLAGS = $(SHLIB_OPENMP_FFLAGS)
C用とFortran用の記述しかありませんが、OSのコマンドプロンプトでR CMD configと実行するとヘルプが出てC++やそのバージョンごとのディレクティブも一覧されるので、環境にあわせて設定できます。
2.3 自動生成されたファイルを書き換える
パッケージの内容にあわせて一行、一行、書き替えていくのが正道ですが、不完全でもインストール可能なパッケージをとりあえず作ることを目指します。
2.3.1
DESCRIPTIONを書き換える
主にパッケージの開発者に関する情報を書いておくファイルです。
今回は以下の一行を書き替えたら終わりです。
Description:
「例なんで、どうも!」と書いておきます。
Description: This is an example. THX!
R CMD checkが他をチェックしないのが謎と言うか、将来のバージョンではもっと書き替えないといけなくなるかも知れません。
2.3.1.1
Depends
package.skeletonがつくる雛形には含まれておらず、必須でもないですが、Depends行は最初から有用です。
例えば、
Depends: R (>= 4.2.0), mice
と書いておくと、Rのバージョン4.2以上でないとinstall.packages関数でインストールできず、miceパッケージがインストールされていない場合は、自動でインストールしてくれます。
2.3.2
NAMESPACEを書き換える
パッケージをロードしたユーザーが使える関数、S3クラスのメソッド、S4クラスやそのメソッドを宣言するファイルです。利用するDLLもここで宣言します。
今回は以下の一行を追加します。example_openmpでなくてgcdなのが奇妙に感じられるかも知れませんが、R CMD builtするとgcd.dllが作られるのでこうなります。
useDynLib(gcd)
DLLのロードやアンロードの記述が不要になります。
なおpackage.skeletonにデータフレームを指定すると、NAMESPACEのexport(...)にデータフレームを保存したオブジェクト名を入れてくるのですが、放置しておくとエラーになるのでexport(...)から消します。
2.3.2.1 DLLの関数/サブルーチンを変数名で呼ぶ
useDynLibの第2引数以降に関数名を指定することで、Cの関数やFortranのサブルーチンを変数名で呼ぶことができます。
また、以下のように変数名を指定することもできます。
useDynLib(gcd, C_gcd=gcd)
名前解決が速くなることと、コード中のあちこちに文字列を書く必要がなくなることが御利益です。
2.3.2.2
exportPattern(...)
exportディレクティブで指定された関数が、パッケージがlibraryでロードしたユーザーに提供される関数ですが、exportPattern(...)を使うと正規表現で一括指定できます。
2.3.2.3
importとimportFrom
作成するパッケージが、Rに標準添付されているもの以外の他のパッケージに依存している場合、importディレクティブで指定します。importFormを使えばパッケージ内の特定の関数だけを名前空間にattachできますし、importでexceptを指定すると特定の関数が名前空間にattachするのを避けることができます。
2.3.2.4 S3メソッド
S3method(myfunc, myclass)と書いておくと、作成するパッケージの関数myfunc.myclassをS3クラスmyclassのメソッドとして提供することができます。
S3method(myfunc, myclass, methodentity)と書いておくと、関数methodentityをmyclassのメソッドとして提供でき、関数myfunc.myclassは無くても良いです。
R
3.6以降だと、S3method(packagename::myfunc, myclass)と書くことで、依存するパッケージpackagenameがロードされた後にS3メソッドの登録ができます。
2.3.3
*.Rdを書き換える
言わずと知れたマニュアル・ページです。
今回は、./gcd/manディレクトリの中にあるgcd_c.Rd,
gcd_r.Rd,
そしてgcd-package.Rdを書き替えます。
gcd_c.Rdとgcd_r.Rdは\title{ ... }の中身がないので入れます。gcd_cとgcd_rと関数名を入れておけば動くので、そうしましょう。
gcd-package.Rdは\title{ ... }の中身はあるのですが、\example{ ... }にエラーが出る文字列が入っているので消します。なお、gcd_c.Rdとgcd_r.Rdの\example{ ... }の中身を消しても問題はありません。
2.3.3.1
\example{ ... }内の自動実行
Rdフォーマットはマニュアルに書く情報を列挙するためのものですが、\example{ ... }内はRのコードとして実際に実行されます。実行されたくない部分は\dontrun{ ... }で囲いましょう。また、実行はしたいが表示したくない場合は\dontshow{ ... }で囲いましょう。
2.4
Read-and-delete-me
消さなくても動きます。消すべきでしょうが。
3 Cソースコードの修正
Cで書かれたDLLの方で関数の型チェックを行なっている場合、DLLのファイル名が変わることからCのソースコードを修正する必要があります。
例で使ったexample_openmp.cの場合は、void R_init_example_openmp(DllInfo *info)をvoid R_init_gcd(DllInfo *info)にしました。Rのラッピング関数で型変換を行なうので、不正な型が入ることは無く、顕在化しない問題になりそうですが。
3.1
useDynLib(..., .registration = TRUE)
DLLに登録情報がある場合、一括で、Rのコード中で.C("gcd", ... )と文字列で名前を指定して呼び出しているのを、.C(C_gcd, ... )と変数名で呼び出すようにできます。
DLLの準備が出来ていれば、パッケージのNAMESPACEで、useDynLib(gcd)を以下のようにするだけです。
useDynLib(gcd, .registration = TRUE, .fixes = "C_")
.fixesはRで使う変数名につけるプリフィックスの指定で、混乱が無ければ無くても良いです。
CRANに登録されているパッケージの多くはこの方法を用いています。
4 パッケージのインストール
R CMD INSTALL
./gcdとする他にも、以下のようにinstall.packagesにrepos = NULLを引数につけて、ファイルパスを指定する方法もあります。
なお、同様にURLも指定できます。
4.1 パッケージのインストール先
system.files関数で取れ、例えばstatsパッケージの場所を特定する場合は、
[1] "/usr/lib/R/library/stats"
とします。
statsパッケージの中のtests/glm.Rの場所を特定する場合は、
[1] ""
とします。
パッケージ内のコードからinst/以下に含めたファイルを参照する場合、アンインストールする場合などに使えます。
4.2 パッケージのアンインストール
インストールしたパッケージを削除するときは、インストール先ディレクトリを確認したあと、Rを終了してパッケージ名のディレクトリ(i.e. gcd)を消します。
5 パッケージを膨らませていく
DESCRIPTIONとNAMESPACEとmanに関しては雛形に書き込まれたものを含めて説明と例が色々あるので、dataとvignettesとtestsの追加について説明します。
5.1
dataの追加
dataは、data関数で作業メモリーにロードできるデータフレームなどです。リポジトリにdata/ディレクトリを作成した後、Rのsave関数でdata/以下に.rdaファイルの形式でデータフレームを保存すれば終わりです。
例えば、
dataset_of_gcd <- data.frame(a=as.integer(round(runif(100, min=0.5, max=100.5))),
b=as.integer(round(runif(100, min=0.5, max=100.5))))
dir.create("./gcd/data")
save(dataset_of_gcd, file="./gcd/data/example.rda")こんな感じでexample.rdaを作ってからR CMD INSTALLをすると、
パッケージに添付したデータセットがロードされていることが分かります。
[1] "dataset_of_gcd"
manの追加は必須ではないですが、./gcd/man/example.Rdは作成した方がよいと思います。
5.2
vignettesの追加
Rでvignette("zoo")のようにすると出てくるpdfやhtmlの説明ページのことです。manはリポジトリに含まれる個々の関数などのオブジェクトの説明、vignetteはリポジトリ全体のオブジェクトをどう組み合わせて使っていくかの説明と言う風に使い分けが想定されていたのではないかと思います。
実際には、広く使われているパッケージでもvignetteは用意されていないことがあり、またパッケージの使い方と言うよりもより詳しい情報源の紹介や、パッケージで用いている計量理論の何十ページもある説明のように、記述される内容も様々です。
R
2.xまではvignetteはLaTeX用プリプロセッサSweaveを使って作ったpdfのみが利用できました。SweaveにLaTeXの中にRのコードを埋め込んだ形式のファイル2を入力すると、Rの実行結果をLaTeXファイルに入れることができます。また自動でtexをpdfに変換してくれます。3.0からエンジンが柔軟になり、今ではR
Markdownを使ってhtmlで書く方法が広く紹介されています。
5.2.1
Sweaveの利用
R
Markdownを使う方が楽なのですが、Sweaveを使ってvignetteを書きます。
まず、TeXがインストールされていてコマンドラインで実行できることを確認します。
次に、./gcd/vignettesと./gcd/inst/docの2つのディレクトリを作成します。次に、Rで./gcd/vignettes/gcd.Rnwをパッケージからコピーしてつくります。
file.copy(system.file("Sweave", "example-1.Rnw", package = "utils"),
file.path("gcd", "vignettes", "gcd.Rnw"))コピーしたファイルを、編集しなくてもそのまま使えますが、編集します。続いてRの作業ディレクトリを、./gcd/vignettesに移動して
R CMD Sweave --pdf gcd.Rnw
とすると、gcd.pdfができます。これを./gcd/inst/docにコピーします。
パッケージbuild時に自動でpdfをつくってコピーしてくれても良さそうなのですが、してくれません。また、./gcd/vignettesの.Rnwファイルに対応するように、./gcd/inst/docに.pdfファイルがないと警告が出て、vignetteとして取り扱ってくれません。
.Rnwファイルの書き方については、vignette("Sweave")で詳細な説明を読むことができます。
なお、DESCRIPTIONでSweaveの利用を明記するときは、VignetteBuilder: utilsと書きます。
5.2.2
instの中は再帰的に丸ごとコピー
inst/docにpdf以外を置いておいても、丸ごとコピーします。ディレクトリinstの中は再帰的に丸ごとコピーする仕様だからです。コピーしたくないファイルは、.Rinstignore(e.g. ./gcd/.Rinstignore)にPerl互換正規表現で指定することができます。
5.3
testsの追加
まず、ディレクトリ./gcd/testsを作成します。ここにユーザー入力無く終了する任意のRスクリプトをおきます。スクリプトが、エラーを出さなければ成功、エラーを出したら失敗です。今回は以下の内容のexample.Rを作成しておきましょう。
library(gcd)
# gcd_cとgcd_rの計算結果が異なればエラーになる
test.gcd <- function(n=1e+5){
v <- as.integer(round(runif(n, min=0.5, max=n-0.5)))
w <- as.integer(round(runif(n, min=0.5, max=n-0.5)))
et_c <- system.time({
r_c <- gcd_c(v, w)
})
et_r <- system.time({
r_r <- gcd_r(v, w)
})
et <- matrix(c(et_r, et_c), 2, length(et_r),
byrow=TRUE,
dimnames=list(c("R", "C"), names(et_r)))
list(flag=all(r_c==r_r), et=et)
}
test.gcd()これでR CMD check中に以下のような表示が出ます。
* checking tests ...
Running 'example.R'
OK
6 説明しなかったディレクトリ
Writing R Extensionsにはしっかり言及があるのですが、Rのパッケージ作成紹介では触れられない残りのディレクトリを紹介しておきます。ただし、CRANに登録されているパッケージでも利用頻度は低いようです。
| ディレクトリ | 説明 |
|---|---|
demo |
demo関数で動かせるデモRスクリプト用 |
exec |
実行可能フラグのついたR以外のスクリプト用3 |
po |
国際化/i18n/gettext用のデータ用 |
tools |
autoconfのM4のようなビルドに使う補助ファイル用 |
7 まとめ
原始的な方法でパッケージの作成をしてみました。DESCRIPTIONとNAMESPACEとSweaveと.Rdがそれぞれ別の見慣れない書式になっているので、細部は説明を見ながらと言うことになりますが、作業自体は大したことは無かったです。公式ドキュメントのWriting
R
Extensionsを含めてSweaveでvignetteを作る方法が、ちょっと薄くて苦労しましたが。最近はRStudioで雛形を出してR
Markdownでvignetteを書くのが一般的で、知らなくてよい情報なのかも知れません。
