Rは関数型プログラミング言語なので、コード1とデータの境界は曖昧です。四則演算+
,-
,*
,/
も関数で、コードと言っても引数がセットされた関数オブジェクト2のリスト構造のデータの集まりに過ぎません3。Rではコードを格納した変数を、表現式オブジェクトと言って、expression
やparse
でつくってeval
で評価4します・・・と書いても、慣れていないと理解し難いモノなので、ぽちぽちと関係した命令を入力して反応を見て行きましょう。
1 表現式オブジェクトを使ってみる
習うより慣れろと言うことで、試しに動かしてみましょう。
1.1
expression
とeval
の基本的な挙動
expression
とeval
の振る舞いを把握するのは難しくなく、以下の4行で済むと思います。
<- 2
x <- 3
y <- expression(x * y)
e sprintf("%sを評価すると%sになります", e, eval(e))
[1] "x * yを評価すると6になります"
すぐに、expression
はRが計算する式を表す式オブジェクトを返し、eval
に渡すと実際に評価してくれることが想像つくと思います。
1.2
expression
には関数も行列も入る
追加と言うよりは確認の説明ですが、expression
には関数も行列も入ります。
<- function(j){ j^2 }
z <- expression(x * y - z(4))
e sprintf("%sを評価すると%sになります", e, eval(e))
[1] "x * y - z(4)を評価すると-10になります"
<- matrix(c(1, 3, 4, 2), 2, 2)
X <- matrix(c(1, 2, 3, 4), 2, 2)
Y <- expression(X %*% Y)
e e
expression(X %*% Y)
eval(e)
[,1] [,2]
[1,] 9 19
[2,] 7 17
1.3 文字列を表現式オブジェクトにする
文字列を表現式オブジェクトにできます。これはsprintf
関数と組み合わせると便利なので、よく使います。
<- parse(text = "x + y")
e sprintf("%sを評価すると%sになります", e, eval(e))
[1] "x + yを評価すると5になります"
1.4 表現式オブジェクトを文字列にできる
逆もできますが、今日まで使ったことがありませんでした。
deparse(e)
[1] "structure(expression(x + y), srcfile = <environment>, wholeSrcref = structure(c(1L, "
[2] "0L, 2L, 0L, 0L, 0L, 1L, 2L), srcfile = <environment>, class = \"srcref\"))"
元に戻すわけではないです。
1.5 表現式オブジェクトをプロットに用いる
Rのplot
は、題名や軸名や凡例に表現式オブジェクトをとることで、数式を表示することができます。
?plotmath
でヘルプが出るので描画可能な数式が分かり、demo(plotmath)
でプロットの実例が見られますが、試しに使ってみましょう。
<- seq(-1, 1, length.out=100)
x <- expression(x^2-1)
y plot(x, eval(y), type="l", ylab=y)
またcurve
関数は表現式をプロットできます。
curve(x^2-1, c(-1, 1), xname = "x", ylab = expression(x^2-1))
あまり綺麗な数式ではなく文法も独特になるので、latex2exp
パッケージを用いてTeX表記を使う人が多いのですが、ちょっとしたものであれば有用です。
2 表現式以外の言語オブジェクト
表現式オブジェクトの感覚を養ったところで、Rの言語オブジェクトについて説明します。Rにはコール、表現式、ネームの3種類の言語オブジェクトがあります。
call
は一般にはクロージャと呼ばれるもので、関数に値をセットしたオブジェクトです。表現式オブジェクトは内部にクロージャを持つオブジェクトになります。クロージャと表現式オブジェクトは似た存在ですが別の扱いなので、表現式オブジェクトを引数にとる関数にクロージャは入れられないですし、逆も同様です。
name
は間接的に参照できるオブジェクトです。eval("x")
をしても"x"
が文字列として評価されるだけですが、eval(as.name("x"))
とすると、変数x
として評価されます。
オブジェクト | is.call |
is.expression |
is.name |
is.language |
---|---|---|---|---|
call |
✓ | ✓ | ||
表現式 | ✓ | ✓ | ||
name |
✓ | ✓ |
2.1 クロージャをつくる
exprssion
関数で表現式オブジェクトをつくれるように、quote
関数でクロージャ(call
オブジェクト)をつくれます。
<- quote(x - y)
cl cl
x - y
is.call(cl)
[1] TRUE
is.expression(cl)
[1] FALSE
is.name(cl)
[1] FALSE
is.language(cl)
[1] TRUE
2.2 表現式の変数に値を代入してクロージャをつくる
後述する理由で使い勝手が悪いのですが、substitute
関数を使うと代入できます。
<- expression(x^y)
e1 <- substitute(x^y, list(x=2))
e2 e2
2^y
bquote
関数を使うと、リストを作らなくても代入できます。
<- expression(x^y)
e1 <- 3
x <- bquote(.(x)^y)
e2 e2
3^y
環境にある変数の値を代入する変数名を.(
と)
で囲います。
substitute
もbquote
も表現式オブジェクトを引数にとり、クロージャ(call
オブジェクト)を戻します。
2.2.1 表現式オブジェクトの中には代入ができない
さてsubstitute
は第1引数にRの式をとるわけですが、表現式オブジェクトを代入しようとすると、表現式オブジェクトの式として処理します。e1
を引数にとるとe1
と言う式だと考えて、x^y
だとは考えないので、x
やy
に何かを代入することはできません。
substitute(e1, list(x=123))
e1
eval
時に参照する環境を代えれば、評価時に使う変数の値を代えられるので、明示的に代入する必要は無いのですが、substitute
を繰り返してプロットに表示する式を作るようなことはできないので注意してください。
2.3 クロージャをリストにする
例えば以下のようにすると、-
も関数であることが分かります。
as.list(quote(x - y))
[[1]]
`-`
[[2]]
x
[[3]]
y
2.4 クロージャを書き換える
クロージャはlist
ではないのですが、リスト構造を持つオブジェクトで書き換えることもできます。
<- 3
x <- 2
y <- quote(x + y) e
eval(e)
とすれば5
になるわけですが、
1]] <- as.name("-")
e[[eval(e)
[1] 1
関数+
を呼ぶところを、関数-
に差し替えることで1
の計算結果を得ています。
以下のようにすると、同様の結果が得られますが、関数-
への参照ではなく、関数-
の中身が入るので注意してください。
1]] <- `-` # notice: backquote
e[[eval(e)
[1] 1
3
eval
が使う環境
eval
関数が使う環境は、デフォルトではeval
関数を呼び出す親の環境になり、引数envir
を指定することで変えられます。
<- expression(x^y)
e1 <- 2; y <- 3
x <- new.env()
myenv $x <- 3
myenv$y <- 4
myenveval(e1) # x=2, y=3で計算
[1] 8
eval(e1, envir=myenv) # x=3, y=4で計算
[1] 81
関数内では別環境になるので、以上のようにわざわざ指定することは少ないと思います。
<- expression(x^y)
e1 <- function(e){
fn <- 3
x <- 4
y eval(e) # 関数のenvironmentを参照して評価する
}<- 2; y <- 3
x eval(e1) # x=2, y=3で計算
[1] 8
fn(e1) # x=3, y=4で計算
[1] 81
4 まとめ
表現式オブジェクトなどの言語オブジェクトは、多くのユーザーがプロットに数式を入れるとき以外は意識していない存在で、使うときもおまじないぐらいに捉えられていると思いますが、汎用的な関数を作るときには役立つときもあります。知っていれば使いたくなるものなので、大雑把に目を通されると、後々、ためになるかも知れません。