パッケージの管理

最終更新日:2024-10-10 | ページの編集

概要

質問

  • targets プロジェクトのパッケージをどのように管理すべきですか?

目的

  • パッケージ管理のベストプラクティスを実演する

パッケージの読み込み


ほとんどすべての R 分析は、base R で利用可能な機能を超える関数を提供するパッケージに依存しています。

targets ワークフローでパッケージを読み込む主な方法は3つあります。

方法1: library()

これはおそらく最も馴染みのある方法であり、これまでデフォルトで使用してきた方法です。

他の R スクリプトと同様に、_targets.R スクリプトの上部近くに library() 呼び出しを含めます。あるいは(プロジェクト組織の推奨ベストプラクティスとして)、すべての library() 呼び出しを別のスクリプトに配置することもできます—通常これは packages.R と呼ばれ、プロジェクトの R/ ディレクトリに保存されます。

このアプローチの潜在的な欠点は、読み込むパッケージのリストが長い場合、tar_visnetwork()tar_outdated() などの特定の関数がすべてのパッケージを読み込まなければならないため、必ずしもそれらを使用しなくても不必要に長い時間がかかる可能性があることです。

方法2: tar_option_set()

この方法では、ワークフローを実行するときに読み込むパッケージを指定するために、_targets.R 内で tar_option_set() 関数を使用します。

これは palmerpenguins パッケージの事前にクリーンアップされたデータセットを使用して実演されます。例えば、アデリーペンギンのデータのみにフィルタリングしたいとしましょう。

進捗の保存

1つのプロジェクト内でアクティブな _targets.R ファイルは1つだけです。

新しい _targets.R ファイルを作成しようとしていますが、これまで作業してきたもの(ペンギンのくちばし分析)の進捗を失いたくないでしょう。そのファイルを一時的に _targets_old.R のような名前に変更することで、以下の新しい例の _targets.R ファイルを上書きしないようにできます。再び作業を再開するときに名前を戻してください。

これが tar_option_set() メソッドを使用したときの例です:

R

library(targets)
library(tarchetypes)

tar_option_set(packages = c("dplyr", "palmerpenguins"))

tar_plan(
  adelie_data = filter(penguins, species == "Adelie")
)

出力

▶ dispatched target adelie_data
● completed target adelie_data [0.018 seconds]
▶ ended pipeline [0.098 seconds]

この方法は、方法1で時々経験するかもしれない遅延を回避します。

方法3: tar_target()packages 引数

ターゲットを定義するための主要な関数である tar_target() には、指定したターゲットのためにのみ指定されたパッケージを読み込む packages 引数があります。

これは、上記と同じ例から修正したこの方法の使用例です。

R

library(targets)
library(tarchetypes)

tar_plan(
  tar_target(
    adelie_data,
    filter(penguins, species == "Adelie"),
    packages = c("dplyr", "palmerpenguins")
  )
)

出力

▶ dispatched target adelie_data
● completed target adelie_data [0.016 seconds]
▶ ended pipeline [0.097 seconds]

これは、すべてのパッケージを読み込むよりもメモリ効率が良い場合があります。なぜなら、ワークフローの通常の実行中にすべてのターゲットが常に作成されるわけではないからです。 しかし、ターゲットごとに必要なパッケージを覚えて指定するのは手間がかかることがあります。

もう一つのオプション

実際にパッケージを読み込むことなく、:: 記法を使用して各関数に関連付けられたパッケージを指定する方法があります。例えば、dplyr::mutate() です。 これにより、パッケージの読み込みを完全に避けることができます

この方法を使用してプランを書く方法は以下の通りです:

R

library(targets)
library(tarchetypes)

tar_plan(
  adelie_data = dplyr::filter(palmerpenguins::penguins, species == "Adelie")
)

出力

▶ dispatched target adelie_data
● completed target adelie_data [0.008 seconds]
▶ ended pipeline [0.088 seconds]

このアプローチの利点は、すべての関数の起源が明確になることです。例えば、GitHub でソースを見たりすることで、すぐに関数がどこから来ているかを知ることができます。 欠点は、関数を使用するたびにパッケージ名を入力する必要があるため、やや冗長になることです。

どの方法が正しいのでしょうか?

パッケージの読み込み方法に「正解」はありません—それは特定の状況に最も適した方法の問題です。

多くの場合、最も頻繁に使用するパッケージ(例えば tidyverse)を packages.R 内で library() を使って読み込み、起源を忘れがちなあまり頻繁に使用しない関数には :: 記法を使用するのが合理的なアプローチです。

パッケージバージョンの維持


カスタム関数とパッケージの関数の追跡

