NSEとは何か
@dichikaさんによる素晴らしdplyrの紹介。
http://d.hatena.ne.jp/dichika/20141027
Non Standard Evaluation (NSE)とは、関数内部から、その関数を呼び出した時の引数の値じゃなくて表現式そのものを、
関数の中での処理に利用しようという引数評価の方法です。
ある関数が引数を扱うとき、普通は興味あるのはその引数の値。
ところがRでは関数内部で、関数に与えられた引数の表現式を知ることができる。別にRに限った話じゃないけど。
以下のf()
(通常評価)とg()
(NSE)の違いを見れば一目瞭然で、
f = function(i, j, k) {
print(i)
print(j)
print(k)
}
g = function(i, j, k) {
print(substitute(i))
print(substitute(j))
print(substitute(k))
}
x = "orz"
f(i = 1, j = mean, k = x)
## [1] 1
## function (x, ...)
## UseMethod("mean")
## <bytecode: 0x7ff59bbb6ce0>
## <environment: namespace:base>
## [1] "orz"
g(i = 1, j = mean, k = x)
## [1] 1
## mean
## x
この引数に対するg()
的な評価のしかたをNon Standard Evaluation (NSE)と呼ぶのである。
どう使うかというと、一般的にはデータフレームとかリストとかのデータを与えて、その要素を指定する状況が多分、圧倒的におおい。
例えば、
# Standard evaluation
m = function(d, i) {
d[[i]]
}
m(mtcars, "vs")
## [1] 0 0 1 1 0 1 0 1 1 1 1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 0 1 0 0 0 1
m(mtcars, vs)
## Error in (function(x, i, exact) if (is.matrix(i)) as.matrix(x)[[i]] else .subset2(x, : オブジェクト 'vs' がありません
# NSE
n = function(d, i) {
d[[deparse(substitute(i))]]
}
n(mtcars, "vs")
## NULL
n(mtcars, vs)
## [1] 0 0 1 1 0 1 0 1 1 1 1 0 0 0 0 0 0 1 1 1 1 0 0 0 0 1 0 1 0 0 0 1
何がオトクかというと、引数に変数名を与えるときに""で囲わなくてよい、とか、まあ色いろあるんだけど、あんまり気にしないでいいと思う。
subset()
のsubset
とかをNSEじゃない方法で与えるとか、結構面倒かも。
@dichika さんの元記事の例
# NSE
iris %>% group_by(Species) %>% summarise_each(funs(min, mean, median, max), Sepal.Width)
## Error in eval(expr, envir, enclos): 関数 "%>%" を見つけることができませんでした
# ふつー評価
iris %>% group_by(Species) %>% summarise_each_(funs(min, mean, median, max), "Sepal.Width")
## Error in eval(expr, envir, enclos): 関数 "%>%" を見つけることができませんでした
列名を変数で与えたいときなんかはふつー評価でやる。
以下余談。RではこのNSEが実に多用されているのである。
例えばsubset
。
subset(mtcars, subset = disp == max(disp))
## mpg cyl disp hp drat wt qsec vs am gear carb
## Cadillac Fleetwood 10.4 8 472 205 2.93 5.25 17.98 0 0 3 4
ここでdisp == max(disp)
というのは引数の値ではなくて表現式で、
これを呼び出し元で評価しても失敗する。
なぜならdisp
なんていうオブジェクトはどこにも無いからで、それはmtcars
の中にしかない。
つまり、これは失敗する。
cond = disp == max(disp); subset(mtcars, subset = cond)
## Error in eval(expr, envir, enclos): オブジェクト 'disp' がありません
## Error in eval(expr, envir, enclos): オブジェクト 'cond' がありません
ちょっと凝ったことしてプロミス化しても
delayedAssign("cond", disp == max(disp))
subset(mtcars, subset = cond)
## Error in eval(expr, envir, enclos): オブジェクト 'disp' がありません
無駄。
ちなみにsubset
の列選択とか、もう無茶苦茶な感じがするが、これはこれで便利である。
head(subset(mtcars, select = c(mpg, wt:am)))
## mpg wt qsec vs am
## Mazda RX4 21.0 2.620 16.46 0 1
## Mazda RX4 Wag 21.0 2.875 17.02 0 1
## Datsun 710 22.8 2.320 18.61 1 1
## Hornet 4 Drive 21.4 3.215 19.44 1 0
## Hornet Sportabout 18.7 3.440 17.02 0 0
## Valiant 18.1 3.460 20.22 1 0