LoginSignup
14
15

More than 5 years have passed since last update.

[翻訳]lazyeval vignette: 非標準評価

Last updated at Posted at 2016-09-18

この文書は Hadley Wickham によるRパッケージ lazyeval (version 0.2.0) のビネット "Non-standard evaluation" の日本語訳です.
文中の注は原著者によるものと訳者によるものがあり,訳者によるものには[訳注]マークを付しています1

License: GPL-3


この文書では,Rで非標準評価(non-standard evaluation, NSE)を行うための,原理に則ったツールであるlazyevalについて述べます.もしあなたがdplyrやggplot22といったパッケージを用いてプログラミングをしたい場合,あるいは自分のパッケージで遅延評価を利用するための原理に則った方法が欲しい場合には,このビネットを読むべきです.非標準評価はその名の通り,特殊なことを行うために標準評価(standard evaluation, SE)のルールから逸脱します.NSEの一般的な用途には以下の3つがあります.

  1. ラベル . 関数に与えた値ではなく,表現式(expression)を使ってラベルを付けると図表が分かりやすくなります.例として,次のプロットの軸ラベルに注目してください.

    par(mar = c(4.5, 4.5, 1, 0.5))
    grid <- seq(0, 2 * pi, length = 100)
    plot(grid, sin(grid), type = "l")
    

    unnamed-chunk-2-1.png

  2. 非標準スコープ によってオブジェクトが現在の環境以外の場所で検索されます.たとえばbase Rには,オブジェクトを現在の環境より先にデータフレーム(またはリスト)の中で探すwith()subset()transform()があります.

    df <- data.frame(x = c(1, 5, 4, 2, 3), y = c(2, 1, 5, 4, 3))
    
    with(df, mean(x))
    #> [1] 3
    subset(df, x == y)
    #>   x y
    #> 5 3 3
    transform(df, z = x + y)
    #>   x y z
    #> 1 1 2 3
    #> 2 5 1 6
    #> 3 4 5 9
    #> 4 2 4 6
    #> 5 3 3 6
    
  3. メタプログラミング はその他のNSEの用途(bquote()library()等)を何でも含む用語です.なぜメタプログラミングと呼ぶかというと,未評価のコードに対して何らかの方法で計算を行うからです.

この文書は,大まかにはこれら3種類の非標準評価の用途に沿って構成されています.主な違いはラベルの後でフォーミュラ(formula)3についてさらに学ぶために寄り道をするところです.おそらく線形モデル(たとえばlm(mpg ~ displ, data = mtcars))のフォーミュラにはなじみがあるでしょうが,フォーミュラはモデリングのための単なるツール以上のものであり,未評価の表現式を捕捉(capture)するための汎用的な方法なのです.

ここで推奨する方法は著者が以前推奨していたものとは大きく異なりますが,新しい方法のほうが正しく,再び本質的に変更を加える必要は生じないだろうと確信しています4.現在のツールは以前には困難だった多くの実用的な問題を容易に解決できますし,伝統ある理論に立脚しているものです.

ラベル

base Rで引数をラベルにする典型的なやり方はdeparse(substitute(x))を使うことです.

my_label <- function(x) deparse(substitute(x))
my_label(x + y)
#> [1] "x + y"

この方法には潜在的な問題が2つあります.

  1. 長い表現式の中には,deparse()によって長さが1を超える文字ベクトルが生成されるものがあります.

    my_label({
      a + b
      c + d
    })
    #> [1] "{"         "    a + b" "    c + d" "}"
    
  2. substitute()は1つ上の環境しか見に行かないので,関数を直接呼ばないと元のラベルが失われてしまいます.

    my_label2 <- function(x) my_label(x)
    my_label2(a + b)
    #> [1] "x"
    

lazyeval::expr_text()によって両方の問題を解決することができます.

my_label <- function(x) expr_text(x)
my_label2 <- function(x) my_label(x)

my_label({
  a + b
  c + d
})
#> [1] "{\n    a + b\n    c + d\n}"
my_label2(a + b)
#> [1] "a + b"

