Menu

R のよくあるエラーとデバッグ方法

R の定番のエラーメッセージ — object not found、could not find function、non-numeric argument など — を読み解く解読表に加え、tryCatch、traceback()、そして正直な print デバッグを紹介します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

R のエラーメッセージを読む

R のエラーには 2 つの部分があり、どちらも役に立ちます。

Error in "10" + 5 : non-numeric argument to binary operator

Error in の後に来るのが 呼び出し で、失敗したコードそのもの ("10" + 5) です。コロンの後に来るのが 条件 で、何がうまくいかなかったか (non-numeric argument to binary operator) を表します。まず呼び出しを読みましょう。それが どこで を教えてくれますし、引用されたコードの中に問題がすでに見えていることも非常に多いのです。次に条件を読んで なぜ を知ります。

素早くデバッグできる人と苦しむ人を分けるのは 2 つの習慣です。第一に、メッセージを実際に読むこと。R のメッセージはたいてい正確で、ただ言葉数が少ないだけです。第二に、最後ではなく 最初の エラーをデバッグすること。スクリプトの早い段階での 1 つの失敗は、下流で「object not found」の山を引き起こしますが、それらは元の問題を直すとすべて消えます。このページの残りは、最もよく出会うメッセージの解読表であり、その後に、読むだけでは足りないときの道具を紹介します。

名前のエラー: 見つからない

Error: object 'total' not found — R は知っているすべての環境を探しましたが、その名前の変数はありませんでした。ほぼすべてのケースは次の 3 つの原因で説明できます。

  • タイプミス(大文字小文字を含む)。 R は大文字小文字を区別します。TotaltotalTOTAL は 3 つの異なる名前であり、R はどれを意図したのか推測してくれません。
  • 定義の行が実行されていない。 スクリプトには total <- sum(x) と書いたものの、このセッションでは一度も実行していない。R を再起動した後によくあることで、スクリプトファイルにはその行が見えていても、セッションはそれを見たことがないのです。スクリプトを先頭から実行しましょう。
  • 環境が違う。 関数の内部で作られた変数は、その呼び出しの内部で生まれて死にます。関数の外でそれを使うのは、もはや存在しないものを求めることです。代わりに値を返しましょう。

Error: could not find function "read_excel" — 同じ考え方ですが、関数名についてです。10 回のうち 9 回は、インストール済みだがこのセッションで 読み込んで いないパッケージの中に、その関数があります。

library(readxl)          # the fix: loading is per-session, installing is per-machine
df <- read_excel("data.xlsx")

library(readxl) 自体がエラーになるなら、パッケージがインストールされていません。まず install.packages("readxl") です。そして関数が base R のものなら、タイプミスしています(lenght() は誰もが通る通過儀礼です)。

構文エラー: unexpected symbol

Error: unexpected symbol in "..."(およびその親戚の unexpected ')'unexpected string constant)は、R がコードを解析すらできなかったことを意味します。メッセージが指すのは R が 気づいた 場所で、実際の誤りの位置より後であることがよくあります。よくある容疑者はこちらです。

mean(x na.rm = TRUE)        # missing comma - should be mean(x, na.rm = TRUE)

name <- "Ada                # unclosed quote - swallows the following lines
total <- sum(c(1, 2, 3)     # unclosed paren - the error fires lines later

指摘された行が無実に見えるとき、誤りはほぼ必ずその にあります。ファイルの前の方にある閉じられていない引用符、括弧、波括弧です。対応する組を強調表示するコードエディタなら数秒で見つけられます。

型と添字のエラーを解読する

non-numeric argument to binary operator — 数値でないものに対して計算をしました。たいていは文字列として届いた数値です(データ型で扱ったように、インポートが定番の発生源で、単語がひとつ紛れ込んだ列は文字列として読み込まれます)。壊れている版はこちらです。

x <- "10"
x + 5
# Error in x + 5 : non-numeric argument to binary operator

そして修正版は、変換してから計算します。

subscript out of bounds[[ ]] を使って、要素数が n 未満のものに対して位置 n を要求しました。

scores <- list(ada = 92, grace = 88)
scores[[3]]
# Error in scores[[3]] : subscript out of bounds

添字を付ける前に length() を確認するか、もっと良いのは名前で問い合わせること (scores[["grace"]]) です。そうすれば並べ替えで壊れることはありません。非対称性に注意してください。単一の角括弧はより寛容で、ベクトルに対する範囲外の [ ] はエラーにならず静かに NA を返します。これは騒がしいバグを静かなバグと引き換えにしているのです。

$ operator is invalid for atomic vectors$ はリストとデータフレームのものです。名前付きベクトルには角括弧を使います。

[[ ]] は裸の値を返し、[ ] は名前を付けたまま返します。)このエラーは、上流の何かがデータフレームだと思っていた場所でベクトルを返したことを意味することが多いので、演算子を差し替えるだけでなく、その前提を確認しに行きましょう。

