Rのパッケージ管理のためのrenvの使い方

## 更新履歴
* 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`が開発されているかの背景を理解するために`renv`の[Project page](https://rstudio.github.io/renv/)に書いてある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の使い道を考えてみた](https://qiita.com/uri/items/5d473a5df91954585e62)がとても詳しいので、Packratの解説は省略します）

## `Packrat`との違い

`renv`の`Packrat`との違いは[Project Page](https://rstudio.github.io/renv/articles/renv.html#comparison-with-packrat)に書かれている。大きいところとしては、`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でパッケージ管理にトライしてみましょう！

## 公式情報
* Github : https://github.com/rstudio/renv
* Project page : https://rstudio.github.io/renv/
    * "Using renv with Docker" のArticles読めばDocker Image作るときに楽になると思います 

## 参考情報

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

* 2020-01-30 [renv: Project Environments for R](https://kevinushey-2020-rstudio-conf.netlify.com/slides.html#1) : rstudio::conf 2020での発表
* 2019-11-06 [renv: Project Environments for R](https://blog.rstudio.com/2019/11/06/renv-project-environments-for-r/) CRANについにアップロードされました 
* 2019-10-01 くろたんくさんの[renvによるパッケージ管理(speakerdeck)](https://speakerdeck.com/black_tank_top/renv-version-control) : 分析再現性担保のためのrenvをつかったパッケージ管理のまとめ資料
* [Snapshot and Restore](https://environments.rstudio.com/snapshot) : この記事がrenvの使用の流れを掴むのにわかりやすいです

## インストール

```R
> 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`によるプロジェクト管理をスタートする処理が書かれている

```R
* 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にはそこへのリンクが以下のような感じで置かれていることが確認できる

```console
$ 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.R`や`yyy.Rmd`）に新たなライブラリが使われていたら、`renv.lock`に書き込んでくれる。変更がない場合は、以下のようなメッセージが出力される

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

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

```R
>  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`に書き込んでくれる。

```R
> 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`からちゃんと消してくれます。プロジェクトで使用しているライブラリの管理が正確にできそうです

```R
> 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させるときに使っている

```R
> 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](https://rstudio.github.io/renv/reference/dependencies.html#ignoring-files)

## その他コマンド

### `renv::upgrade`

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

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

### ```renv::deactivate()```

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

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

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

```R
> 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によるパッケージの管理状態を知るコマンド（といってもあまり使わない）

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

### ```renv::dependencies()```

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

```R
> 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()` の中でこれが実行されている模様

```R
> renv::hydrate()
```

### `renv::clean()`

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

```R
> renv::clean()
```

### `renv::migrate()`

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

### `renv::settings$ignored.packages()`

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

```R
> 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の方法を変更する。デフォルトは`packrat`。`simple`にしたら、もとのRのユーザライブラリのすべてを`renv.lock`に書き込もうとするようになる


## おわりに

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