targets について理解しておくべき重要な点は、カスタム関数とターゲットのみを追跡し、パッケージによって提供される関数は追跡しない ということです。

しかし、パッケージの内容は変更されることがあり、パッケージは通常定期的に更新されます。ワークフローの出力は、使用するパッケージだけでなく、そのバージョンにも依存する可能性があります

したがって、パッケージバージョンを追跡することは良い考えです。

renv について

幸いなことに、これを手動で行う必要はありません。このプロセスを自動化するのに役立つ R パッケージが利用可能です。私たちは renv をお勧めしますが、他にも groundhog などがあります。このレッスンでは renv の詳細な使用方法をカバーする時間がありません。renv を始めるには、“Introduction to renv” ビネット を参照してください。

一般的に、renv は他の R プロジェクトと同様に targets プロジェクトでも使用できます。しかし、1つの例外があります:tar_option_set()tar_target()packages 引数(それぞれ方法2 または方法3)を使用してパッケージを読み込む場合、renv はそれらを検出しません(なぜなら、renvlibrary()require() などでパッケージが読み込まれることを期待しているからです)。

この場合の解決策は、tar_renv() 関数 を使用することです。これにより、ワークフローで使用される各パッケージの library() 呼び出しを含む別のファイルが書き出され、renv がそれらを正しく検出できるようになります。

パッケージの関数の選択的追跡

targets はパッケージの関数を追跡しないため、パッケージを更新してその関数の内容が変更された場合、targets その関数によって生成されたターゲットを再構築しません

しかし、この動作をパッケージごとに変更することは可能です。 これは、依存関係を計算する際に targets に過度な計算負荷をかけないため、少数のパッケージに対してのみ行うのが最適です。 例えば、頻繁に更新する独自のカスタムパッケージを使用している場合にこれを行いたいかもしれません。

これを行う方法は、tar_option_set() を使用し、packagesimports の両方に同じパッケージ名を指定することです。以下は、dplyrpalmerpenguins に対してこれを示す前のコードを修正したバージョンです。

R

library(targets)
library(tarchetypes)

tar_option_set(
  packages = c("dplyr", "palmerpenguins"),
  imports = c("dplyr", "palmerpenguins")
)

tar_plan(
  adelie_data = filter(penguins, species == "Adelie")
)

もし dplyr または palmerpenguins を再インストールし、パイプラインで使用されるそれらの関数の一つ(例えば filter())が変更された場合、その関数に依存するターゲットは再構築されます。

名前空間の競合の解決


パッケージに関連して言及すべき最後のベストプラクティスがあります:名前空間の競合の解決です。

「名前空間」とは、一連の一意の名前が特定のコンテキスト内でのみ一意であるという考え方を指します。 例えば、パッケージのすべての関数名は一意でなければなりませんが、そのパッケージ内でのみです。 関数名は複数のパッケージで重複する可能性があります。

ご想像の通り、これは混乱を招く可能性があります。 例えば、filter() 関数は stats パッケージと dplyr パッケージの両方に存在しますが、それぞれで全く異なる動作をします。 これは名前空間の競合です:私たちはどの filter() を指しているのか、どうやって知るのでしょうか?

conflicted パッケージは、曖昧な関数を使用しようとした場合に停止させ、どのパッケージを使用するかを明確にするのを助けることで、このような混乱を防ぐのに役立ちます。 ここでは詳細をカバーする時間がありませんが、conflicted の使用方法については 公式サイト を参照してください。

conflicted を使用するとき、通常は conflicts_prefer(dplyr::filter) のように名前空間の競合を明示的に解決する一連のコマンドを実行します(これは、dplyrfilter を使用したいことを R に伝えます、statsfilter ではありません)。

これを targets ワークフローで使用するには、.Rprofile と呼ばれる特別なファイルに conflicts_prefer のすべての呼び出しを配置する必要があります。このファイルはプロジェクトのメインフォルダにあります。これにより、各ターゲットに対して常に競合が解決されることが保証されます。

.Rprofile を編集する推奨方法は、usethis::edit_r_profile("project") を使用することです。 これにより、エディタで .Rprofile が開かれ、そこで編集して保存できます。

例えば、あなたの .Rprofile は以下のようになります:

R

library(conflicted)
conflicts_prefer(dplyr::filter)

.Rprofile のコードを実行するために source() を実行する必要はありません。 それは各 R セッションの開始時に自動的に実行されます。

まとめ

  • targets でパッケージをロードする方法は複数あります
  • targets はユーザー定義の関数のみを追跡し、パッケージは追跡しません
  • renv を使用してパッケージバージョンを管理する
  • conflicted パッケージを使用して名前空間の競合を管理する