argument is of length zeroif () が中身のない条件を受け取りました。ほぼ必ず、欠けているリスト要素や何も返さない関数から紛れ込んだ NULL です。

threshold <- NULL
if (threshold > 5) print("big")
# Error in if (threshold > 5) print("big") : argument is of length zero

チェックを守りましょう。そして && は答えが判明した時点で評価を止めるので、比較が NULL に対して実行されることはありません。

(関連するエラー missing value where TRUE/FALSE needed は、NULL の代わりに NA で起きる同じ失敗です。そこでの防御は is.na() で、欠測値で扱っています。)

replacement has length zero — 同じ病気の代入版です。x[2] <- numeric(0) は、1 つのスロットをゼロ個の値で埋めようとしています。右辺を生み出したものが空で返ってきたということなので、代入ではなくそちらをデバッグしましょう。

警告はエラーではない — それこそが危険

エラーは実行を止めますが、警告は止めません。R は計算を完了し、結果を渡し、その後で留保事項に触れます。その結果は問題ないこともあれば、静かに間違っていることもあります。

どちらの行も完了します。1 つ目は短い方のベクトルをリサイクルし、longer object length is not a multiple of shorter object length と警告します。長さ 2 のベクトルを長さ 3 に対してリサイクルするのは、まず誰の意図でもありません。2 つ目は NAs introduced by coercion と警告し、穴の空いたベクトルを返します。これは下流のあらゆる mean()NA を返させます。どちらの警告も、読み飛ばすノイズではなく、調査すべきバグとして扱いましょう。スクリプトでは options(warn = 2) でその姿勢を強制でき、すべての警告がエラーに格上げされるので、何も忍び込めなくなります。

tryCatch() で失敗を処理する

ときには失敗が 想定内 のこともあります。何百ものファイルが入ったフォルダの中の 1 つが壊れている、1 行だけ不正である、といった場合です。そのときは、死ぬのではなく処理して先に進みたいものです。tryCatch() は危険な式をハンドラで包みます。

仕組みはこうです。主ブロックが成功すればその値が結果になります。エラーになれば代わりに error = のハンドラが実行され、その 戻り値(ここでは NA)が結果になります。スクリプトは動き続けます。conditionMessage(e) はログ用に元のメッセージを回収します。finally = は成功でも失敗でもどちらの場合にも実行されるので、コネクションを閉じるといった後片付けはここに置きます。上の出力では、各結果の前にそれが表示されるのが確認できます。

warning = のハンドラもあります。tryCatch(as.numeric(x), warning = function(w) NA) は、前節の型変換の警告を見逃すのではなく捕まえます。ひとつ注意があります。何もログに残さずに フォールバックを返すハンドラは、失敗を処理しているのではなく隠しているだけです。必ず conditionMessage() を記録しましょう。将来のあなたに必要になります。

失敗箇所を特定する: traceback()、browser()、そして正直な出力

入れ子になった関数呼び出しの奥からエラーが来るとき、メッセージだけでは どの 呼び出しの連鎖がそこに至ったのかは分かりません。エラーの直後に traceback() を実行しましょう。

f <- function(x) g(x)
g <- function(x) stop("boom")

f(1)
# Error in g(x) : boom
traceback()
# 2: g(x)
# 1: f(1)

失敗した瞬間のコールスタックを出力します。一端があなたの呼び出し、もう一端が失敗した呼び出しです。次に実行するものでなければなりません。別のエラーが起きるとスタックは破棄されます。

