論理の流刑地

地獄の底を、爆笑しながら闊歩する

【小ネタ】関数のなかで生成された文字列をsubstituteして関数内の関数に渡すには【NSE: 非標準評価】

なんか詰まったところを備忘。マニアックすぎるトピックなのだが。

(19/9/2 追記)
↓こっちで書いたquosureを利用した方法のほうがスマートだった。
ronri-rukeichi.hatenablog.com

Introduction

NSE(non-standard evaluation, 非標準評価)を利用して引数を受け付ける関数*1を自作関数の中で使いたいとする。

例として、変数の名前を受け取ってその変数にindexを追加し、その変数名で並び替えたデータフレームをリストで取得するような変数をつくるとする。
今考えた適当な関数だけど。

myfunc <- function(vname, df){
cnt <- 1:10
vname_num <-  paste0(vname, cnt)

ret_list <- list()
for(  i in seq_along(vname_num)){
vn <- vname_num[i]
ret_list[[i]] <- dplyr::arrange(df , vn) #! ここで表現型を渡す必要がある !#

}# for
return(ret_list)
}# function

この例だと、新たに定義した変数名を文字列としてarrange()に渡してしまっているのでエラーがでる。
ゆえに、vname_num(の要素のvn)から表現型を生み出す必要がある。
それが簡単ではないのだなという話。

Try and Error

Hadley神のNSE解説においても、説明されているが関数の引数を受け取って表現型として使うには一般にsubstitute()を使う。
quote()は受け取った引数を評価せずにそのまま表現型にするが、substitute()は当該環境(envに引数指定されているときにはその変換)を受け取って、評価してから表現型にするのである。

testFun <- function(x){
  print(quote(x))
  print(substitute(x))
  print(substitute(x, env=list(x=77)))
} 

testFun(Y)
#x
#Y
#[1] 77

Failure Case1

問題を単純化して、数字を受け取って、"var"の末尾に追加して、表現型にして返す関数を考える。
まず単純にsubstituteしてみる

toExpression <- function(idx){
  vname <- paste0("var", idx)
  return(substitute(vname))
} #function

toExpression(1)
#"var1"

失敗。文字列になってしまう。

Failure Case 2

少しひねってparse(text=... ) をつかってみる

toExpression <- function(idx){
  vname <- paste0("var", idx)
  return(parse(text=vname))
} #function

toExpression(1)
# expression(var1)

expression()がついてしまう。このままeval()とかに渡すんだったらこれでよい。
しかしdplyr::filter()やdplyr::arrange()といったNSEな関数に渡すとエラーが出る(出た)。
泥沼....

Best Solution(?)

上記のような泥沼を経て実現できたのは以下のようなインチキ対応策だった*2

toExpression <- function(idx){
  vname <- paste0("var", idx)
  exprs <- eval(parse(text=paste0("substitute(",vname,")")))
  return( exprs)
} #function

toExpression(1)
# var1

成功。dplyr系の関数にも渡せる。
eval(parse(text = XXX)))を使うとXXXに書かれている文字列がRのコードとして評価される。
vnameのほうをsubstituteでうまく扱えないなら、substitute()の方を一旦文字列にしてしまうことで対応するという方法。

スマートじゃないな....

Enjoy!!
www.youtube.com

*1:dplyrの各関数のSE版が非推奨かつ廃止予定になっているのはなぜなんだ

*2:もっといい方法があればだれか教えてほしい