1. okiyuki99

    No comment

    okiyuki99
Changes in body
Source | HTML | Preview

更新履歴

  • 2020-04-03 : version 0.9.3 で内容を見直しました
  • 2019-08-16 : version 0.6.0.108 で初稿を上げました

renv とは?

RStudioが開発を進めているRのPackage管理のためのパッケージである

パッケージ管理といえば、Pythonでは新しいパッケージが次々出ており、流れをキャッチアップするだけで一苦労だが、RではこれまでRStudio社のPackratくらい?しかなかったように思える(私が知らないだけかもしれないが)

パッケージ管理が必要な理由の1つとして、Rの場合は分析の再現性を担保するためだと思う。とくに、複数人でRプロジェクトをおこなうときに威力を発揮する。また自分の場合、CIでテストを行うときやShiny Appを開発するときにも威力を発揮している

もう1つの理由としては、プロジェクトをまたいで別のバージョンの同じライブラリを使いたいときである。たとえば、あるプロジェクトでは、dplyrの0.7系でがっつりコードを書いてていきなり修正するのが難しい。しかし、あるプロジェクトでは最新のdplyrを使いたい。また、あるプロジェクトでは、dplyrの開発バージョンを使いたいなどである。こうなってくると、1つのライブラリパスで管理することは困難になる

なぜrenvが開発されているかの背景を理解するためにrenvProject pageに書いてあるGoalを見てみる

The goal is for renv to be a robust, stable replacement for the Packrat package, with fewer surprises and better default behaviors.

この一文から、Packratを代替しながら、より安定して使いやすいものを目指していることがわかる。Packratを使ってるRユーザはどれくらいいるのだろうか。少なくとも自分の周りでPackrat使ってますという話はほとんど聞かない(自分はそこそこ使ってたほうだと思う)

Packratの使い道を考えてみたがとても詳しいので、Packratの解説は省略します)

Packratとの違い

renvPackratとの違いはProject Pageに書かれている。大きいところとしては、renv.lockというjson形式でRプロジェクトで使用しているパッケージのメタ情報を保存してくれることや、global cacheという仕組みでRプロジェクト間でパッケージを共有できる仕組みがある。そのため、Rプロジェクトを変更するごとに、毎回CRANにパッケージをダウンロードする待ち時間が発生するのはほとんどなくなった点が大きい

global cacheは簡単に言うと、ローカルのhomeディレクトリのような決められた配下にパッケージを保存して、Rプロジェクトで利用するときに、キャッシュがあるなら、そこへのリンク(正式にはシンボリックリンク)を貼ってくれる仕組みである

