ほくそ笑む

R言語と統計解析について

R言語こぼれ話(2) tryCatch はなんでもつかめる

R言語を使う上で知っていても知らなくても特に問題にならないようなこぼれ話をしていくシリーズ、第2回は tryCatch() の話です。

tryCatch() は主にエラー処理に使われる関数です。 例えば、ファイルを読み込もうとしたときに、そのファイルが存在しない場合は警告が発生します。 警告が発生したとき、ファイルの読み込みを中断し、その警告が持つメッセージを表示するには次のようにします。

tryCatch({
  read.csv("hogehoge.csv")
}, warning = function(w) {
  print(w$message)
})
[1] "cannot open file 'hogehoge.csv': No such file or directory"

一般に、なんらかの処理中にエラーが発生した場合に、別の処理を実行するには次のように書きます。

tryCatch({
  # なんらかの処理
}, warning = function(w) {
  # 処理中に警告が発生したときの処理
}, error = function(e) {
  # 処理中にエラーが発生したときの処理
}, finally = {
  # 後処理
})

つまり、処理の実行を試してみて (try) 、警告やエラーなどが発生した場合にはそれをつかんで (catch)、別の処理を実行する、というわけですね。

この「つかむ部分」に書けるものにはどんなものがあるでしょうか? Rがデフォルトで提供しているのは次の5つです。

  • error(エラー)
  • warning(警告)
  • message(メッセージ)
  • interrupt(割り込み)
  • condition(上記すべてをキャッチできる特別なやつ)

ふつうのRユーザーであれば、error, warning, message の3つと、後処理を書くために finally を知っていれば十分だと思います。

しかし、この「つかむ部分」、実は「なんでもつかめる」ことはあまり知られていないように思います。

tryCatch() はなんでもつかめる

tryCatch() は実はなんでもつかめます。

tryCatch() に自分の好きなものをつかませるためには、カスタムエラーを作成します。 ここでは例として hoge をつかませるためのカスタムエラーを作成しましょう。 カスタムエラーの作成方法は、message を持つ list オブジェクトを作成し、hoge, error, condition をクラスとして設定するだけです。

# カスタムエラーを作成する
my_error <- list(message = "ほげほげ")
class(my_error) <- c("hoge", "error", "condition")

エラーを発生させる関数 stop() を使って、このカスタムエラーを発生させると、tryCatch() で hoge をつかむことができます。

tryCatch({
  stop(my_error)
}, hoge = function(e) {
  "hogeをキャッチしたよ"
})
[1] "hogeをキャッチしたよ"

このように、hoge でも fuga でも piyo でも、なんでも思い通りに tryCatch() につかませることができるというわけです。

ちなみに、rlang パッケージにはカスタムエラーを作成する便利な関数があります。次のコードは上記と同じ動きをします。

my_error <- rlang::error_cnd("hoge", message = "ほげほげ")

tryCatch({
  rlang::cnd_signal(my_error)
}, hoge = function(e) {
  "hogeをキャッチしたよ"
})

何に使えるのか?

tryCatch() はなんでも好きなものをつかめることが分かりました。 では、これが何の役に立つのか? というのは難しいところです。 なかなか使いどころが思いつきません。 けっこうな無駄知識だと思います。

ただ、1つ思いつくのは、エラー処理の細分化に使えそうです。

例えば、ファイルが存在しない場合とそれ以外の場合で、エラー処理を変えたいとします。 Rのエラーはメッセージしか持たないので、メッセージの内容によってエラー処理を分けます。

file_name <- "piyopiyo.txt"
tryCatch({
  if (!file.exists(file_name)) {
    stop("File not found")
  } else {
    message("ファイル見つけてえらい")
  }
}, error = function(e) {
  if (e$message == "File not found") {
    message(file_name, "は見つかりませんでした")
  } else {
    message(e)
  }
})

これだと、メッセージの文言を変えるたびにエラー処理のコードも変える必要があります。

そこで、ファイルが存在しない場合に対応したカスタムエラーを作成すると次のように書けます。

fnf_error <- rlang::error_cnd("file_not_found", message = "File not found")

file_name <- "piyopiyo.txt"
tryCatch({
  if (!file.exists(file_name)) {
    rlang::cnd_signal(fnf_error)
  } else {
    message("ファイル見つけてえらい")
  }
}, file_not_found = function(e) {
  message(file_name, "は見つかりませんでした")
}, error = function(e) {
  message(e)
})

こうすることによって、エラーメッセージの変更は、エラー処理のコードに影響しなくなりました。

地味な改善ですが、こっちの方が私は好みです。

参考

カスタムエラーについては Hadley Wickham の "Advanced R (Second Edition)" が詳しいです。

adv-r.hadley.nz

こちらは上記を日本語訳した書籍です。

しかし、この書籍は第一版の日本語訳なので、内容が古い部分もあります。

yutannihilation さんと atusy さんあたりが主導して第二版の翻訳をやってくれたらなーとか勝手に思っております。

enjoy!