R

トップページ
Google
WWWを検索
サイト内を検索

Rを本格的に使っている人々は、RStudioやVSC/R Extension/R Debuggerのような統合開発環境を利用していおり、GUIを使ったデバッグに慣れていると思いますが、対話型インターフェイスのCUIで使えるデバッグ関数群も知っておくと便利なことがあります1。存在ぐらいは覚えておきましょう。

1 ブレークポイントを仕掛ける

プログラム中のある箇所の状態やある部分の処理フローの流れを追いかけたいときは、ブレークポイントを仕掛けるのが定番です。コード中にbrowser関数を埋め込む方法、tracedebugdebugonceで関数にブレークポイントを仕掛ける方法の3種類があります。手軽なのはbrowser()と書いてしまうことなので、まずはそこから慣れていきましょう。

1.1 browser関数

最初に覚えるべき関数がbrowser関数です。実行すると、プログラムの実行がそこで中断され、変数の値を確認したり、変数に値を代入したり、一行づつプログラムを実行していったり(ステップ実行)できる、デバッグモードに入ることができます。 例えば、

(function(x){
    browser()
    x <- x + 1
    print(x)
})(1)

と埋め込めば、関数内でxが定義されたところで実行が中断されます。挿入したところがブレークポイントになるわけです。 中断された後は、

コマンド 挙動
n ステップ・オーバー(一文づつ実行,関数の中は追いかけない)
s ステップ・イン(一文づつ実行,関数の中も追いかける)
c 再開(デバッグモードを抜けて実行を再開)
f 再開(ステップ実行で入った関数やループを抜ける)
Q 終了(プログラムの実行自体を終了)
where トレースバックを表示
help コマンドの説明を表示

と言うコマンドを入れるか、Rの命令を入力します。ENTERキーは、options(browserNLdisabled=TRUE)で無効にできますが、デフォルトでは最初は再開で、ステップ・オーバー実行後はステップ・オーバー、ステップ・イン実行後はステップ・インになります。なお、cfは実用上の違いは無い2こt、? browserで出てくるドキュメントにあるrコマンドは機能しない3ので注意してください。また、デバッグモードに入ったとき、次に実行する文がdebug:の後に表示されていないときのnsは無視されます。

1.1.1 browser関数を使ってみる

習うより慣れろ系なので、上述の例を実際に動かして見ましょう。

Called from: (function(x) {
    browser()
    x <- x + 1
    print(x)
})(1)
Browse[1]> 

とコマンド・プロンプトが表示されるので、コマンドか命令を入れていきます。ステップ・オーバーしていくと

Browse[1]> n
 #3 の debug: x <- x + 1
Browse[2]> 
 #4 の debug: print(x)
Browse[2]> 
[1] 2

となり、ステップ・インしていくと

e[1]> s
 #3 の debug: x <- x + 1
Browse[2]> 
 #4 の debug: print(x)
Browse[2]> 
debugging in: print(x)
debug: UseMethod("print")
Browse[3]> 
debugging in: print.default(x)
debug: {
    args <- pairlist(digits = digits, quote = quote, na.print = na.print, 
        print.gap = print.gap, right = right, max = max, width = width, 
        useSource = useSource, ...)
    missings <- c(missing(digits), missing(quote), missing(na.print), 
        missing(print.gap), missing(right), missing(max), missing(width), 
        missing(useSource))
    .Internal(print.default(x, args, missings))
}

print関数の中まで追跡してくれます。ステップインした関数の中でfcを押すと、関数を抜けて、関数の次の文に飛びます。

変数名をいれれば値を確認できますし、代入演算子<-で変数に異常値を代入したり、フォーマット付きで値を確認したり、plotしたりと、Rの命令が使えるのは便利です。コンパイル言語のデバッガーと比較して融通が利きます。

1.1.2 デバッグモードに入る条件を引数exprで指定する

条件付きにデバッグモードに入れます。以下のように引数exprを指定したら、x <= 0FALSEなのでデバッグモードに入りません。

(function(x){
    browser(expr = x <= 0)
    x <- x + 1
    print(x)
})(1)

1.1.3 引数skipCalls

ほかにtextconditionskipCallsを指定することができます。

(function(x){
    browser(text = "example", condition = 1, skipCalls=2)
    x <- x + 1
    print(x)
})(1)

上を実行するとskipCalls=2の効果でCalled from:が変わっているのが分かります。

1.1.4 引数textcondition

複数のブレークポイントを管理する統合開発環境用だそうですが、textconditionで指定した値をbrowserText関数とbrowserCondition関数でとることができます。

Called from: top level 
Browse[1]> browserText()
[1] "example"
Browse[1]> browserCondition()
[1] 1
Browse[1]> 

1.2 trace関数

trace関数とbrowser関数を組み合わせて、関数にブレークポイントを仕掛けることができます。

fn <- function(x){
    z <- -1/2 + sqrt(3)/2i
    z^x
}
trace(fn, browser, at=3)
fn(3)

この例では、fnの3行目になるz^xの実行前にブレークポイントを仕掛けています。

デバッグモードに入ったあとは、browser関数を呼んでいるだけに、browser関数と同様です。

芸達者な関数で、exitで関数終了時に呼び出す関数を指定できるほか4print=FALSEで表示を抑制したり、edit=TRUEでtraceしている関数を実行前にテキストエディタを開いて編集したりできます。

