Rでは規格化された形式でコード本体、テストコード、ドキュメント、データセットなどのその他のリソースをまとめ、メタ情報を付与したパッケージを、モジュール(もしくはadd-on)の配布単位としています。これによって、多種多様なパッケージを巨大アーカイブCRANにまとめあげ1、エンドユーザーが容易にモジュールを入手できるようになりました。皆さんinstall.package
でCRANから様々なパッケージをダウンロードして利用されていると思います。
このパッケージと言う仕組みはCRANにアップロードしなくても、モジュールの共有には有用な仕組みです。組織でのデータ分析では、コードをパッケージ化しているところもあります。決まったディレクトリに決まった種類のファイルを配置して、メタ情報を記したファイルを作ればパッケージになるので、パッケージ作成支援ツールもあることもあり、簡単に作ることができます。
1 パッケージ作成支援ツール
最近はRStudioの機能を使って作ることが一般的になっているようです。毎日作るものでもないですから、GUIの対話型I/Fの方が適していますね。RStudioを使わない場合は、
usethis::create_package
utils::package.skeleton
Rcpp::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の作業メモリーにオブジェクトがリストされるので、
package.skeleton("gcd", c("gcd_c", "gcd_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
消さなくても動きます。消すべきでしょうが。
2.5 ビルド、チェック、インストール
まずビルドします。
R CMD build ./gcd
これでgcd_1.0.tar.gz
と言うファイルができ配布できますが、配布する前にインストール可能かチェックして、開発環境でインストールしてみましょう。
まずはチェックです。
R CMD check ./gcd
エラーが出れば、メッセージにしたがって修正します。
エラーが無ければ、もしくは、エラーが無くなれば、インストールします。
R CMD INSTALL ./gcd
これでRで、
library(gcd)
gcd_c(1:10, 2*(1:10))
などとできるようになりました。
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
を引数につけて、ファイルパスを指定する方法もあります。
install.packages("./gcd_1.0.tar.gz", repos = NULL)
なお、同様にURLも指定できます。
4.1 パッケージのインストール先
system.files
関数で取れ、例えばstats
パッケージの場所を特定する場合は、
system.file(package = "stats")
[1] "C:/PROGRA~1/R/R-44~1.1/library/stats"
とします。
stats
パッケージの中のtests/glm.R
の場所を特定する場合は、
system.file("tests", "glm.R", package = "stats")
[1] "C:/PROGRA~1/R/R-44~1.1/library/stats/tests/glm.R"
とします。
パッケージ内のコードからinst/
以下に含めたファイルを参照する場合、アンインストールする場合などに使えます。
4.2 パッケージのアンインストール
インストールしたパッケージを削除するときは、インストール先ディレクトリを確認したあと、Rを終了してパッケージ名のディレクトリ(i.e. gcd
)を消します。
5 パッケージを膨らませていく
DESCRIPTION
とNAMESPACE
とman
に関しては雛形に書き込まれたものを含めて説明と例が色々あるので、data
とvignettes
とtests
の追加について説明します。
5.1
data
の追加
data
は、data
関数で作業メモリーにロードできるデータフレームなどです。リポジトリにdata/
ディレクトリを作成した後、Rのsave
関数でdata/
以下に.rda
ファイルの形式でデータフレームを保存すれば終わりです。
例えば、
<- data.frame(a=as.integer(round(runif(100, min=0.5, max=100.5))),
dataset_of_gcd 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
をすると、
library(gcd)
data(example)
ls()
パッケージに添付したデータセットがロードされていることが分かります。
[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の計算結果が異なればエラーになる
<- function(n=1e+5){
test.gcd <- as.integer(round(runif(n, min=0.5, max=n-0.5)))
v <- as.integer(round(runif(n, min=0.5, max=n-0.5)))
w
<- system.time({
et_c <- gcd_c(v, w)
r_c
})
<- system.time({
et_r <- gcd_r(v, w)
r_r
})
<- matrix(c(et_r, et_c), 2, length(et_r),
et 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
を書くのが一般的で、知らなくてよい情報なのかも知れません。