実行中の様子を見るには、browser() を仕込んだ場所で実行が一時停止し、関数の 内側 の対話プロンプトに落とされます。変数を調べ、n でステップ実行し、c で続行し、Q で抜けます。debug(f) はコードを編集せずに同じことをします。f に印を付け、次の呼び出しでブラウザが開くようにします(undebug(f) で解除)。

そして、誰も学会のスライドには載せないけれど誰もが使うテクニックがあります。出力です。要所に print()cat() をばらまき、実行し、現実が期待と合わなくなる箇所を見つけます。これは正当で、速く、スクリプトではしばしば最も実用的な道具です。その最良の相棒が str() で、おそらく R のエラーの半分の裏にある問い — 「このオブジェクトは実際 なのか?」 — に答えてくれます。

コンパクトな 1 つの出力で分かります。これはリストで、要素は 2 つ、1 つは整数ベクトル、もう 1 つはこれらの列と型を持つデータフレームです。$ が失敗したり計算がおかしな振る舞いをしたりしたときは、理屈をこねる前にオブジェクトを str() しましょう。答えはたいていそこにあります(「…ああ、これは私のデータフレームを 含む 長さ 1 のリストだったのか」)。

この章のまとめ

  • メッセージを読みましょう。Error in の後の部分がどこで、コロンの後の部分がなぜを表します。最も騒がしいエラーではなく、最初のエラーを直しましょう。
  • object not found はタイプミス、未実行のコード、または関数の内部にしか存在しなかった変数です。could not find function は、ほぼ必ず library() の呼び出し忘れです。
  • unexpected symbol は文法が解析できないという意味で、本当の誤りは指摘された箇所より前にある閉じられていない引用符や括弧であることがよくあります。
  • 型と添字の定番 — non-numeric argumentsubscript out of bounds、アトミックベクトルへの $argument is of length zero — はいずれも、オブジェクトが何であるかについての誤った前提を指し示します。str() はその前提を 1 回の呼び出しで確認します。
  • 警告は実行を止めません。まさにそれゆえに注意に値します。結果が静かに間違っている可能性があるのです。
  • tryCatch(error =, warning =, finally =) は想定内の失敗を死なずに処理します(必ず conditionMessage() をログに残しましょう)。エラー直後の traceback() は呼び出しの連鎖を示し、browser()/debug() はその内部で一時停止し、print() デバッグは正真正銘の仕事です。

次は、エラーですらない多くの「エラー」がたどり着く、3 文字の値 — NA と、欠測値が R の計算するすべての中をどう流れていくかです。

よくある質問

R の "object 'x' not found" は何を意味しますか?

R が x という名前の変数を探しましたが、検索したどの環境にもその名前が存在しなかったということです。原因は可能性の高い順に、名前のタイプミス(R は大文字小文字を区別します。Totaltotal ではありません)、x を作る行がこのセッションでまだ実行されていない、あるいは x が関数の内部で作られたのにその外側で使おうとしている、です。

R の "could not find function" は何を意味しますか?

その関数は、このセッションで読み込んでいないパッケージの中にあります。パッケージのインストールはマシンごとに 1 回、library() はセッションごとに 1 回であり、library() の呼び出し忘れが通常の原因です。library() 自体が失敗するなら、そのパッケージはインストールされていません。関数名のタイプミスでも同じエラーが出ます。

R で tryCatch を使ってエラーを処理するにはどうしますか?

危険な式を包みます: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup)。expr が失敗すると、スクリプトが死ぬ代わりに対応するハンドラが実行され、そのハンドラが返した値が結果になります。ハンドラの中で conditionMessage(e) を使うと、ログ用に元のメッセージが取得できます。

R の traceback() は何をしますか?

エラーの直後に実行すると、traceback() はエラーが発生した時点で有効だった関数呼び出しの連鎖を出力します。一端があなたの呼び出し、もう一端が失敗した呼び出しです。何かを修正するわけではありませんが、どこを見ればよいかを教えてくれます。入れ子になった関数の奥からエラーが来た場合、それが戦いの大半です。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める