総称関数(e.g. summary)をデバッグする場合は、引数signatureでクラス名を指定します。また、パッケージ内の関数のように現在の環境と異なる環境にある関数の場合は引数whereで環境を指定します。

untrace(fn)

なお、デバッグ完了後は、次回実行時もデバッグモードに入らないように、untraceする必要があります。untraceにもtraceにつけたsignaturewhereは必要です。

1.2.1 tracingState関数

tracingState(FALSE)とすると、あらゆる箇所のtraceが基本的に動かなくなります5tracingState(TRUE)で有効になります。

1.3 debug, debugonce関数

debug関数とdebugonce関数はtrace関数に似た用途に用います。debugを使うとundebugをしないといけないので、1度だけデバッグモードに入れるdebugonceを使ってみましょう。

debugonce(fn)
fn(3)

browser関数と同様の引数textconditionと、trace関数と同様のsignatureをもてます。isdebuggedでブレークポイントが仕掛けられているか確認することができ、debuggingState(FALSE)で無効にすることができます。もちろんTRUEを指定すれば有効化です。

2 エラーや警告が出た状態を把握する

コードを書いているときはエラーが出てもどこが問題かだいたい想像がついているわけですが、データセットを変えるなどしてエラーを出した場合などで、直観的に状況を把握できないときがあります。

2.1 traceback関数

コードが複雑になってくると、例えばsource関数で外部ファイルを実行するとき、エラーが出て停止したときにどこで停止したか分からないときがあります。そういうときにtraceback()と入力すると、直前のエラーが生じた関数とその中の位置、エラーが生じた関数を呼んでいる関数を表示してくれます。

assign(".Traceback", NULL, "package:base") # 説明のためframes/stackをクリア
fn1 <- function(x) fn2(x)
fn2 <- function(x) fn3(x)
fn3 <- function(x) stop("an example error!")
fn1(1)
traceback()

入れ籠構造になっているわけですが、しっかり関数名と行番号を追いかけてくれます。

4: stop("an example error!") at #1
3: fn3(x) at #1
2: fn2(x) at #1
1: fn1(1)

2.1.1 警告でも中断する

warningでも中断してtraceback()したいときは、options(warn = 2)としてください。デフォルトはoptions(warn = 0)です。

2.2 options(error = recover)

グローバルオプションを変更することで、エラーが出る直前でデバッグモードに入って状態を確認できます。

fn2 <- function(x){
    print("fn2 begin")
    stop("an example error!")
    print("fn2 end")
}

fn1 <- function(x){
    print("fn1 begin")
    fn2(x)
    print("fn1 end")
}

options(error = recover)
fn1(1)

エラーになった関数と、それを呼び出している関数のどちらかで起動できますが、ステップ実行をすると必然的にすぐエラーになって止まります。

> fn1(1)
[1] "fn1 begin"
[1] "fn2 begin"
 fn2(x) でエラー: an example error!

Enter a frame number, or 0 to exit

1: fn1(1)
2: #3: fn2(x)

Selection:

0を入力して終了、fn1(1)を呼ぶところ、fn2(x)の3行目と選択肢が出ていますね。 options(error = NULL)で解除できます。

2.3 options(error = dump.frames)debugger(last.dump)

グローバルオプションを変更して、ダンプ情報を取得することで、エラーが出た直後の状態を確認することができます。

fn2 <- function(x){
    print("fn2 begin")
    stop("an example error!")
    print("fn2 end")
}

fn1 <- function(x){
    print("fn1 begin")
    fn2(x)
    print("fn1 end")
}

options(error = dump.frames)
fn1(1)

使ったことがなかったので、こんな仕掛けが・・・と驚いています。

[1] "fn1 begin"
[1] "fn2 begin"
 fn2(x) でエラー: an example error!
> ls()
[1] "fn1"       "fn2"       "last.dump"
> debugger(last.dump)
 メッセージ:    fn2(x) でエラー: an example error!
 利用可能な環境は以下の呼び出しを含んでいました: 
1: fn1(1)
2: #3: fn2(x)
3: #3: stop("an example error!")

 環境ナンバーを入力してください。0 を入力すると終了します   Selection: 0

3 まとめ

統合開発環境を使わない/使えないことが前提ですが、browser()を挟んでデバッグモードに入るだけで、だいぶ効率が上がると思います。後はtraceback()で十分で、かなりはまらないと細かく引数やオプションをつけていくことはないと思いますが、何かの不幸で統合開発環境が使えないときにどん詰まりのときは、気分転換に試してみましょう。


  1. 例えば、R Markdownのファイルにブレークポイントを設定できない統合開発環境でも、R Markdownの中のRのコードの中にbrowser()と書いておけば、そこでデバッグモードに入ることができます。↩︎

  2. cfを押した後、ループや関数が終了する前にbrowser()の行が来た場合に、cの場合はデバッグモードに入りなおすのでスタックトレースが表示される一方で、fは入ったままなので表示されないと言う違いはなくもないのですが。↩︎

  3. ソースコードを確認したところ未実装。↩︎

  4. 例えばrecoverを指定すると、終了するか継続するか選択できます。↩︎

  5. edit=TRUEをつけておくと、なぜか編集は実行されます。コードは実行されません。↩︎