最近やたら叫んでいますが、Rは関数型言語です。ということはもちろん、無名関数ことλを使うことができます。だから二乗するようなことはこんな感じで書けます。
> (function(x){return(x^2)})(1:10) [1] 1 4 9 16 25 36 49 64 81 100
もちろんRは優等生なので
> (1:10)^2 [1] 1 4 9 16 25 36 49 64 81 100
とか書けますが、とりあえずこれは忘れさることにします。
で、これまで困っていた事態として次のようなものがあった。例えば、標準正規乱数が100個入ってるオブジェクトxがあった時に0以上のものがいくつあるか数えたい。そんな時こんなことをやっていた。
> x <- rnorm(100) > x[x>0] [1] 1.04072273 0.49322122 0.42067606 1.15146069 0.38594549 0.22689647 [7] 0.82263066 0.85221958 0.52983126 0.41086309 0.47649805 0.27552753 [13] 0.72608652 0.25727302 1.07544251 0.29934374 0.58917023 1.27117311 [19] 0.09235985 0.41334768 0.32333618 0.09962336 0.64318894 0.91780209 [25] 1.04953773 0.18723730 0.04810438 0.29805343 1.21660303 1.89804602 [31] 1.10297179 0.96260102 0.12219669 0.62445401 0.03813653 0.29562154 [37] 0.16795036 0.31484896 0.16397835 0.72700134 0.05479128 0.79129518 [43] 0.26684804 0.27863482 0.80515921 0.70092077 0.59598623 1.11061054 [49] 1.63675368
まあ、目的としてはこれで達成なのだが、これは気にくわない。x[x>0]という書き方に無駄がありすぎると思うからだ。xって知っているのに2回も書くとか怠惰な人間には耐えがたいことである。あまりにもつらく、ご飯がのどを通らなくなってきたので、自分で関数を書いてみたりなどした。
> (function(x,cond){return(x[ifelse(eval(parse(text=cond)),TRUE, FALSE)])})(x,"x>0") [1] 1.04072273 0.49322122 0.42067606 1.15146069 0.38594549 0.22689647 [7] 0.82263066 0.85221958 0.52983126 0.41086309 0.47649805 0.27552753 [13] 0.72608652 0.25727302 1.07544251 0.29934374 0.58917023 1.27117311 [19] 0.09235985 0.41334768 0.32333618 0.09962336 0.64318894 0.91780209 [25] 1.04953773 0.18723730 0.04810438 0.29805343 1.21660303 1.89804602 [31] 1.10297179 0.96260102 0.12219669 0.62445401 0.03813653 0.29562154 [37] 0.16795036 0.31484896 0.16397835 0.72700134 0.05479128 0.79129518 [43] 0.26684804 0.27863482 0.80515921 0.70092077 0.59598623 1.11061054 [49] 1.63675368
しかし、これも無名関数の中でxというのがあるというのを知っていなければ使えない。これもだめだ。きいいいいいいいいいいいいいいい!!!
filterだろjk
ところで、僕はハチロク世代開発合宿でGaucheという素晴しい言語を勉強した。そういえばこの中にfilterという関数があることを思い出した。これだ!!!Rにもmapやらfilter関数があるに違いない!!!ということで僕はRjpWikiを検索した。するとやはりあった!!!さすがRである。
意外なことにこれが導入されたのは2.6.0かららしい*1。バージョンが古くてfilterが使えないという不幸なことにならないようにバージョンを上げることをお勧めする。というわけでfilter関数とのご対面!!!
> Filter(function(x){x>0},x) [1] 1.04072273 0.49322122 0.42067606 1.15146069 0.38594549 0.22689647 [7] 0.82263066 0.85221958 0.52983126 0.41086309 0.47649805 0.27552753 [13] 0.72608652 0.25727302 1.07544251 0.29934374 0.58917023 1.27117311 [19] 0.09235985 0.41334768 0.32333618 0.09962336 0.64318894 0.91780209 [25] 1.04953773 0.18723730 0.04810438 0.29805343 1.21660303 1.89804602 [31] 1.10297179 0.96260102 0.12219669 0.62445401 0.03813653 0.29562154 [37] 0.16795036 0.31484896 0.16397835 0.72700134 0.05479128 0.79129518 [43] 0.26684804 0.27863482 0.80515921 0.70092077 0.59598623 1.11061054 [49] 1.63675368
ktkr!!!Fが大文字なのを除けば希望どおりのものである。Rでは関数定義を見ることができるので、早速見てみることにする。
> Filter function (f, x) { ind <- as.logical(sapply(x, f)) x[!is.na(ind) & ind] } <environment: namespace:base>
sapplyが使われていたのか。なるほど。自分がapplyファミリーを使いこなせていないことを痛感する。
fold関数
Gaucheで言うところのfoldもできる。1から10までの和を計算している。別にsumでできるよ、とかそういう夢のないつっこみはいらない。
> Reduce("+",1:10,0) [1] 55
文字の連結もReduceを使えばできる。Rでの文字の連結はpaste関数を使うことが多いのだが、これは結構めんどかったりする。paste関数をとりあえずちょっと改造する。
my_paste <- function(init,x){paste(init,x,sep="")}
で、こんな感じで使える。Gaucheを勉強する際に大変おせわになったid:hogelog先生を召喚してしまうなどする。
> x <- c("h","o","g","e","l","o","g") > Reduce(my_paste,x,c("")) [1] "hogelog"
map関数
xをもう一回標準正規乱数がつまっているオブジェクトに戻そう。これを平均が3、分散が1のものに変換する。unlistしているのは結果がリストになって返ってきているから。
> x <- rnorm(100) > unlist(Map(function(x){x+3},x)) [1] 2.7346506 1.4019619 2.1514353 2.2880097 3.2504995 2.0327941 2.7101821 [8] 1.5326318 3.6993861 2.5410795 2.7402735 3.3302353 3.7692598 2.2756684 [15] 3.3664339 1.2126028 3.4788409 1.9165951 3.0049383 3.4903511 2.8756507 [22] 3.1812996 1.0207506 5.8498462 2.6369946 3.5551857 3.7314775 0.5169512 [29] 2.6455718 3.0461002 3.2551023 2.1812471 5.0515013 1.4229575 2.3603549 [36] 2.5212393 3.0627048 3.0863989 3.4950045 2.5142980 0.9146005 3.0387320 [43] 2.2782546 2.5370769 1.9298568 1.2659040 4.0574855 4.2956965 3.2354722 [50] 3.0571974 3.0282453 1.9124323 3.7634894 2.8952408 1.5218918 2.4085856 [57] 2.5630876 1.8249114 3.6092662 1.7197687 2.5599362 2.7106493 1.0648810 [64] 3.8002935 3.3379323 2.5754760 2.8901305 2.6761843 3.2943702 3.5423376 [71] 3.0484507 3.1246627 1.3920451 3.9929911 1.4126346 5.7621317 2.0157619 [78] 2.4203851 3.0050790 2.4659525 4.2260554 4.3557077 4.2148091 4.3513946 [85] 3.2345262 1.2212419 3.7936228 4.7061995 1.9028336 3.3365334 3.1704000 [92] 2.9282389 4.3541470 4.1391755 4.4525903 1.7986936 1.7243301 1.0198071 [99] 2.7124266 4.1455937
ちゃんと変換されているかな?
> mean(unlist(Map(function(x){x+3},x))) [1] 2.856763 > var(unlist(Map(function(x){x+3},x))) [1] 1.101727
おk、絶好調のようだ。「それrnorm(100,mean=3)でできるよ」とかこれまた夢のないことを言う人がいるかもしれないのでもうひとつ例をば。例えば1:10の先頭に"No:"を振るような操作は次のようにできる。
> unlist(Map(function(x){paste("No:",as.character(x),sep="")},1:10)) [1] "No:1" "No:2" "No:3" "No:4" "No:5" "No:6" "No:7" "No:8" "No:9" [10] "No:10"
Mapの関数定義を見れば分かるが、これはapplyファミリーの一員であるmapplyのラッパーに過ぎない。つまりはこういうことである。
> mapply(function(x){paste("No:",as.character(x),sep="")},1:10) [1] "No:1" "No:2" "No:3" "No:4" "No:5" "No:6" "No:7" "No:8" "No:9" [10] "No:10"
楽勝ですね。
まとめ
みんなもっとRの関数型らしい特徴に注目するべき!!少なくともRjpWikiにもっと項目があっていいだろjk。
*1:現在は2.7.2だったはず