Kengo Nagashima



eval + parse + text vs. eval + substitute + do.call

R で実行したいコードが character 型のオブジェクトとして格納されている場合、eval + parse + text などを使って実行する事があります。 例えば、添え字をインクリメントして x1, x2, ..., という連番オブジェクトを生成する場合などに利用されていると思います。
各所 R コミュニティでは eval + parse + text なんて使うのはダメだ!という話をよく見かけます。 使ってはいけない理由についてはクリアに理解できなかったのですが、計算速度には違いがあるという事が指摘されているようです。
そこで、非推奨の eval + parse + text と推奨されている eval + substitute + do.call のシンプルな例を示した上で、計算速度の違いについて検討したいと思います。

eval + parse + text の例

シンプルな eval + parse + text の例です。
テキストで入力されている各コマンドを、paste 関数を用いて結合し、eval(parse(text = command)) で実行します。
とても直感的で書きやすいです。複数の関数を適用していますが、R に慣れている人であればコマンドを並べていくだけなので、特に悩むところも無いと思います。

strDataName  <- "x"
strFuncName1 <- "mean"
strFuncName2 <- "rnorm"
strDataValue <- "2"

command <- paste(
  strDataName, " <- ",
  strFuncName1, "(", strFuncName2, "(", strDataValue, "))",
  sep = ""
)
eval(parse(text = command), envir = .GlobalEnv)
print(x)

eval + substitute + do.call の例

シンプルな eval + substitute + do.call の例です。
複数の関数を適用する場合は若干面倒に見えますが、結局のところ適用したい関数に応じた処理が必要となるところは eval + parse + text の場合と同じで、関数の使い方や引数名などを知っている必要があります。
substitute(R の命令文, 環境 or オブジェクトリスト) 関数と do.call(関数名, 引数リスト) 関数は内部で実行したい関数の引数を "list" 型のオブジェクトで渡す必要があるため、各所に list() が入っています。

args <- list(
  data = "x",
  fun1 = "mean",
  fun2 = "rnorm",
  arg1 = "2"
)

eval(
  substitute(
    data <- do.call(fun1, list(x = do.call(fun2, list(n = arg1)))),
    args
  ), 
  envir = .GlobalEnv
)
print(x)

実行速度の比較

では、二つの方法を rbenchmark パッケージを使って比較してみましょう。

require(rbenchmark)

evalParse <- function() {

  set.seed(111207)

  strDataName  <- "x"
  strFuncName1 <- "mean"
  strFuncName2 <- "rnorm"
  strDataValue <- "2"

  command <- paste(
    strDataName, " <- ",
    strFuncName1, "(", strFuncName2, "(", strDataValue, "))",
    sep = ""
  )
  eval(parse(text = command), envir = .GlobalEnv)

}

evalSubstitute <- function() {

  set.seed(111207)

  args <- list(
    data = "x",
    fun1 = "mean",
    fun2 = "rnorm",
    arg1 = "2"
  )

  eval(
    substitute(
      data <- do.call(fun1, list(x = do.call(fun2, list(n = arg1)))),
      args
    ), 
    envir = .GlobalEnv
  )

}

res <- benchmark(
  evalSubstitute(),
  evalParse(),
  columns = c("test", "replications", "elapsed", "relative", "user.self", "sys.self"),
  order = "relative",
  replications = 10000)

print(res)

これを実行すると、以下のような結果が得られました。

> print(res)
              test replications elapsed relative user.self sys.self
1 evalSubstitute()        10000    0.41 1.000000      0.41        0
2      evalParse()        10000    1.04 2.536585      1.03        0

上の結果を見ると、計算速度は eval + substitute + do.call の方が速い事が分かりました。
eval + parse + text 形式は直感的で書きやすいですが、計算速度という面から見ればできる限り eval + substitute + do.call 形式で書いた方が良いでしょう。

履歴