初めてのRパッケージ作成:最初の一歩

この記事は,ベイズ塾 Advent Calendar 2020 - AdventarOpen and Reproducible Science Advent Calendar 2020 - Adventarの12日目の記事です。

この数年,Rで解析をしていて,繰り返し使いそうなものは関数化したり,さらに他の人に使ってもらう場合は,パッケージ化するようにしています。といっても,CRANに登録するような気合の入ったものではなくて,GitHubで公開するようなライトなものです。まず,ベイズ塾の塾生はRスキルが高いので,どんどんオリジナルなパッケージを作っていくといいなあと思います。また,オープンサイエンスにおいては,オープンな解析ソフトの開発や公開が重要なので,日本からもこういうことに貢献できるような素地を作っていきたいなあと思います。ということで,今回は,そういうライトなパッケージ作成の最初の一歩の記事を書いてみようかと思います。

Rパッケージ作成に有用なサイト

私がRパッケージを作る時は以下のサイトを参照しています。全く覚えられないので毎回検索して,大体この2つを参照して作って,すぐに忘れることを繰り返しています。「Rパッケージの作成は,以下のサイトを参考にすると良いよ!終わり!!!」でもいいのですが,ちょっとは自分で覚えましょうかねということで,以下にまとめてみようかなと思います。

Practical R Package Development (Japanese)

こわくないRパッケージ開発!2016 - Qiita

今回作成するパッケージ

具体的な作業手順を説明したいと思うので,何か適当なRパッケージを作ることにします。簡単でかつ使いそうなものがなかなか浮かばず,今回は,Rで解析が終了したらslackに通知するというパッケージを作ります。ただ,slackとRの連携については,以下のslackrという便利なパッケージが既にあります。今回は,自分用にライト(あとでマニアックに調整できるくらいシンプル)なslack通知パッケージを作ることにします。

github.com

私は,時間のかかる解析をする時は,以下のようなコードを解析に関するコードの下にいれておいて,解析が終わったらslackに通知されるようにしています(slackのAPIトークンの取得は以下の記事を参照ください)。まあ,これでいいっちゃいいのですが,まあまあコードが長いですし,slackのトークンとチャンネル名などが入っているので,githubでコードをそのまま共有できないです。

httr::POST(url="https://slack.com/api/chat.postMessage",
             body = list(token = slackのトークン,
                         channel = slackのチャンネル,
                         username = slackのユーザーネーム,
                         text = paste(format(as.POSIXlt(Sys.time(), tz = "Asia/Tokyo"),"%Y/%m/%d %H:%M"),"なんかメッセージ")))

qiita.com

そこで,もう少し工夫をして,以下のような関数にしてみました。工夫点は以下のとおりです。

  • 関数の引数にslackのトークンの情報をそのままいれるとそのコードの共有がしにくくなりますし,GitHubにもあげにくいので,環境変数に書き込む関数set_slack_info()を追加しました。よくわかりませんが,Sys.setenv()で環境変数を設定できるようです。この設定はRを再起動しちゃうと消えちゃうので,そうしないためには,".Renviron"などに書き込む必要がありますが,今回は深入りを避けます。

  • 次に,上記のコードをsend_slack()関数にしました。トークンなどの情報は,set_slack()で設定するので,もしそれらの設定ができてなかったらエラーが出るようにしておきます。

set_slack <- function(slack_token, 
                           slack_channel = "#general", 
                           slack_username = "R"){
  if(missing(slack_token)){
    stop("Please set slack_token")
  }
  Sys.setenv(SLACK_TOKEN=slack_token)
  Sys.setenv(SLACK_CHANNEL=slack_channel)
  Sys.setenv(SLACK_USERNAME=slack_username)
}