expr_text()には2つの変種があります.

  • expr_find()は引数の元々の表現式を見つけます.substitute()に似た動作をしますが,プロミスの連鎖を辿って最初の表現式まで戻っていきます.これはしばしばメタプログラミングに役立ちます.
  • expr_label()はユーザへのメッセージ用にデザインされたラベルを生成するようにexpr_text()をカスタマイズしたものです.

    expr_label(x)
    #> [1] "`x`"
    expr_label(a + b + c)
    #> [1] "`a + b + c`"
    expr_label(foo({
      x + y
    }))
    #> [1] "`foo(...)`"
    

練習問題

  1. plot()はx軸とy軸のラベルを生成するのにdeparse(substitute(x))を使っています.plot()が不適切なラベルを表示するような入力を作成してください.expr_label()を使ってxlimylimを計算するplot()のラッパー関数を書いてください.

  2. 引数がnumericでない場合に有用なエラーメッセージを出して停止する,mean()の簡単な実装を作成してください.

    x <- c("a", "b", "c")
    my_mean(x)
    #> Error: `x` is a not a numeric vector.
    my_mean(x == "a")
    #> Error: `x == "a"` is not a numeric vector.
    my_mean("a")
    #> Error: "a" is not a numeric vector.
    
  3. expr_text()のソースコードを読み,どのように機能しているか,またdeparse()のどの引数を追加で使っているのか調べてください.

フォーミュラ

非標準スコープはおそらくNSEのツールで最も有用なものですが,確固とした使用方法について語れるようになる前に,フォーミュラの話に寄り道しておく必要があります.フォーミュラは線形モデルで馴染み深いツールですが,その有用性はモデリングに限られたものではありません.実際には,フォーミュラは以下の2つのものを捕捉するので,強力で汎用的なツールとなっているのです.

  1. 未評価の表現式

  2. 表現式が作成されたコンテクスト(環境)

~は「このコードの意味を,今すぐに評価はせずに捕捉したい」と言うことを可能にする一文字なのです.このことから,フォーミュラはクオート(quoting)演算子だと考えることもできます.

フォーミュラの定義

専門的に言うと,フォーミュラとはlanguageオブジェクト(すなわち未評価の表現式)であって,クラスがformulaであり,属性の中に環境を保持しているものです.

f <- ~ x + y + z
typeof(f)
#> [1] "language"
attributes(f)
#> $class
#> [1] "formula"
#> 
#> $.Environment
#> <environment: R_GlobalEnv>

フォーミュラが両側か片側かによって,内部のオブジェクトの構造が微妙に異なります.

  • 片側フォーミュラの長さは2です

    length(f)
    #> [1] 2
    # 1番目の要素は常に~
    f[[1]]
    #> `~`
    # 二番目の要素は右辺
    f[[2]]
    #> x + y + z
    
  • 両側フォーミュラの長さは3です

    g <- y ~ x + z
    length(g)
    #> [1] 3
    # 1番目の要素はやはり~
    g[[1]]
    #> `~`
    # だが,2番目の要素は左辺
    g[[2]]
    #> y
    # そして,3番目の要素が右辺
    g[[3]]
    #> x + z
    

lazyevalはこの違いを捨象して,フォーミュラの両辺にアクセスするためのf_rhs()f_lhs()を提供します.さらにf_env()でフォーミュラの環境にもアクセスできます.

f_rhs(f)
#> x + y + z
f_lhs(f)
#> NULL
f_env(f)
#> <environment: R_GlobalEnv>

f_rhs(g)
#> x + z
f_lhs(g)
#> y
f_env(g)
#> <environment: R_GlobalEnv>

フォーミュラの評価

フォーミュラで表現式を捕捉し評価を遅延させておいて,後からf_eval()によって評価することができます.

f <- ~ 1 + 2 + 3
f
#> ~1 + 2 + 3
f_eval(f)
#> [1] 6

これにより,フォーミュラの作成を評価からきれいに分離して,フォーミュラを遅延評価のロバストな方法として使うことが可能になります.フォーミュラはコードとコンテクストを捕捉しているので,フォーミュラが作成されたのとは別の場所で評価された時にも正しい結果が得られます.次の例でadd_1000()の中のxが使われていることに注目してください.

