devtools — Rパッケージ作成支援
自分で書いた関数が多くなってきたら、まとめてパッケージを作るとよい。 少しだけ面倒だが、以下のようなメリットがある。
- 普通に変数や関数を定義するとワークスペースが名前でいっぱいになってしまうが、
パッケージ内で定義されている変数や関数は
ls()で出てこないのでスッキリ - 既存のオブジェクトと名前が衝突するような場合でも、
mypackage::func1のように名前空間を明示的に指定して呼び出せる
CRANに上げる程ではないとしても、 GitHubに公開しておけば誰でも使えるようになるので、 共同研究者と解析環境を共有したり、 ひとつの論文のワークフローを置いておいたり、いろいろ使い道はある。
Rパッケージ
最低限の作成手順
- 開発支援パッケージをインストールして読み込む:
usethis の関数も読み込まれて利用可能になる。
install.packages("devtools") library(devtools) - パッケージの骨組みを作る:
予め作った
create_package("hello") getwd() # 自動的に移動していることを確認hello/の中でcreate_package(".")しても同じ。 - 一般的な初期設定して、様子を見てみる:
use_mit_license() use_package_doc() check() - ローカルgitリポジトリを作って最初のコミットをする
最初のコミットを空っぽにしたい場合は予め手動でやっておく:
use_git()git init git commit --allow-empty -m "empty commit" - GitHubに同名のリポジトリを作ってプッシュ:
リポジトリの名前をパッケージ名と違うものにしたい場合などは手動で。
use_github() - とりあえず誰でもインストール可能なパッケージができたはず:
install_github("heavywatal/rhello")
ソース構造
https://r-pkgs.org/structure.html
DESCRIPTION # 一番大事
NAMESPACE # 見せるオブジェクトを列挙
README.md # 全体の説明を簡単に
R/ # Rソースコード
data/ # サンプルデータなど
inst/ # CITATION
man/ # マニュアル.Rd
src/ # C++ソースコード
tests/
vignettes/
CRANから落としてくる .tar.gz ソースコード (bundle package) とは違う。
DESCRIPTION
https://r-pkgs.org/description.html
- どうでも良さそうなファイル名とは裏腹に、ちゃんと書かないと動かない。
- 始めはusethisとかに生成してもらい、他のパッケージを参考にしつつ修正していく。
書き換えたら
use_tidy_description()で整える。 Importsに列挙したものは依存パッケージとして一緒にインストールされる。NAMESPACEにおけるimport()とは意味が異なり、library()時に読み込まれるわけではない。DependsはR (>= 4.4.0)としてRの下限を指定する程度にしか使わない。Importsのように依存パッケージを列挙することもできるが、 そうするとlibrary()時にそいつらもattachされてしまう。 それが有用なのは既存パッケージを拡張する場合 (e.g.,stars→sf) や、 パッケージ機能を分割した場合 (e.g.,devtools→usethis) だけ。Suggestsにはオプショナルな機能や開発時に必要なパッケージを列挙する。 ユーザーの環境にインストールされている保証は無いので、requireNamespace()などで確認してから使う。Titleはピリオドを含まない一文でタイトルケース。Descriptionはピリオドを含む一段落。- ライセンスを別ファイルにする場合は
License: file LICENSEと書く Authors@Rのとこは後でRで評価されるので変な形。
NAMESPACE
https://r-pkgs.org/namespace.html
- 後述のroxygen2がソースコードから自動生成するので直接触らない。
- ここで
export()された関数だけがユーザーから見える。 - 外部パッケージから
importFrom(package, function)された関数はattachされてパッケージ内で利用可能になる。import(package)で全ての関数を一括処理できるけど名前の衝突が怖いので避ける。
R/ ソースコード
NAMESPACEやman/*.Rdを自動生成してもらえるように 後述のroxygen形式でコメントを書く- ファイルの数や名前は何でもいいので、開発者が分かりやすいようにしとく。
例えば、似たような関数をひとつのファイルにまとめ、
同名の
@rdnameを指定してman/と同じ構造にするとか。 library()やrequire()を書かない。 必要なパッケージはDESCRIPTIONのImportsに書き、名前空間::関数()のようにして使う。 どうしても名前空間を省略したいものだけ@importFromを指定する。- パッケージ読み込み時などに実行される特殊関数
.onLoad(...),.onAttach(...),.onUnload(...)は慣習的にzzz.Rというファイルに記述する。
src/ C++ソースコード
vignettes/
https://r-pkgs.org/vignettes.html
個々の関数の使用例はRソースファイルの @examples に書くとして、
複数の関数を組み合わせてどう使うかを説明するのがvignettes/の役割。
R Markdown形式で書いてHTMLやPDFで表示できるので表現力豊か。
use_vignette("hello") で雛形を作ってもらうのが楽。
pandoc と pandoc-citeproc が必要なので
Homebrew とかでインストールしておく。
check()がデフォルトでvignette=TRUEかつ処理がやや重いので、
毎回その重さを受け入れるか、わざわざFALSE指定するかというのは悩みどころ。
後述のpkgdownでウェブサイトを構築すると、 ここに置いてある文書は Articles という位置づけで出力される。
ただしパッケージと同名で vignettes/<package-name>.Rmd というファイルを作ると、
Articles一覧の中ではなくReferenceの隣に “Get started” としてリンクされる。
ここでパッケージの使い方を説明することが期待されている。
index.md/README.md から生成されるホームページも似たような役割じゃないかと思うが、
そちらはパッケージを使うかどうか判断するための情報を提供するところとして書き、
「実際に使いたくなったら “Get started” を見よ」
というリンクを末尾に置いておくのが良さそう。
pkgdown#2372
tests/
testthatパッケージを使うのがデファクトスタンダード。
use_testthat() で初期設定して
use_test("somefunction") のようにテストファイルを追加する。
tests/testthat/ 以下のファイル構成は R/ と同じにしておくとわかりやすい。
さらにcovrパッケージを使って、 ソースコードのうちどれくらいがテストでカバーされてるかを可視化すると良い。
data/
use_data_raw() で data-raw/<dataset>.R をセットアップし、
その中で use_data() を呼んで
data/<dataset>.rda や R/sysdata.rda に配置する。
data/- R から
save(),load()で読むようなバイナリ形式のファイルを置く。 1オブジェクト1ファイルで同名にして.rda,.RData拡張子を付ける。 - 勝手にexportされるので
R/内のソースにroxygenドキュメントを書く。 R/sysdata.rda- ユーザーに公開せずパッケージ内部で使うためのデータ。
data-raw/data/のファイルを作るためのソースコードやテキストデータを置いておく。- bundled package には入れない
(ように
use_data_raw()が.Rbuildignoreを設定する)。 inst/extdata/- データ読み書きを例示するためのデータファイルを置いておく。
system.file("extdata", "mtcars.csv", package = "readr")のようにアクセスできる。
その他
demo/- vignettesに取って代わられた古い機能。
ソースコード
*.Rを置いておくとdemo()関数で呼び出せるというだけ。 check()でソースコードの中身は実行されないが、demo/00Indexというファイルとの整合性が取れてないと警告される。 「デモの名前 + スペース3つ以上かタブ + 適当な説明」という形式。 ファイル名から拡張子を取り除いたものがデモの名前になる。mydemo1 Description of demo 1 mydemo2 Description of demo 2exec/- 実行可能スクリプトの置き場所。
インストール先はそのままパッケージ内の
exec/(つまり最初からパスが通ってるような場所ではない)。 パーミッションはインストール時に設定してもらえるのでソースツリーでは644でOK。Rscriptをshebangで指定することも可能。 例えばRStudioを使いこなせないターミナル勢としてはknitr::knit()するだけのコマンド とか作っておくと便利。 inst/- ここに入ってるものはインストール先でトップディレクトリに移される謎仕様。
- 論文で引用されることを想定している場合は
inst/CITATIONを作る。citation("ggplot2")のように参照できるようになる。 tools/- たぶん何を入れても一切チェックされずインストールもされない自由なディレクトリ。
tests/やvignettes/に入れる前のガラクタコードを一時的に置いとくとか。 .Rbuildignore- Rパッケージらしからぬ変なファイルやディレクトリがあると怒られるので、 そういうやつをこのファイルに列挙して無視してもらう。 ほとんど手で書くことはなく、usethisを使っているうちに膨らんでいく。
README.md- GitHubでいい感じに見えるようにMarkdown形式でパッケージの概要を書く。
Rコードを含む
README.Rmdからbuild_readme()で生成することも可能。 - pkgdownでもホームページの材料として使われるが、
index.mdを置けばそちらが優先される。 開発者向けと一般向けを使い分けるとか。index.mdをルートに置きたくない場合はpkgdown/index.mdでもいい。
devtools
骨組みを作るとこからCRANにデプロイするとこまでお世話してくれる。 いろんな専門パッケージの集合体。
document(pkg = ".", roclets = NULL, quiet = FALSE)roxygen2を呼び出してソースコードからNAMESPACEやman/*.Rdを自動生成する。check(pkg = ".", document = NA, ...)- パッケージとしての整合性を確認。
ついでに
document()は実行できるけどspell_check()はできないので手動で。 test(pkg = ".", filter = NULL, ...)testthatを呼び出してtests/以下のテストコードを実行するbuild(pkg = ".", path = NULL, ...)R CMD installでインストール可能な tar ball (bundle package) を作る。src/のコードをコンパイルするという意味ではない。install(pkg = ".", reload = TRUE, quick = FALSE, build = !quick, ...)- ローカルにあるソースからインストール。
build = TRUEのとき(デフォルト)、わざわざ bundle package をtempdir()に作ってからそいつでインストールする。 install_github(repo, ref = "HEAD", subdir = NULL, ...)- GitHubリポジトリからインストール。
unload(pkg = ".", quiet = FALSE)detach("package:XXX")とかunloadNamespace(XXX)よりもちゃんとまっさらにパッケージを外す。load_all(pkg = ".", reset = TRUE, recompile = FALSE, export_all = TRUE, ...)install()せずファイルから直接library()する。 ロード済みでもまずunload()が呼ばれるので安心。load_all状態のパッケージに対してsystem.file()を呼び出すと、pkgload::system.file()が間に割り込み、ソースのトップとinst/を起点にして探索してくれる。configureで生成するファイルを見つけてもらうには$R_PACKAGE_DIRに直接送り込まず一旦inst/などに置く必要がある。clean_dll(pkg = ".")src/以下に生成される.o,.soを消す。 普段は触る必要ないが、たまにこれが必要な不具合に出くわす。
roxygen2
RソースコードのコメントからNAMESPACEとマニュアル(man/*.Rd)を自動生成する。
- https://cran.r-project.org/web/packages/roxygen2/
- https://github.com/klutometis/roxygen
- https://r-pkgs.org/man.html
- https://kbroman.org/pkg_primer/pages/docs.html
- https://cran.r-project.org/web/packages/roxygen2/vignettes/rd.html
roxygen2::roxygenise(package.dir=".", ..., clean=FALSE)
を直接呼んでもよいが、
基本的には devtools::document() を使って間接的に利用する。
使い方
#' Title of the simple function to add 1 (without explicit @@title tag)
#'
#' The second paragraph is recognized as the description.
#' Explicit @@description is unnecessary unless you want to include
#' an empty line to express multiple paragraphs or bullet lists.
#'
#' The third and subsequent paragraphs are recognized as the details.
#' @param x A numeric vector.
#' @returns A numeric vector with 1 added to each element.
#' @export
#' @examples
#' increment(42)
increment = function(x) {x + 1}
#'から始まる行がroxygenコメントとして扱われる。- タグは
@で始まる。@そのものを入力したいときは重ねて@@とする。 - 1行目にタイトル。1行あけて2段落目に説明文を書く。
明示的な
@title,@description,@detailsタグは大概不要。 箇条書きや複数段落を表現したい場合にだけ適宜使う。 タイトルをコピペして全部同じにすると怒られる。 2段落目を省略するとタイトルが流用される。 - 空行だけでは切れ目として扱われないので
NULLなどを置いたりする。 - Rd形式の代わりにMarkdown形式で記述できる。
create_package()がデフォルトでRoxygen: list(markdown = TRUE)をDESCRIPTIONに書いてくれる。 その全体設定をせず使いたいブロックにいちいち@mdを書く個別設定も可能。```{r }でコードチャンクを作ったり、`r `でインラインRコードを評価したりもできる。 "_PACKAGE"という文字列の上に書かれたブロックは、 パッケージそのものに対応するマニュアル*-package.Rdになる。@useDynLibなど全体に関わる設定はここでやるのが良いのでは。#' Example package to say hello #' @useDynLib hello, .registration = TRUE #' @importFrom rlang .data := #' @keywords internal "_PACKAGE"- dplyrなどで列名を直に指定すると
undefined global variablesという警告が出るのでdplyr::filter(diamonds, .data$cut == "Ideal")のように pronoun を使って抑える。 そのためにどこかに#' @importFrom rlang .dataを書いておく。 ただしgroup_by()で多数のグループを処理するときなど、.data$の遅さが気になる場合は!!as.name("carat")のようにする。
タグ
使用可能なタグ一覧を求める声があがって久しいけどまだ無さそう。 roxygen2公式reference は充実してきた。
@import pkg1, pkg2, ...NAMESPACEでimport()するパッケージを指定。 名前の衝突が怖いので基本的に使わない。@importFrom pkg funcNAMESPACEでimportFrom()するパッケージと関数を指定。 重複して書いても大丈夫。- パッケージ内でよほど何回も登場する関数や
名前空間::の形で使いにくい演算子を登録する。 e.g.,@importFrom rlang .data := - ロード時に依存パッケージも丸ごと読み込まれるので重いやつに注意。
@exportNAMESPACEでexport()する関数を指定。 一般ユーザーからはこれが付いてる関数だけ見える。- S3メソッドにもこれをつければ勝手に認識してくれるはずだが、
うまくいかない時は明示的に
@method generic classをつけてみる。 - これ無しのinternalな関数はパッケージ内部か
devtools::load_all()環境下でのみ使える。pkg:::fun()のように三連コロンで無理やり呼び出すこともできるけど、 安全ではなくいろいろ警告される。 @rdname basenameman/に書き出すRdファイルの名前。 複数の関数で同じものを指定すればマニュアルをひとつにまとめられる。 このときタイトルや@paramなどは共有される。@name name- マニュアルやpkgdownで参照されるトピック名を指定する。
デフォルトでは同じ
@rdnameを持つ関数の中で先頭のもの。@aliases alias1 alias2のように追加できる。 @keywordsや@conceptは使わない。 R内のhelp.search()や???での検索にしか使われないので。@keywords internalは特別で、トピックとして登録せずマニュアルを作る効果がある。 一応@exportするけど一般ユーザー向けではない、みたいな関数に使うのかな。@exportしない真のinternal関数については@noRdでマニュアル生成を抑制する(それでもroxygenコメントを書く)ことが推奨されている。@param arg1 description...- 関数の引数。型や役割の説明を文として書く。つまり大文字で始まりピリオドで終わる。
@returns ...- 返り値の型などを簡潔に、文として書く。
“Value” というセクションに出力される。
@descriptionで済むような場合でもCRANに求められるらしい。@returnも同じだが tidyverse 界隈では@returnsのほうが好まれている。 @examples code...- 例となるコードを記述する。 最後に空行を入れるとそれもしっかり含まれてしまう。
- 親環境に影響を与えないように注意する。
ファイルの書き出しや
options()など副作用を伴う例を書く場合は手動で現状復帰する。tempdir()は使えるがon.exit()とwithrは使えないらしい。 - 使い方は見せるけど実行しない、結果を見せない場合は
\dontrun{}に入れる。@examplesIfで条件付きにもできる。 @descriptionの中に## Examplesセクションを自作する手もある。 e.g., dplyr/R/select.R- 単数形の
@exampleは外部ファイルのパスを受け取る。 @inherit package::function [fields...]- 別の関数から継承する。 部分的に継承したければ関数名の後に指定。
- params return title description details seealso sections references examples author source note
@inheritParams pkg::funは@paramの不足を補うショートカット。@inheritDotParams pkg::funを使うと...の内訳を別の関数のドキュメントから継承できる。 特定の引数だけを含めたり除外したりもできる。@usage ...- “Usage” セクションで関数をどう表示するか。
指定しなければ普通に
fun(x, y)のようになる。 - ggplot2の
scale_colour_とscale_color_のようにエイリアスを保持する場合、 ほぼ同じものが2つ表示されるのを抑えたいので、@usage NULLで消したり@usage # alias = origのように明示したりする。 @eval ...- コードを評価して出てきた文字列ベクタをroxygenコメントとして処理する。
各要素の先頭は
#'ではなく@タグで。 関数には引数も渡せるのでいろいろできる。 @seealso ...[mean()],[ggplot2::diamonds],<https://...>のようにしてリンクを張れる。@family ...- これを共通して持つ関数同士に
@seealsoが自動生成される。 ただし@rdnameとかで関数をまとめたりしてるとうまくいかないっぽい。 @include other-file.R- 指定したファイルを先に読み込むように
DESCRIPTIONのCollateを自動生成する。 ひとつでも使うと全ファイルを列挙する形になってしまうので、 メソッドの定義などで順序が重要になる場合にのみ使う。 @template template-nameman-roxygen/template-name.Rの内容を利用する。@inheritParamsと違って、その関数で使用しないparamまで展開されちゃうのが欠点。@section Some Title:@descriptionや@detailsにセクションを作るための古いタグ。 今はMarkdownで簡単に書ける:# Section,## Subsection@docType- パッケージやデータを記述するのに必要だったが今では不要っぽい。
pkgdown
https://r-pkgs.org/website.html
パッケージのソースコードからウェブサイトを自動生成してくれる。 初期設定は例によってusethisに任せる:
use_pkgdown()
use_github_pages()
_pkgdown.yml という設定ファイルを適宜編集して
pkgdown::build_site() を実行すると docs/ 以下にファイルが生成される。
それを GitHub Pages とかで公開する。
ファイル生成と GitHub Pages へのデプロイも GitHub Actions で自動化したければ
use_pkgdown_github_pages() で設定してもらえる。
生成される docs/pkgdown.yml には last_built の日付とか pandoc のバージョンとか、
いちいち差分を発生させたくないような情報が入っているので、
.gitignore に入れて無視したくなる。
しかし pkgdown.yml 不在の docs/ ディレクトリにファイルを生成しようとすると
“docs is non-empty and not built by pkgdown”
と怒られる。
仕方ないので touch docs/pkgdown.yml で空ファイルを作って対処する。