send_slack <- function(message = "Analysis has been completed"){
  # check argument
  if (Sys.getenv("SLACK_TOKEN") == "") {
    stop("Please set slack_token with set_slack")
  }
  if (Sys.getenv("SLACK_CHANNEL") == "") {
    stop("Please set slack_channel with set_slack")
  }
  if (Sys.getenv("SLACK_USERNAME") == "") {
    stop("Please set slack_username with set_slack")
  }
  
  # send message to slack
  httr::POST(url="https://slack.com/api/chat.postMessage",
             body = list(token = Sys.getenv("SLACK_TOKEN"),
                         channel = Sys.getenv("SLACK_CHANNEL"),
                         username = Sys.getenv("SLACK_USERNAME"),
                         text = paste(format(as.POSIXlt(Sys.time(), tz = "Asia/Tokyo"),"%Y/%m/%d %H:%M"),message)))
}

使い方は,以下のようにset_slackでslackの情報を環境変数に追加します。

set_slack("slackのAPIトークン","#チャンネル名", "ユーザー名")

そのうえで,send_slack()内にメッセージをいれると,「2020/12/11 10:15 研究1の仮説1の推定終了!」みたいに,終わった時間(今回は,Asia/Tokyo時間に固定しています)とメッセージがslackの指定したチャンネルに届きます。まあ,それだけのチョット便利関数です。

send_slack("研究1の仮説1の推定終了!")

関数をRパッケージ化してみよう!

では,このシンプルな関数をRパッケージ化していくことにしましょう!まず,以下をご準備ください。

  • GitHubアカウント

  • Rstudio

  • Rパッケージ(devtools, usethis, remotes)

install.packages("devtools")
install.packages("usethis")
install.packages("remotes")

(1) Rパッケージ作成用プロジェクトを用意する

では,Rパッケージ作成用プロジェクトを用意しましょう。Rstudioで,File -> New Project...を選びます。

f:id:cpp-laboratory:20201211115811p:plain

出てきたウィンドウで,New Directoryを選びます。

f:id:cpp-laboratory:20201211120523p:plain

続いて,Project Typeは,R packageを選びます。

f:id:cpp-laboratory:20201211120527p:plain

出てきたウィンドウで,Package nameを記入します。なお,アンダーバー(_)は使えません。今回は,雑にeasySlackにします。Create a git reprositoryにチェックをいれておいてください。

f:id:cpp-laboratory:20201211120530p:plain

Filesをみると以下のようにファイルができています。

f:id:cpp-laboratory:20201211155209p:plain

(2) Rパッケージ作成用プロジェクトを設定する

第1章 新しくRパッケージを作る | Practical R Package Development (Japanese)を参考に,設定していきます。RstudioのBuild -> Configure Build Tools...をクリック。

f:id:cpp-laboratory:20201211155212p:plain

Build ToolsのGnerate documentation with Roxygenにチェックを入れる。

f:id:cpp-laboratory:20201211155215p:plain

なにかポップアップしてくるけど,これは,「OK」をクリックする。

f:id:cpp-laboratory:20201211155218p:plain

以下のファイルは不要なので,削除する。

  • NAMESPACE
  • Rフォルダ内のhello.R
  • manフォルダ内のhello.Rd

以下のように選択して,Deleteで削除する。

f:id:cpp-laboratory:20201211155734p:plain

(3) GitHubの設定をする

GitHubにアクセスして,ログインします。どこかに,Newっていう新たにリポジトリを作るボタンをあるので,クリックします。

f:id:cpp-laboratory:20201211160444p:plain

以下のように,Create a new repositoryが出てくるので,Repository nameを書き込んで,Create repogitoryをクリックします。Repository nameはパッケージ名です。

f:id:cpp-laboratory:20201211160448p:plain

レポジトリができると何か文字がでてくる画面になりますが,以下を確認します。

f:id:cpp-laboratory:20201211160451p:plain

上記の画面のうち,まずはgit remote ... の1行を使いますので,コピーします。コピーしたものを以下のように,RstudioのTerminalにペーストして実行します(Consoleじゃありません)。

f:id:cpp-laboratory:20201211161123p:plain

(4) パッケージに関する情報を設定をする

