同じ操作を反復するときは、map( )やlapply( )を使って関数を一括して適用すると便利です。そしてコードも読みやすくなります。 map( )やlapply( )では、関数を引数にする必要があります。mean( )やsd( )のように、既に与えられている関数だけでなく、自分で定義した関数を使うこともできますが、初学者にとって関数を自作することは少しハードルが高かった記憶があります(今でもキレイに書けるようになったわけではないですが)。
今回は関数の記述の仕方についてまとめます。
関数とは
「関数(function)とは何か」を数学的に説明したものを読むと、集合とか写像とかいう言葉が出てきてしまって宇宙に放り出された感じがしてしまいます。ここではシンプルに「ある処理を行う部品」と理解しましょう。
解析をしていると、同じ処理が複数の箇所で必要になることがよくあります。その度に処理の方法を全部書いていくのはとても大変なので、名前をつけて「部品」として保存しておけば、関数名を書くだけで同じ処理を呼び出すことが出来るので便利です。
関数は材料を受け取って、特定の処理をして、完成品を出してくれる機械です。処理の材料になるものを「引数(ひきすう), argument」、処理によって出力されるものを「戻り値, return」と言います。
下の例では、meanという名前の関数に、1,2,...,5を引数として渡して平均を求めるという処理をしてもらい、戻り値として3を得ています。
function関数を使って関数を定義する
関数に名前をつけて定義するときは、function( )以下で定義される関数を関数名に代入する方法で記述します。 function( )は関数を定義するための関数で、定義する関数で用いる引数をカッコ内に書きます。
例えば、引数 を入力すると を返す関数をsquare
という名前で定義するときは次のように書きます(累乗は^
の代わりに**
を使ってもOK)。
square <- function(x) x^2
長くなる場合は{ }
を使って複数行に分けることが可能です
square <- function(x){ y = x^2 return(y) }
改行の代わりに;
を使うこともできます。
square <- function(x){y = x^2; return(y)}
名前をつけずに関数を定義する
繰り返し使う関数なら名前をつけておけば便利ですが、再利用しない関数にイチイチ名前をつけるのは面倒です(処理の内容が分かりやすく、他と重複しないように名前を考えるのはまあまあ面倒)。その場で使っておしまいの関数であれば、名前をつけずに定義したいですね。このような名前を与えられない、その場限りの関数のことを無名関数(anonymous function)と呼びます。
lapply( )を使って関数を一括で適用する
例として、次のようなリストls
に対し、各要素を2乗する関数をlappyを使って適用してみます。
ls <- list(x=1:3, y=4:6)
lapply( )に対し、1つ目の引数としてリスト(の形式のデータ)を、2つ目の引数に適用したい関数を与えます。前述の要領で、あらかじめsquareという名前の関数を作っておけば、
square <- function(x){ y = x^2 return(y) } lapply(ls, square)
と書けます。上記を実行したときの戻り値は、次のようになります。
$x [1] 1 4 9 $y [1] 16 25 36
square( )を事前に定義するのが面倒な場合は、次のように引数の中で直接関数を定義することもできます。
lapply(ls, function(x) x^2)
こちらの方が1行で済むので、読みやすいですね。
map( )を使って関数を一括で適用する
purrrパッケージのmap( )を使って一括適用することもできます。
square <- function(x){ y = x^2 return(y) } library(purrr) map(ls, square)
lapply( )と同様、squareという名前で事前に定義せずに、引数の中で直接定義することもできます。
map(ls, function(x) x^2)
map( )ではさらに、次に説明する方法を使ってfunction( )も省くことができます。
ラムダ式で定義する
プログラミングに疎い私は、ラムダ式(lambda expression, lambda function)という用語の定義がよくわかっていません。Pythonではdef
で関数を定義する方法に対して、lambda
で始めて短い一行で関数を定義する方法があるので、前者との対比でラムダ式と呼ばれているようです。
これに対してRでは、複数行に渡る関数でも1行の関数でも、function
というキーワードを使って記述することができるので、区別がよくわかりません。
正確な用語の定義は置いといて、Rでもfunction(x)
を使わずにもっと短い方法で関数を定義することはできます。記述のルールは、
~
で始める(これが関数定義のキーワード)- 引数は
.x
を使う
です。
library(purrr) map(ls, ~.x^2)
これはmap( )限定の記述法で、lapply( )では使えません。また、Pythonのラムダ式のように複数の引数を取ることができません(書いてみるとエラーが出るので多分)。
この記述方法をラムダ式と呼んでいいのか分かりませんが、「関数の引数の中で関数を簡潔に定義する」という目的で用いられているのは正しいと思いますし、ラムダ式と呼んでいる人もそれなりにいると思いますので、敢えて「ラムダ式のようなもの」とは表現しませんでした。
おわりに
- 最初は「"~"から始まるからラムダ式なんだな」と納得していましたが、よく考えたら"~"はチルダでした。恥ずかしい。
参考資料
- ラムダ式は高階関数(関数を引数・戻り値にとる関数)で威力を発揮するとのことです。なんとなくは分かるのですが、経験が浅い故に実感には至っていません。
- 例も多く、全部順番に学びたい人には最適な読み物です。著者が猫好き。