R

トップページ
Google
WWWを検索
サイト内を検索

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にデータフレームを指定すると、NAMESPACEexport(...)にデータフレームを保存したオブジェクト名を入れてくるのですが、放置しておくとエラーになるので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 importimportFrom

作成するパッケージが、Rに標準添付されているもの以外の他のパッケージに依存している場合、importディレクティブで指定します。importFormを使えばパッケージ内の特定の関数だけを名前空間にattachできますし、importexceptを指定すると特定の関数が名前空間にattachするのを避けることができます。

2.3.2.4 S3メソッド

S3method(myfunc, myclass)と書いておくと、作成するパッケージの関数myfunc.myclassをS3クラスmyclassのメソッドとして提供することができます。 S3method(myfunc, myclass, methodentity)と書いておくと、関数methodentitymyclassのメソッドとして提供でき、関数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.Rdgcd_r.Rd\title{ ... }の中身がないので入れます。gcd_cgcd_rと関数名を入れておけば動くので、そうしましょう。

gcd-package.Rd\title{ ... }の中身はあるのですが、\example{ ... }にエラーが出る文字列が入っているので消します。なお、gcd_c.Rdgcd_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.packagesrepos = 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-42~1.2PA/library/stats"

とします。

パッケージ内のコードからinst/以下に含めたファイルを参照する場合、アンインストールする場合などに使えます。

4.2 パッケージのアンインストール

インストールしたパッケージを削除するときは、インストール先ディレクトリを確認したあと、Rを終了してパッケージ名のディレクトリ(i.e. gcd)を消します。

5 パッケージを膨らませていく

DESCRIPTIONNAMESPACEmanに関しては雛形に書き込まれたものを含めて説明と例が色々あるので、datavignettestestsの追加について説明します。

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をすると、

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")で詳細な説明を読むことができます。

なお、DESCRIPTIONSweaveの利用を明記するときは、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 まとめ

原始的な方法でパッケージの作成をしてみました。DESCRIPTIONNAMESPACESweave.Rdがそれぞれ別の見慣れない書式になっているので、細部は説明を見ながらと言うことになりますが、作業自体は大したことは無かったです。公式ドキュメントのWriting R Extensionsを含めてSweavevignetteを作る方法が、ちょっと薄くて苦労しましたが。最近はRStudioで雛形を出してR Markdownでvignetteを書くのが一般的で、知らなくてよい情報なのかも知れません。


  1. CRAN(1997年)に先行するPerlのCPAN(1995年)、CPANとCRANに続く、PythonのPyPI(2003年),Rubyのgem(2004年)などがあります。↩︎

  2. 後でつくる雛形を見たらすぐ分かりますが、<<>>=@の行の間がRのコードとして解釈されるコードチャンクになります。<<eval=FALSE>>とオプションをつけたりでき、R Markdownの原型感があります。↩︎

  3. R以外のスクリプトを用意してもインストール先に実行環境があるとは限らないので、使わないようにしましょう。↩︎