RstudioでFilesにあるDESCRIPTIONを開きます。以下のような感じです。

f:id:cpp-laboratory:20201211161710p:plain

細かいところは,後で直せるので,Version を0.0.1にして,AuthorとMaintainerだけ書き込みました。

f:id:cpp-laboratory:20201211161714p:plain

次は,ライセンスの情報を追加します。MITライセンスが多いかなと思います。usethisパッケージのuse_mit_license()関数が便利です。自分の名前を引数にいれましょう。次に,use_readme_md()でREADMEファイルを追加し,use_roxygen_md()でRのドキュメント作成を自動化してくれるroxygenの設定をします。以下をConsoleに打ち込みましょう。

usethis::use_mit_license("Yoshihiko Kunisato")
usethis::use_readme_md()
usethis::use_roxygen_md()

README.mdファイルが自動的に開く(開いてなければ,Filesから開く)。将来的にはここに説明を書き込むわけですが,後でもできるので,そのままにします。

f:id:cpp-laboratory:20201211163315p:plain

(5) 一旦コミットして,GitHubにプッシュする

さて,ここまでのところ,一旦Gitでコミットして,GitHubにプッシュしておきます。RstudioのGitタブで,Commitをクリックします。

f:id:cpp-laboratory:20201211164357p:plain

以下のような画面でてくるので,ファイルを全て選択して,Stageをクリックします。

f:id:cpp-laboratory:20201211164401p:plain

Commit messageにコメントを書きます。ここでは,適当にinit commitとかにしてみました。Commitをクリックして,終わったら,閉じます。

f:id:cpp-laboratory:20201211164405p:plain

さて,通常はこのcommitの画面でプッシュできますが,最初は以下のコードをRsutidoのTerminalに打ち込みます。これで,GitHub上にファイルや変更点がプッシュされます。

git branch -M main
git push -u origin main

(6) Rの関数を作成して,roxygenコメントを書き込む

これでパッケージ作成の下準備が整いました。ここから,パッケージのRフォルダ内にRの関数を書いていったり,それを文章化するためのroxygenコメントを書いていきます。まず,Rフォルダ内に,新規でRのファイルを作成し,先程のRの関数を貼り付けて,easy_slack.Rという名前で保存します。

次に,roxygenコメントを書きます。roxygenコメントとは, #' から始まるもので,@XXXによって,タイトル,説明,関数内で使用する他のパッケージ,出力,例などを書きます。これをやっておくと,あとで勝手にRで使われるマニュアルの形式にしてくれます(便利!)。おおよそ以下がR関数の記述の前にあればいいかなと思います。

#' @title タイトル(関数がすることを書くと良い)
#' @description \code{関数名} 説明(関数がすることを書くと良い)
#'
#' @importFrom パッケージ名 関数名
#' @param 引数 説明 
#' @return 出力の説明
#' @export
#' @examples
#' 関数の例

今回は,特に出力をだす関数じゃないので,@returnはないのですが,それ以外は,上記と同じように以下のように設定します。なお,@exportはユーザーが実行する関数につけます。今回,set_slack()もsend_slack()もユーザーが実行するので,@exportをつけます。

#' @title Setting the slack information to system
#' @description \code{set_slack} set the slack information to system
#'
#' @param slack_token slack acess token
#' @param slack_channel slack channel
#' @param slack_username slack username
#' @export
#' @examples
#' # set_slack("GitHub access token", "#r", "Mr.R")

set_slack <- function(slack_token,
                      slack_channel = "#general",
                      slack_username = "R"){
  if(missing(slack_token)){
    stop("Please set slack_token")
  }
  Sys.setenv(SLACK_TOKEN=slack_token)
  Sys.setenv(SLACK_CHANNEL=slack_channel)
  Sys.setenv(SLACK_USERNAME=slack_username)
}


#' @title Sending the message to slack channel
#' @description \code{send_slack} send the message to slack channel
#'
#' @importFrom httr POST
#' @param message message send to slack
#' @export
#' @examples
#' # send_slack("Hello!")