(以上、つらつらと書いたがよくわからないと思うので、絵としてわかりやすく図解化してくれているくろたんくさんの以下の図が圧倒的にわかりやすいので参考にすると良いです : https://speakerdeck.com/black_tank_top/renv-version-control?slide=13

個人的な感想

Packratをそこそこ使ってた私の感想としては、global cacheにより待ち時間が減り、デフォルトの挙動が覚えやすいので、使いやすいです。そのため、Rでパッケージ管理したことがない方へも導入のしきいは以前よりも低くなっていると感じます。renvでパッケージ管理にトライしてみましょう!

公式情報

参考情報

renvの有用な記事を随時更新していきます

インストール

> install.packages("renv")
> library(renv)
> print(packageVersion("renv"))
[1] 0.9.3

使い方1 : 同じホストでの作業

普段作業するマシン上での作業の流れは以下です。

  1. renv::init() をする
  2. Rコードを書く
  3. renv::snapshot() をたまにする
  4. 作業を終えたら、renv::snapshot()をして、編集したファイルとともに renv/renv.lock ファイルらをgit commitする

renv::initは最初にするだけで、普段はrenv::snapshot()だけ使うだけ。簡単です

renv::init()

renvによるパッケージ管理を始めるために使用する

実行すると、renv/というprivate R libraryの置き場と、.Rprofileが作成される

.Rprofileには、source("renv/activate.R") が記載されており、Rプロジェクト開始とともにrenvによるプロジェクト管理をスタートする処理が書かれている

* Initializing project ...
* Discovering package dependencies ... Done!
* Copying packages into the cache ... Done!
The following package(s) will be updated in the lockfile:

# CRAN ===============================
- renv   [* -> 0.9.3]

* Lockfile written to '~/github/renv-sandbox/renv.lock'.

Restarting R session...

* Project '~/github/renv-sandbox' loaded. [renv 0.9.3]

完了後、.libPaths()を実行してみると、private R libraryが使われている事がわかる(2つめはよくわかっていない)

[1] "/home/ooki/renv-sandbox/renv/library/R-3.6/x86_64-pc-linux-gnu"
[2] "/tmp/RtmpUk1Icd/renv-system-library"     

補足 : global cacheの具体的な動き

このときprivate R library配下にパッケージのソースが置かれるのではなく、global cache配下(Linuxだと~/.local/share/renv)に置かれ、private R libraryにはそこへのリンクが以下のような感じで置かれていることが確認できる

$ ls -la ~/renv-sandbox/renv/library/R-3.6/x86_64-pc-linux-gnu/
<snip>
lrwxrwxrwx 1 ooki ooki  124 Aug 16 19:24 assertthat -> /home/ooki/.local/share/renv/cache/v4/R-3.6/x86_64-pc-linux-gnu/assertthat/0.2.1/87318c127c936afe4da4d066d1fd01c3/assertthat/
lrwxrwxrwx 1 ooki ooki  122 Aug 16 19:24 backports -> /home/ooki/.local/share/renv/cache/v4/R-3.6/x86_64-pc-linux-gnu/backports/1.1.4/8ed7589b3c92d5dc620c45b5f0909d9f/backports/
lrwxrwxrwx 1 ooki ooki  111 Aug 16 19:23 BH -> /home/ooki/.local/share/renv/cache/v4/R-3.6/x86_64-pc-linux-gnu/BH/1.69.0-1/a3839f6fcfa121ea0b5c4b3a0b42456e/BH/
<snip>

renv::snapshot()

このコマンドにより、プロジェクト配下の何らかのファイル(xxx.Ryyy.Rmd)に新たなライブラリが使われていたら、renv.lockに書き込んでくれる。変更がない場合は、以下のようなメッセージが出力される

> renv::snapshot()
* The lockfile is already up to date.

たとえば、新たにtidyrパッケージを使うために、install.packages("tidyr") と実行する。global cacheにすでにtidyrが存在する場合は、そこへのリンクを貼ってくれるだけ(linked cacheという箇所)なのですぐに完了する

>  install.packages("tidyr")
Retrieving 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/tidyr_1.0.2.tgz' ...
    OK [downloaded 996.5 Kb in 0.5 secs]
Installing tidyr [1.0.2] ...
    OK (linked cache)

たとえば、test.Rというファイルにlibrary(tidyr)と新たに追記。

test.R
library(tidyr)

<Rのコードが続く>

この後に再度snapshotを実行すると、tidyrが使われていることをrenvが発見してくれて、renv.lockに書き込んでくれる。

> renv::snapshot()
The following package(s) will be updated in the lockfile:

# CRAN ===============================
- BH           [* -> 1.72.0-3]
- R6           [* -> 2.4.1]
- Rcpp         [* -> 1.0.4]
- assertthat   [* -> 0.2.1]
<snip>
- tidyr        [* -> 1.0.2]
- tidyselect   [* -> 1.0.0]
- utf8         [* -> 1.1.4]
- vctrs        [* -> 0.2.4]

Do you want to proceed? [y/N]: y
* Lockfile written to '~/github/renv-sandbox/renv.lock'.

この辺の発見ロジックは、https://github.com/rstudio/renv/blob/master/R/dependencies.R#L488-L494 あたりのrenv::dependecies()あたりにより実現されていると思われる

(当時 pacman::p_load()renv::dependecies()に対応していないIssueをあげたのですが、翌日には対応してくれるほどのスピード感だったことに驚いた : https://github.com/rstudio/renv/issues/143 )

では、逆にこのtest.Rからlibrary(tidyr)を消した(またはコメントアウトをした)あとに、再びsnapshotをするとどうなるかというと、renv.lockからちゃんと消してくれます。プロジェクトで使用しているライブラリの管理が正確にできそうです

> renv::snapshot()
The following package(s) will be updated in the lockfile:

# CRAN ===============================
- BH           [1.72.0-3 -> *]
- R6           [2.4.1 -> *]
- Rcpp         [1.0.4 -> *]
- assertthat   [0.2.1 -> *]
<snip>
- tidyr        [1.0.2 -> *]
- tidyselect   [1.0.0 -> *]
- utf8         [1.1.4 -> *]
- vctrs        [0.2.4 -> *]

Do you want to proceed? [y/N]: y
* Lockfile written to '~/github/renv-sandbox/renv.lock'.

使い方2 : 異なるホストでの作業

普段作業するマシンとは別のマシン(たとえば、スペックの高い分析サーバが別にあるなど)上での作業の流れは以下です

  1. git clone したのちに、renv::restore()
  2. あとは同じ

renv::restore()

別のマシンまたはCIからRプロジェクトと同じパッケージをインストールするときに使用する

基本的にrenv.lockファイルさえあれば動くので、Gitでrenv.lockを管理しておけばどこからでも同じ環境が再現しやすい

今のところ自分はDocker ImageやCIのまっさらな環境に、renv::restore()させてから、Testさせるときに使っている

> renv::restore()
The following package(s) will be installed:
               _             
  BH             [1.69.0-1]  
  R6             [2.4.0]     
  RColorBrewer   [1.1-2]     
<snip>
Do you want to proceed? [y/N]: y
<snip>

プロジェクトのうち一部のファイルのみをrenvで管理したいときは

デフォルトでは、.gitignoreに準拠してrenvで管理するファイルを決めているが、gitでは管理したいけど、renvでは管理したくないというケースがある。例えば、プロジェクトのすべてのファイルは管理したいけど、試しにアドホックに分析したファイルで読み込んだみたライブラリなどである。そのようなときは、.renvignoreを用意しておけば良い

adhoc_analysis.R

のように書いてから、renv::snapshot()をすれば、adhoc_analysis.RにあるRライブラリは管理されなくなる

参考 : dependencies.html#ignoring-files

その他コマンド

renv::upgrade

renv自体をupdateしたいときは、renv::upgrade でversionを指定すれば、renvのupdateが行われ、管理してるrenv.lockファイルでも自然と更新される

> renv::upgrade(version = "0.9.2-12")

renv::deactivate()

renvによるパッケージ管理が不要と思ったら、気軽にdeactivate()をすればOK

> renv::deactivate()
Restarting R session...

もう一度気が変わってrenvを使いたいとなれば、再度renv::init()をすると、以前のrenv.lockをもとに再開するか聞かれるので選択肢から選びましょう

> renv::init()
This project already has a lockfile. What would you like to do? 

1: Restore the project from the lockfile.
2: Discard the lockfile and re-initialize the project.
3: Activate the project without snapshotting or installing any packages.

renv::status()

renvによるパッケージの管理状態を知るコマンド(といってもあまり使わない)

> renv::status()
* The project is already synchronized with the lockfile.

renv::dependencies()

プロジェクトにあるソースとパッケージの関係を出力。おそらく、renv::snapshot()等で使われる

> renv::dependencies()
Finding R package dependencies ... Done!
                              Source Package Require Version
1 /home/ooki/renv-sandbox/analysis.R   dplyr                
2 /home/ooki/renv-sandbox/analysis.R ggplot2                
3 /home/ooki/renv-sandbox/analysis.R   tidyr                
4   /home/ooki/renv-sandbox/script.R remotes                
5   /home/ooki/renv-sandbox/script.R    renv 

renv::hydrate()

ProjectのなかでSystem libraryから使われているPackageをprivate R libraryにインストールしてくれる。renv::init() の中でこれが実行されている模様

> renv::hydrate()

renv::clean()

使ってないライブラリをPrivate R libraryからRemoveしてくれる。たまにお掃除したくなったときに使う

> renv::clean()

renv::migrate()

Packratからrenvに移行するコマンド。使用したことはないが一応あるみたい

renv::settings$ignored.packages()

renv管理から無視するパッケージ(testやdevtoolsなどが該当するかと思う)を設定する
参考 : https://rstudio.github.io/renv/reference/settings.html

> renv::settings$ignored.packages()
character(0)
> renv::settings$ignored.packages("devtools", persist = FALSE)
> renv::settings$ignored.packages()
[1] "devtools"

renv::settings$use.cache(FALSE)

renvのglobal cacheを使わなくする

renv::settings$snapshot.type("simple")

renvのsnapshotの方法を変更する。デフォルトはpackratsimpleにしたら、もとのRのユーザライブラリのすべてをrenv.lockに書き込もうとするようになる

おわりに

Packratはあまり周りで使ってる人はいなかったが、複雑なことをしなければrenvは使い勝手もシンプルでわかりやすい気がします。デフォルトのパッケージ環境を汚さなかったり、分析の再現可能性を担保できることを期待して、Rユーザの中で流行ってほしいなと個人的には思っています。Enjoy Renv!