x <- 1
add_1000 <- function(x) {
  ~ 1000 + x
}

add_1000(3)
#> ~1000 + x
#> <environment: 0x0000000008685f70>
f_eval(add_1000(3))
#> [1] 1003

フォーミュラを見ても,概して不明瞭な環境に重要な値が保存されているために,何が起きているのか理解するのが難しいことがあります.フォーミュラの中の名前を対応する値で置き換えるのにf_unwrap()が使えます.

f_unwrap(add_1000(3))
#> ~1000 + 3

非標準スコープ

f_eval()にはオプションとして2つ目の引数があります.フォーミュラの環境にある値を上書きするための名前付きリスト(またはデータフレーム)です.

y <- 100
f_eval(~ y)
#> [1] 100
f_eval(~ y, data = list(y = 10))
#> [1] 10

# 環境とdata引数の変数を混ぜて使うことが可能
f_eval(~ x + y, data = list(x = 10))
#> [1] 110
# 関数を与えることも可能
f_eval(~ f(y), data = list(f = function(x) x * 3))
#> [1] 300

これによって非標準スコープを実装することが非常に簡単になります.

f_eval(~ mean(cyl), data = mtcars)
#> [1] 6.1875

非標準スコープの問題点として,ちょっとした曖昧性を持ち込んでしまったことがあります.
たとえば,以下のコードでxmydataと環境のどちらからやって来るのでしょうか?

f_eval(~ x, data = mydata)

これはmydataxという名前の変数を持っているかどうか知らなければわかりません.この問題を克服するためにf_eval()は二つの代名詞を提供しています.

  • .dataはデータフレームに束縛されます
  • .envはフォーミュラの環境に束縛されます

既存の変数名と衝突する可能性をできる限り小さくするため,どちらも名前は.で始まるようになっています.

これらの代名詞を使えば,先程のフォーミュラを曖昧さがないように書き直すことができます.

mydata <- data.frame(x = 100, y = 1)
x <- 10

f_eval(~ .env$x, data = mydata)
#> [1] 10
f_eval(~ .data$x, data = mydata)
#> [1] 100

変数オブジェクトが存在しない場合は,有用なエラーメッセージが出ます.

f_eval(~ .env$z, data = mydata)
#> Error: Object 'z' not found in environment
f_eval(~ .data$z, data = mydata)
#> Error: Variable 'z' not found in data

アンクオート

f_eval()にはもう一つ有用なトリックが用意されています.アンクオート(unquoting)です.アンクオートによって,ユーザがフォーミュラの一部を与えることができる関数を書くことが可能になります.たとえば,次の関数では任意の列(あるいは列に対する任意の関数)の平均値を計算することができます.

df_mean <- function(df, variable) {
  f_eval(~ mean(uq(variable)), data = df)
}

df_mean(mtcars, ~ cyl)
#> [1] 6.1875
df_mean(mtcars, ~ disp * 0.01638)
#> [1] 3.779224
df_mean(mtcars, ~ sqrt(mpg))
#> [1] 4.43477

これがどのように機能しているか見るのに,f_eval()が内部で呼んでいるf_interp()が使えます(あなた自身のコードの中ではこの関数を呼ぶべきではありませんが,デバッグには有用です).鍵となるのはuq()です.uq()は第1の(そして唯一の)引数を評価して,その値をフォーミュラの中に挿入します.

variable <- ~cyl
f_interp(~ mean(uq(variable)))
#> ~mean(cyl)

variable <- ~ disp * 0.01638
f_interp(~ mean(uq(variable)))
#> ~mean(disp * 0.01638)

アンクオートによってコードの「テンプレート」を作成することが可能になります.テンプレートでは,表現式の大部分をあなたが書いておきながら,ユーザにも重要なコンポーネントを制御させることが可能です.呼ばれる関数を変更するためにuq()を使うことさえできます.

f <- ~ mean
f_interp(~ uq(f)(uq(variable)))
#> ~mean(disp * 0.01638)

uq()はフォーミュラの右辺だけを受け取ることに注意してください.これは与えたフォーミュラをそのまま呼び出し(call)に挿入することを難しくします.

