cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

名前空間の衝突をconflictedパッケージで防ぐ

要約

  • パッケージを複数利用すると関数名等の衝突が発生し、意図しない振る舞いを取ることがある
  • conflictedパッケージは、こうした衝突を防ぐための機能を提供する
  • 多少の手間を惜しんでも、衝突の恐れのある関数については名前空間を指定することを勧める

f:id:u_ribo:20180929102909p:plain

衝突にまつわるトラブル

Rは標準の機能として十分な統計処理やデータ可視化の関数を備えていますが、パッケージと呼ばれる拡張機能を導入することでより多くの処理が実行可能になります。一方で多数のパッケージを使うと名前空間の衝突が発生することがあります。これは共通の名前を持つ関数やデータセット、定数といったRオブジェクトが複数のパッケージに存在する時に起きる問題です。衝突が発生すると、オブジェクトが意図しない働きをもつことがあり、トラブルの元になります。

普通、後から読み込まれたパッケージにより、先に読み込まれているオブジェクトはマスクされます。ここでいう「マスク」とは、オブジェクト名だけでの参照ができなくなる状態を指します。例を見てみましょう。

library(dplyr)
## 
## Attaching package: 'dplyr'

## The following objects are masked from 'package:stats':
## 
##     filter, lag

## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

パッケージdplyrを読み込むと複数の関数と競合を起こすことを伝えるメッセージが出力されます。具体的には、statsパッケージのfilter()log()、baseパッケージの4つの関数です。この段階でfilter()を実行すると、呼び出されるのはdplyr::filter()です。

# 呼び出されるのはdplyr::filter()なのでエラーとなる
x <- 1:100
filter(x, rep(1, 3))
# Error in UseMethod("filter_") : no applicable method for 'filter_' applied to an object of class "c('integer', 'numeric')"
# 出力は省略します
filter(mtcars, cyl == 8)

マスクされたstatsパッケージの関数filter()を実行するには、二重コロン演算子::)を使ってパッケージ名と関数名による名前空間を指定した参照をする必要があります。

x <- 1:100
stats::filter(x, rep(1, 3))
## Time Series:
## Start = 1 
## End = 100 
## Frequency = 1 
##   [1]  NA   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51
##  [18]  54  57  60  63  66  69  72  75  78  81  84  87  90  93  96  99 102
##  [35] 105 108 111 114 117 120 123 126 129 132 135 138 141 144 147 150 153
##  [52] 156 159 162 165 168 171 174 177 180 183 186 189 192 195 198 201 204
##  [69] 207 210 213 216 219 222 225 228 231 234 237 240 243 246 249 252 255
##  [86] 258 261 264 267 270 273 276 279 282 285 288 291 294 297  NA

処理が止まってしまうのは困りますが、さらに厄介なのは関数が実行されてしまう時です。頻繁にある事ではないと思いますが、衝突により意図しない結果を導いてしまう恐れがあります。

この問題を回避するための一つの方法として、conflictedパッケージを紹介します。

また、この辺の詳しい話は下記の記事や関連するシリーズを参考すると良いです。

qiita.com

conflictedパッケージによる衝突の回避

conflictedパッケージはCRANに登録されています。ここではバージョン1.0.0を使います。開発版を利用する際はGitHubリポジトリを参照してください。

install.packages("conflicted") # 1.0.0
# 開発版
# remotes::install_github("r-lib/conflicted")

conflictedパッケージを読み込み、改めて衝突を起こしている関数、filter()を実行するとエラーになります。

library(conflicted)
filter(mtcars, cyl == 8)
# Error: [conflicted] `filter` found in 2 packages.
# Either pick the one you want with `::` 
# * dplyr::filter
# * stats::filter
# Or declare a preference with `conflict_prefer()`
# * conflict_prefer("filter", "dplyr")
# * conflict_prefer("filter", "stats")

表示されたメッセージには、名前空間の指定かconflict_prefer()を使った宣言をするかという指示が書かれています。最初の指摘に従い、コードを下記に書き換えると処理が通ります。

dplyr::filter(mtcars, cyl == 8)

続いて、もう一つの指示にあるconflict_prefer()の働きをみてみましょう。この関数は、衝突を起こす関数間の関係を事前に定義しておく事で衝突を回避するために使われます。

conflict_prefer(name = "filter", winner = "dplyr")
## [conflicted] Will prefer dplyr::filter over any other package
filter(mtcars, cyl == 8)
##     mpg cyl  disp  hp drat    wt  qsec vs am gear carb
## 1  18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
## 2  14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
## 3  16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
## 4  17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
## 5  15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3

現在の競合関係を確認する

読み込まれているパッケージのうち、どの関数が競合しているかはconflict_scout()で確認できます。

conflict_scout()
## 6 conflicts:
## * `filter`   : [dplyr]
## * `intersect`: [dplyr]
## * `lag`      : dplyr, stats
## * `setdiff`  : [dplyr]
## * `setequal` : [dplyr]
## * `union`    : [dplyr]

箇条書きで示され、競合するパッケージ名が書かれています。[]で囲まれたパッケージは競合関係を解消した状態を意味していてfilter()には標準ではdplyr::filter()が使われることを示します(filter()の競合関係の解消は先ほどconflict_prefer()で行った通りです)。

一方で依然としてlag()については衝突が残っているため、これを解消しない限り実行できません。

lag(1:10, 1)
# Error: [conflicted] `lag` found in 2 packages.
# Either pick the one you want with `::` 
# * dplyr::lag
# * stats::lag
# Or declare a preference with `conflict_prefer()`
# * conflict_prefer("lag", "dplyr")
# * conflict_prefer("lag", "stats")
dplyr::lag(1:10, 1)
##  [1] NA  1  2  3  4  5  6  7  8  9

# あるいは
# conflict_prefer(name = "lag", winner = "dplyr")
# lag(1:10, 1)

conflictedのREADMEには、パッケージ読み込みの直後に関係性をconflict_prefer()を宣言しておくことが推奨されています。ただ私としては、該当する部分だけを見た時にどのパッケージの関数なのかが一目でわかるように衝突の恐れがある関数については多少の苦労を惜しまずに名前空間の宣言をしておくことをお勧めします。

prefixで自動的に名前空間を宣言していく

すでに書かれたコードに名前空間の指定を行っていくのは手間です。そういう時にはprefixerパッケージを使うと、候補の中から該当するパッケージ名をGUI操作で選べるので便利です。

remotes::install_github("dreamRs/prefixer")

RStudioで対象のファイルを開いた状態でprefixer::prefixer()を実行すると、次の画像で示すようにShinyアプリケーションが起動します。

f:id:u_ribo:20180929100909g:plain

トラブルを減らすためにも衝突には気をつけましょう。Enjoy!