【小ネタ】関数のなかで生成された文字列を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