formula <- y ~ x
f_interp(~ lm(uq(formula), data = df))
#> ~lm(x, data = df)

かわりに,右辺だけでなくフォーミュラ全体を用いるuqf()を使うことができます.

f_interp(~ lm(uqf(formula), data = df))
#> ~lm(y ~ x, data = df)

アンクオートは強力ですが,単一の引数を変更することしかできず,任意の数の引数をさらに追加することはできません.それをやるためには,「アンクオート・スプライス(unquote-splice)」すなわちuqs()がが必要になります.uqs()の第1の(そして唯一の)引数は,呼び出しの中に接合(splice)される引数のリストでなければいけません.

variable <- ~ x
extra_args <- list(na.rm = TRUE, trim = 0.9)
f_interp(~ mean(uq(variable), uqs(extra_args)))
#> ~mean(x, na.rm = TRUE, trim = 0.9)

練習問題

  1. ユーザが説明変数と目的変数を2つの別々のフォーミュラで与えることができるようなlm()のラッパー関数を作成してください.

  2. f_eval()with()と比較,対照してください.

  3. 次のコードが,fが2か所で定義されているにもかかわらず(しかも片方は関数ではないのに)動作するのはなぜでしょうか?

    f <- function(x) x + 1
    f_eval(~ f(10), list(f = "a"))
    #> [1] 11
    

非標準スコープ

非標準スコープ(non-standard scoping, NSS)はRの重要な構成要素です.というのも,これによってインタラクティブなデータ探索のための関数を書くのが容易になるからです.そのような関数では,ちょっとした曖昧さや「マジック」と引き換えにタイピングが少なくて済みます.このことは,インタラクティブなデータ探索においては良いトレードオフになっています.なぜなら,インタラクティブなデータ探索では頭の中のアイディアをできるだけ速くコンピュータで実現したいですし,もし関数が間違った推測をしても,インタラクティブに作業しているのですぐに間違った箇所の見当がつくからです.

非標準スコープの実装には困難が3つあります.

  1. 計算(表現式)とコンテクスト(環境)の両方を捕捉し,関数の引数の評価を正確に遅延させなければいけません.著者としては,ユーザにすべてのNSS引数を~で「クオート」するよう要求して,計算とコンテクストが捕捉されていることを明示的にし,それからf_eval()で明示的に評価することを推奨します.

  2. NSS関数を使う関数を書くときには,オブジェクトの自動的な検索を避けて,どこでオブジェクトを見つけるべきかを明示的にする手段が必要です.f_eval().data.envという代名詞によってこの問題を解決します.

  3. ユーザがフォーミュラの一部分を与えることができるようにするための手段が必要です.f_eval()はアンクオートによってこれを解決します.

