/* terashim.com */

システム開発・データエンジニアリング・データ分析についての個人的なノート

renv の explicit なスナップショットタイプと DESCRIPTION ファイル

要約

renv のスナップショット(バージョン固定)にはいくつかのタイプがあります。 そのうち "explicit" なスナップショットタイプがおすすめです。 この方法では対象となるパッケージを DESCRIPTION ファイルで明示的に指定します。 プロジェクトの依存関係を宣言的に記述することができるため、意図が理解しやすく変更も容易になります。

renv とは

renv は R のパッケージマネージャです。 renv を使うとプロジェクトごとに使用するパッケージのバージョン固定を行い、 ライブラリの状態を再現することができるようになります。 バージョン固定を行ったとき、その情報は renv.lock ファイルに記録されます。

renv 自体の使い方については例えば

に詳しい説明があります。

スナップショットタイプ

バージョン固定を行うための関数 renv::snapshot() には type というオプション引数があります。 このオプションによってバージョン固定の対象となるパッケージの選び方が変わります。

  • "all": その時点でライブラリにインストールされているすべてのパッケージが対象となる
  • "implicit": プロジェクトのコードで使用されているパッケージとその依存関係が対象となる
  • "explicit": DESCRIPTION ファイルで明示的に指定されたパッケージとその依存関係を対象となる
  • "custom": "implicit" タイプの方法で選ばれたパッケージに対して、ユーザー定義のフィルタを適用した結果が対象となる

renv::init() でプロジェクトを初期化したとき、デフォルトの設定では "implicit" なスナップショットが使用されるようになっています。

"explicit" なスナップショットの使い方

"explicit" なスナップショットを使用する場合、まず DESCRIPTION ファイルを作成します。DESCRIPTION ファイルは通常パッケージ開発プロジェクトでパッケージのメタ情報を示すために作成されるファイルです。これについては例えば https://r-pkgs.org/description.html に詳しい説明があります。 renv ではパッケージ開発プロジェクトでなくても DESCRIPTION ファイルで依存関係を宣言できます。

例えば、プロジェクトのルートディレクトリに DESCRIPTION ファイルを作成して次のような内容に編集します:

Type: project
Description: My project.
Depends:
    renv (0.15.4),
    palmerpenguins,
    dplyr (== 1.0.9),
    tidyr (== 1.1.0)

このように、利用したいパッケージは Depends フィールドや Imports フィールドで指定します。

この状態で

renv::install()

を実行すると、パッケージ renvpalmerpenguinstidyrdplyr がインストールされます.

次に、バージョン固定のため

renv::snapshot(type = "explicit")

を実行すると、renv.lock ファイルが作成されパッケージのバージョンが記録されます。ここで対象となるのは DESCRIPTION ファイルに記載された renvpalmerpenguinstidyrdplyr とその依存関係のパッケージです。

このとき、例えばもしライブラリに devtools がインストールされていたとしても、バージョン固定の対象にはなりません。またソースコード中に library(ggplot2) のように ggplot2 を呼び出すコードが入っていたとしても、やはりこれも対象になりません。

renv.lock ファイルの内容は例えば次のようになります:

{
  "R": {
    "Version": "4.2.0",
    "Repositories": [
      {
        "Name": "CRAN",
        "URL": "https://packagemanager.rstudio.com/cran/latest"
      }
    ]
  },
  "Packages": {
    "R6": {
      "Package": "R6",
      "Version": "2.5.1",
      "Source": "Repository",
      "Repository": "RSPM",
      "Hash": "470851b6d5d0ac559e9d01bb352b4021",
      "Requirements": []
    },
    "cli": {
      "Package": "cli",
      "Version": "3.3.0",
      "Source": "Repository",
      "Repository": "RSPM",
      "Hash": "23abf173c2b783dcc43379ab9bba00ee",
      "Requirements": [
        "glue"
      ]
    },

    ...(中略)...

    "utf8": {
      "Package": "utf8",
      "Version": "1.2.2",
      "Source": "Repository",
      "Repository": "RSPM",
      "Hash": "c9c462b759a5cc844ae25b5942654d13",
      "Requirements": []
    },
    "vctrs": {
      "Package": "vctrs",
      "Version": "0.4.1",
      "Source": "Repository",
      "Repository": "RSPM",
      "Hash": "8b54f22e2a58c4f275479c92ce041a57",
      "Requirements": [
        "cli",
        "glue",
        "rlang"
      ]
    }
  }
}

"explicit" なスナップショットの利点

デフォルトの "implicit" なスナップショットでは、ソースコードの解析によってどのパッケージが使われているかが判定されます。コードの規模が大きくなってくると、コードの解析に時間がかかったり意図しないパッケージが追加されたりする可能性が高くなってきます。

"explicit" なスナップショットを使うことによって、コード解析を行わず開発者が DESCRIPTION で明示的に指定したパッケージとその依存関係だけが renv.lock に記録されるようになります。

また、DESCRIPTION ファイルにインストールしたいパッケージを宣言することで、意図して指定したパッケージやそのバージョンに関する条件と、結果的にインストールされたパッケージやそのバージョンとを区別することができるようになります。

そのため、使用するパッケージを後から変更したときもその意図がわかりやすくなります(tidyr を 1.0 から 1.1 に変えたが、同時にその依存関係がアップデートされたなど)。DESCRIPTION ファイルなしで renv.lock の差分だけから変更の意図を推測することは困難でしょう。

他言語との比較

他言語のパッケージマネージャでは、依存関係の宣言とバージョン固定用のロックファイルとを区別することはよく行われています。例えば Pipenv との比較でいうと、DESCRIPTION ファイルは Pipfile に、renv.lock ファイルは Pipfile.lock に相当します。

いくつかの例を表に示します。

言語パッケージマネージャ依存関係の宣言ロックファイル
RrenvDESCRIPTIONrenv.lock
PythonPipenvPipfilePipfile.lock
JavaScriptnpmpackage.jsonpackage-lock.json
PHPComposercomposer.jsoncomposer.lock

注意

現時点 (renv バージョン 0.15.4) では、DESCRIPTION ファイルにおいて >>=<<= によるバージョンの上限・下限指定は効かないので注意が必要です。

例えば tidyr のマイナーバージョンまでは 1.1 で固定し、パッチバージョンは最新のものを反映したいという意図で

Depends:
  tidyr (>= 1.1.0),
  tidyr (< 1.2.0)

のように指定したとしても、renv::install() を実行すると常に最新のバージョン(1.2 以上)がインストールされてしまいます。

現状で選択できるのは == で完全にバージョンを指定するか、何も指定せず新しいバージョンがインストールされるようにするかの2通りのみとなっています。

標準リポジトリ以外からのインストール

標準リポジトリ以外の場所からパッケージをインストールしたい場合は Remotes フィールドを使います。

例えば、 GitHub から tidyrv1.1.0 をインストールしたい場合は次のように指定します:

Type: project
Description: My project.
Imports:
    renv (0.15.4),
    palmerpenguins,
    dplyr (== 1.0.9),
    tidyr
Remotes: tidyverse/[email protected]

Remotes フィールドの文法について詳しくは devtools のドキュメント https://devtools.r-lib.org/articles/dependencies.html に説明があります。

関連記事