send_slack <- function(message = "Analysis has been completed"){
  # check argument
  if (Sys.getenv("SLACK_TOKEN") == "") {
    stop("Please set slack_token with set_slack")
  }
  if (Sys.getenv("SLACK_CHANNEL") == "") {
    stop("Please set slack_channel with set_slack")
  }
  if (Sys.getenv("SLACK_USERNAME") == "") {
    stop("Please set slack_username with set_slack")
  }

  # send message to slack
  httr::POST(url="https://slack.com/api/chat.postMessage",
             body = list(token = Sys.getenv("SLACK_TOKEN"),
                         channel = Sys.getenv("SLACK_CHANNEL"),
                         username = Sys.getenv("SLACK_USERNAME"),
                         text = paste(format(as.POSIXlt(Sys.time(), tz = "Asia/Tokyo"),"%Y/%m/%d %H:%M"),message)))
}

roxygenが書けたら,以下を実行します。すると,manフォルダ内に関数ごとにマニュアルが作成されます。これは,後でパッケージとしてインストールしたら,help()で見ることができます。

devtools::document()

send_slack()関数では,roxygenで@importFrom httr POSTと書いています。つまり,httrパッケージのPOST関数を使います。これをDESCRIPTIONに書き込む必要がありますが,以下のusethis::use_package()を使えば書き込んでくれます。以下をConsoleで実行します。

usethis::use_package("httr")

DESCRIPTIONをみると,Imports: httrが追加されています。

f:id:cpp-laboratory:20201211175256p:plain

動作確認をしてみましょう。devtools::load_all()を使うとインストールせずに,パッケージの関数などを確認できます。devtools::load_all()した上で,set_slack()とsend_slack()を確認します。slackのトークンとチャンネルを適切に設定した上で(XXXXと#r),以下をConsoleで実行します。

devtools::load_all()

set_slack("XXXXXXXXXXX","#r", "R博士")
send_slack()

動作してそうです。

f:id:cpp-laboratory:20201212042711p:plain

slackにも無事に通知がきました。

f:id:cpp-laboratory:20201211175300p:plain

さて,ここまできたら,devtools::check()でビルドとチェックをします。

devtools::check()

実行すると何かごにょごにょやって,もし問題があればエラーなどが出ます(以下は全て問題ない場合です)。なお,実行途中で止まることがありますが,その場合は,このRパッケージ作成用プロジェクトを開き直すと良いかもしれません(理由は分からないけどうまくいくことが多い謎Tipsです)。

f:id:cpp-laboratory:20201212041035p:plain

Rパッケージの完成までは,以下を繰り返します。

  1. Rフォルダ内でR関数とroxygen書く
  2. 適宜devtools::document()やusethis::use_package()を実行する
  3. devtools::load_all()で動作確認する
  4. devtools::check()を実行する

(7) GitHub経由でインストールして,動作確認する

完成したら,GitHub経由でインストールしてみましょう。一旦,パッケージ作成用のプロジェクトを閉じます(FileのClose project)。Consoleに以下を打ち込んでインストールします。

remotes::install_github("ykunisato/easySlack")

インストールが無事にできたら,早速関数を使ってみてテストをしてみます。

library(easySlack)
set_slack("GitHubのアクセストークン", "#チャンネル名", "ユーザー名")
send_slack()

無事にslackに送れました!

f:id:cpp-laboratory:20201211175300p:plain

さらにConsoleに以下を打ち込むと,ヘルプが出てきます。

?set_slack

roxygenで書いた内容が反映されています。ただ,もう少しドキュメントをちゃんと作らないとですね(急いでやったので英語も適当です)。

f:id:cpp-laboratory:20201212044001p:plain

今回作ったパッケージのGitHubリポジトリは以下になります。練習用ではありますが,ちょっとずつ整備をしてみようかと思います。

github.com

Enjoy!