これらの困難を説明するため,base::subset()dplyr::filter()に似た働きをするsieve()という関数を実装します.sieve()の目的は,論理式で定義された条件に合致するオブザベーションを簡単に選択できるようにすることです.sieve()には[と比べて3つの利点があります.

  1. データフレームの名前を何度もくり返し書く必要がないので,条件式で多くの変数を使う場合にずっとコンパクトになります.

  2. 条件式がNAと評価される行は,NAで埋めるのではなく除外します.

  3. 常にデータフレームを返します.

sieve()の実装は単純明快です.まずf_eval()を使ってNSSを実行します.それから評価結果が論理ベクトルであることを確認して,NAFALSEで置き換え,[で行を抽出します.

sieve <- function(df, condition) {
  rows <- f_eval(condition, df)
  if (!is.logical(rows)) {
    stop("`condition` must be logical.", call. = FALSE)
  }

  rows[is.na(rows)] <- FALSE
  df[rows, , drop = FALSE]
}

df <- data.frame(x = 1:5, y = 5:1)
sieve(df, ~ x <= 2)
#>   x y
#> 1 1 5
#> 2 2 4
sieve(df, ~ x == y)
#>   x y
#> 3 3 3

sieve()によるプログラミング

あなたが次のようなコードを書いたと思ってください.

sieve(march, ~ x > 100)
sieve(april, ~ x > 50)
sieve(june, ~ x > 45)
sieve(july, ~ x > 17)

(これはわざとらしく作った例ですが,もっと役に立つ関数を書くときに考える必要のある重要な問題点がすべて示されています.)

あなたはコードのコピペを続けるかわりに,共通のふるまいを関数にまとめることにします.

threshold_x <- function(df, threshold) {
  sieve(df, ~ x > threshold)
}
threshold_x(df, 3)
#>   x y
#> 4 4 2
#> 5 5 1

この関数は2つの点において不具合が生じるかもしれません.

  1. データフレームはxという名前の変数を持たないかもしれません.グローバル環境にxという名前の変数がない限り,不具合が生じます.

    rm(x)
    df2 <- data.frame(y = 5:1)
    
    # Throws an error
    threshold_x(df2, 3)
    #> Error in eval(expr, envir, enclos):  オブジェクト 'x' がありません
    
    # Silently gives the incorrect result!
    x <- 5
    threshold_x(df2, 3)
    #>   y
    #> 1 5
    #> 2 4
    #> 3 3
    #> 4 2
    #> 5 1
    
  2. データフレームがthresholdという名前の変数を持っているかもしれません.

    df3 <- data.frame(x = 1:5, y = 5:1, threshold = 4)
    threshold_x(df3, 3)
    #>   x y threshold
    #> 5 5 1         4
    

これらの不具合は,エラーを出さずに無言で間違った答えを出すため,とりわけやっかいなものです.どちらの不具合も,f_eval()が各変数名を2箇所(与えられたデータと,フォーミュラの環境)で探すせいで曖昧性が持ち込まれたために生じたものです.

threshold_x()の信頼性を高めるには,代名詞.data.envを使って,もっと明示的にする必要があります.

threshold_x <- function(df, threshold) {
  sieve(df, ~ .data$x > .env$threshold)
}

threshold_x(df2, 3)
#> Error: Variable 'x' not found in data
threshold_x(df3, 3)
#>   x y threshold
#> 4 4 2         4
#> 5 5 1         4

ここで.env~が評価された環境,すなわちthreshold_x()の中に束縛されています.

引数の追加

threshold_x()関数は特定の変数と結び付いているため,それほど有用ではありません.閾値と変数の両方を変更できればもっと強力になるでしょう.これは使う変数を指定するための引数を追加すれば可能です.

単純な方法は文字列と[[を使うものです.

threshold <- function(df, variable, threshold) {
  stopifnot(is.character(variable), length(variable) == 1)

  sieve(df, ~ .data[[.env$variable]] > .env$threshold)
}
threshold(df, "x", 4)
#>   x y
#> 5 5 1

これは単純でロバストな解法ではありますが,使えるのは既にある変数だけで,sqrt(x)のような任意の式は使えません.

より一般的な解法は,ユーザがフォーミュラを与えられるようにして,アンクオートを使うことです.

threshold <- function(df, variable = ~x, threshold = 0) {
  sieve(df, ~ uq(variable) > .env$threshold)
}

threshold(df, ~ x, 4)
#>   x y
#> 5 5 1
threshold(df, ~ abs(x - y), 2)
#>   x y
#> 1 1 5
#> 5 5 1

この場合には,variableが曖昧さなく指定されるように保証するのはユーザの責任です.f_eval().data.envuq()の中で評価されたときにも動作するように設計されています.

x <- 3
threshold(df, ~ .data$x - .env$x, 0)
#>   x y
#> 4 4 2
#> 5 5 1

ドット・ドット・ドット

...を受け取る関数について,あなたが便利だと思うかもしれないもうひとつのツールがあります.たとえば以下のコードでは,dplyr::mutate()base::transform()に似た関数を実装しています.

mogrify <- function(`_df`, ...) {
  args <- list(...)

  for (nm in names(args)) {
    `_df`[[nm]] <- f_eval(args[[nm]], `_df`)
  }

  `_df`
}

(注意:第1引数は構文違反の名前(つまり`でクオートする必要あり)なので,新しく作られる変数の名前と誤って一致することはありません.)

mogrifty()によってデータフレームに変数を追加するのが簡単になります.

df <- data.frame(x = 1:5, y = sample(5))
mogrify(df, z = ~ x + y, z2 = ~ z * 2)
#>   x y z z2
#> 1 1 4 5 10
#> 2 2 2 4  8
#> 3 3 5 8 16
#> 4 4 1 5 10
#> 5 5 3 8 16

この実装の問題点のひとつは,作成される変数の名前を指定するのが難しいことです.変数名と式が別々の変数に入っている関数が欲しい場合を考えてみてください.作成される変数の名前はmogrify()の引数の名前として与えられるので,この関数はぎこちないものになります.

add_variable <- function(df, name, expr) {
  do.call("mogrify", c(list(df), setNames(list(expr), name)))
}
add_variable(df, "z", ~ x + y)
#>   x y z
#> 1 1 4 5
#> 2 2 2 4
#> 3 3 5 8
#> 4 4 1 5
#> 5 5 3 8

lazyevalはこの種の関数を書くのを少し楽にするためにf_list()を提供しています.この関数はフォーミュラのリストを受け取り,各フォーミュラに左辺があれば評価して,フォーミュラの名前をその評価値で置き換えます.

f_list("x" ~ y, z = ~z)
#> $x
#> ~y
#> 
#> $z
#> ~z

list()のかわりにf_list()を使ってmogrify()に手を加えると次のようになります.

mogrify <- function(`_df`, ...) {
  args <- f_list(...)

  for (nm in names(args)) {
    `_df`[[nm]] <- f_eval(args[[nm]], `_df`)
  }

  `_df`
}

add_new()はずっと単純になります.

add_variable <- function(df, name, expr) {
  mogrify(df, name ~ uq(expr))
}
add_variable(df, "z", ~ x + y)
#>   x y z
#> 1 1 4 5
#> 2 2 2 4
#> 3 3 5 8
#> 4 4 1 5
#> 5 5 3 8

練習問題

  1. dfからvariableがその平均値より大きい行をすべて選択する関数を書いてください.ユーザが使う関数をmean()以外(median()等)にも指定できるように関数を一般化してください.

  2. mogrify()の第1引数をxにしたものを作成してください.xという名前の変数を作成しようとするとどうなるでしょうか?

非標準評価

場合によっては,フォーミュラを全く削除してしまい,ユーザが式を直接タイプできるようにしたいということがあるかもしれません.著者もかつてはこの方法に大いに心を奪われていましたが(ggplot2dplyr,等々を見よ),今ではこの方法を使うのは控えめにすべきだと考えています.なぜなら,~による明示的なクオートの方がコードがより単純になり,何か特殊なことが起きているということがユーザにとってより明確になるからです.

とは言え,あなたが本当に~を削除したいのであれば,lazyevalはそれを可能にします.その場合にはNSEとSEの両方のバージョンの関数を作ることを推奨します.フォーミュラを受け取るSEバージョンには_というサフィックスを付けるべきです.

sieve_ <- function(df, condition) {
  rows <- f_eval(condition, df)
  if (!is.logical(rows)) {
    stop("`condition` must be logical.", call. = FALSE)
  }

  rows[is.na(rows)] <- FALSE
  df[rows, , drop = FALSE]
}

それから明示的なフォーミュラを必要としないNSEバージョンを作成します.鍵となるのは,未評価の引数(プロミス)を受け取り,それをフォーミュラとして捕捉するf_capture()の使い方です.

sieve <- function(df, expr) {
  sieve_(df, f_capture(expr))
}
sieve(df, x == 1)
#>   x y
#> 1 1 4

もしsubstitute()に馴染みがあれば,同じ欠点がこの関数にもあてはまると思うかもしれません.しかしf_capture()は充分に賢く,元々の値までプロミスの連鎖を辿っていくので,たとえば次のコードはうまく動きます.

scramble <- function(df) {
  df[sample(nrow(df)), , drop = FALSE]
}
subscramble <- function(df, expr) {
  scramble(sieve(df, expr))
}
subscramble(df, x < 4)
#>   x y
#> 2 2 2
#> 1 1 4
#> 3 3 5

ドット・ドット・ドット

もしフォーミュラを要求しない...を使う関数が欲しければ,SEバージョンの関数は引数のリストを受け取るようにしておき,NSEバージョンでは複数の引数をフォーミュラのリストとして捕捉できるようにdots_capture()を使うことを推奨します.

mogrify_ <- function(`_df`, args) {
  args <- as_f_list(args)

  for (nm in names(args)) {
    `_df`[[nm]] <- f_eval(args[[nm]], `_df`)
  }

  `_df`
}

mogrify <- function(`_df`, ...) {
  mogrify_(`_df`, dots_capture(...))
}

練習問題

  1. sieve()のかわりにbase::subset()を使ってsubscramble()を再度作成してください.うまくいかないのはなぜでしょうか?

メタプログラミング

非標準評価の最後の用途はメタプログラミングをすることです.これは未評価の表現式に対する計算をする関数なら何でも含む用語です.メタプログラミングについてはhttp://adv-r.had.co.nz/Expressions.html,特にhttp://adv-r.had.co.nz/Expressions.html#ast-funsで学ぶことができます5.時間をかけて,メタプログラミングに役立つヘルパー関数のすべてをこのパッケージに移し,ここでもっとメタプログラミングについて議論するのが目標です.



  1. [訳注]訳者の環境は以下の通り. 

    devtools::session_info()
    #> Session info --------------------------------------------------------------
    #>  setting  value                       
    #>  version  R version 3.3.1 (2016-06-21)
    #>  system   x86_64, mingw32             
    #>  ui       RTerm                       
    #>  language (EN)                        
    #>  collate  Japanese_Japan.932          
    #>  tz       Asia/Tokyo                  
    #>  date     2016-09-19
    #> Packages ------------------------------------------------------------------
    #>  package       * version  date       source                            
    #>  assertthat      0.1      2013-12-06 CRAN (R 3.3.1)                    
    #>  devtools        1.12.0   2016-06-24 CRAN (R 3.3.1)                    
    #>  digest          0.6.10   2016-08-02 CRAN (R 3.3.1)                    
    #>  evaluate        0.9      2016-04-29 CRAN (R 3.3.1)                    
    #>  formatR         1.4      2016-05-09 CRAN (R 3.3.1)                    
    #>  htmltools       0.3.5    2016-03-21 CRAN (R 3.3.1)                    
    #>  knitr           1.14     2016-08-13 CRAN (R 3.3.1)                    
    #>  lazyeval      * 0.2.0    2016-06-12 CRAN (R 3.3.1)                    
    #>  magrittr        1.5      2014-11-22 CRAN (R 3.3.1)                    
    #>  memoise         1.0.0    2016-01-29 CRAN (R 3.3.1)                    
    #>  Rcpp            0.12.6   2016-07-19 CRAN (R 3.3.1)                    
    #>  RevoUtils       10.0.1   2016-08-24 local                             
    #>  RevoUtilsMath * 8.0.3    2016-04-13 local                             
    #>  rmarkdown       1.0.9013 2016-09-05 Github (rstudio/rmarkdown@5d05a39)
    #>  stringi         1.1.1    2016-05-27 CRAN (R 3.3.0)                    
    #>  stringr         1.1.0    2016-08-19 CRAN (R 3.3.1)                    
    #>  tibble          1.2      2016-08-26 CRAN (R 3.3.1)                    
    #>  withr           1.0.2    2016-06-20 CRAN (R 3.3.1)                    
    #>  yaml            2.1.13   2014-06-12 CRAN (R 3.3.1)
    
  2. 今のところ著者自身が理解したばかりなので,ggplot2もdplyrもこれらのツールを実際には使っていませんが,著者のすべてのパッケージが近い将来に整合的になるように努力します. 

  3. [訳注]"formula"は「モデル式」と訳されることが多いと思うが,ここではモデリング以外にもformulaが使えるということが主題なので,「フォーミュラ」とした. 

  4. [訳注]著者の以前の考え方はここで知ることができる.日本語での解説はたとえばここで読むことができる. 

  5. [訳注]邦訳(石田基広,市川太祐,高柳慎一,福島真太朗(訳)「R言語徹底解説」共立出版)では14章「表現式」と14.7節「再帰関数を用いた抽象構文木」に相当する. 

14
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
15