R
RDay 3

Rプログラミングのための第一歩(未完)

More than 3 years have passed since last update.

R Advent Calendar@Qiita 3日目では、Rを扱う上で欠かせない関数についての説明と関数作成のためのRプログラミングの基礎について書きます。書き足りない点があったりということで未完 :tangerine:です(いつか書く、という意味も込めて)。

書いた人間はやっとこさ関数を書けるようになったひよっこなので、間違いやより良い記述方法などございましたら、コメントやTwitter(@u_ribo)にて報告いただければ幸いです :sweat:

はじめに:japanese_goblin:

関数型言語としてのR

Rは関数型言語です。Rで行われるすべての操作は関数 Functionによって処理されています。Rにおいて関数はfunction()の形で定義されます。

次の2つの和算の実行結果を見てください。

1 + 3 # 電卓風の和算
[1] 4

"+"(1, 3) # 関数風の和算
[1] 4

2番目の例では、和の記号を関数として扱い、和算を行いました。では次に、関数のヘルプを読みだすように、この"+"のヘルプを出してみてくださいhelp("+")算術 Arithmeticという関数のヘルプが出てきたと思います。これは演算子として用いる和の記号も関数として定義されていることを示しています。

このように、通常使っているsummary関数plot関数、パッケージの読み込み時に用いるlibrary関数含めて、すべてが関数です。

この記事の目的:hatching_chick: -> :baby_chick: -> :chicken:

Rの良いところの一つとして、自分でRが実行する関数を定義できる、という点があります。これは、自分自身が持つデータや状況に応じて、俺々な処理を行うプログラミングを組めるということであり、Rでさまざまな処理を行う際の自由度を劇的に高めます。

例えば、ほげほげという処理を実行したいのだけど、それを実現する関数やパッケージがない、というときには、関数がなければ作ればいいじゃない、となるわけです。

といっても関数の作成には少しだけ知識が必要であり、効率的なプログラミングのために知っておくと良いことがあります。今回は、目的に応じた関数を作成できるようにする:hatched_chick:、ということを目的に、Rでのプログラミング作法のようなものを書きます。

Rにおける関数の形

Rでは関数は次の形をもちます。繰り返しになりますが、関数自体の定義はfunction関数を用います。

function (引数) {
    処理内容
}
  • 処理内容(関数の本体) body... この部分に実行したい処理を記述します。上では処理内容を中括弧で囲いましたが、一行で記述できる処理などの場合には必須ではありません。中括弧を使うと、この部分は関数の処理内容であることがわかりやすいのでつけておくのが良いです。
  • 引数 arguments... 関数内で使用する引数を記述します。引数はなくても構いませんし、複数の引数を定義する場合にはカンマで区切って指定します。ここでの引数は初期値をもつ仮引数 formal argumentとして定義されます。

参考にいくつかの関数の処理内容を見てみることにしましょう(できれば簡単な処理をする関数が良いです)。コンソールに関数名のみを入力してみてください。

help
function (topic, package = NULL, lib.loc = NULL, verbose = getOption("verbose"), 
    try.all.packages = getOption("help.try.all.packages"), help_type = getOption("help_type")) 
{
    types <- c("text", "html", "pdf")
    if (!missing(package)) 
        if (is.name(y <- substitute(package))) 
            package <- as.character(y)
# 以下省略

関数のヘルプを呼び出すhelp関数は、topic, package, lib.loc, verbose, try.all.packages, help_typeという6つの引数を持っていることがわかります。引数の定義の後には、複雑な処理の内容が記述されています。

はじめての自作関数

my_functionという名前で、2つのパラメータの和算を行う簡単な関数を作りましょう。

my_function <- function (a = 1, b = 2) {
    a + b # 関数内でも演算子は使える。これは"+"(a, b)と同じ意味をもつ
}

my_function()
[1] 3

関数my_functionを実行すると、3が返されました。これはmy_functionの引数に仮引数が記述されていたためです。今度は関数呼び出し時に引数を再定義します。

my_function(a = 4, b = 1)
[1] 5

引数には文字列を扱うこともできます。新たにプログラミングの第一歩で有名なhello world!を関数で実行してみましょう。関数名はhello_wlとします。

hello_wl <- function (message) {
    print(message)
}

hello_wl("Hello world!")
[1] "Hello world!"

今度はmessageという引数を用意し、実行結果を出力するprint関数messageの内容を表示する、という形にしました。hello_wl関数実行時にmessgageの値(文字列)を変更すれば、その文字列が出力されます。

自作関数の保存・呼び出し

自作関数を書いたら、保存しておきましょう。拡張子はRで通常使っている.Rです。また、一度保存した関数定義ファイルを呼び出す際にはsource関数を使います。

source("my_function.R")

普段使っているパッケージは、こうした複数の関数をまとめたものです。パッケージ内には、複数の関数定義ファイルがあります。自作関数が多くなったら、パッケージとしてまとめておくと、パッケージの呼び出しのみでパッケージ内の関数が使えるようになるのでおすすめです。

簡単ではありますが、Rでの関数の構造と定義の方法についてまとめました。次にもう少し複雑な処理を行うための制御構造について説明します。

制御構文

上で実行したhelp関数の本体をもう一度見てみましょう。本体にはif関数が使われています。Rにおいても、多くのプログラミング言語で使用されるこれらの制御構文を使用することができます。これらの制御構文を効率的に使い分けることで、より複雑で条件に応じた汎用性の高い関数を作成できますので、ぜひ覚えておきましょう。

条件分岐: if () else {}構文

まずはRでの条件分岐そのもの働きについてみます。

if (条件式) {
    条件が満たされたときに行う処理
} else {
    条件が満たされないときに行う処理
}

if関数は、条件式(例えば... A = B, A != C, A = D + E, A > a)と条件式に一致したときの処理および条件式に一致しなかった場合の処理で構成されます。if関数は、条件に一致しなかった場合の処理を記述するため、else関数を併せて呼び出します。そのため、実際には条件分岐はif () else{}構文という風に覚えておきましょう。

またここでもR関数のときと同様、別々の処理(条件が満たされた場合の処理、条件が満たされない場合の処理)を区分するために中括弧を使います。

もう一つの条件分岐: ifelse構文

条件分岐のパターンとして、if () else{}構文に加えてifelse構文があります。

(...ちょっと飛ばします orz)

関数内での条件分岐

関数内に条件分岐を使用することで、汎用性の高い関数が作成できます。例えば、ある引数に異なる値を与えたときの処理を条件によって変更することが可能です。また、多くの関数はこうした複雑な条件分岐で書かれています(試しにもう一度helpを実行)。

簡単な例を示します。demo_if_point関数point引数に与えられた数値によって異なる処理を行います。

demo_if_point <- function (point) {
  if (point >= 6) {
    print("Good :)") 
  } else {
    print("Um...")
  }
}

demo_if_point(point = 7)
[1] "Good :)"

demo_if_point(point = 3)
[1] "Um..."

繰り返し

Rにおいて、同じ命令や処理を繰り返し実行するループ制御は3種類あります(repeat, while, for)。ここではforに焦点を当てて説明します。

for構文

for構文は次の構造をもちます。

for (変数 in 変数の集合) {
  処理する内容
}

関数の形に似ています。定義するのは変数変数の集合処理内容の3つです。

変数の処理の部分は、実際には:演算子を使って連続した値からなるベクトルを与える場合が多いです。例を見てみましょう。

i <- 0 # 初期値として0を与える

for(i in 1:5) { # 1:5はc(1, 2, 3, 4, 5)と同等
  i + 1
}

i
[1] 5

変数の初期化はあってもなくても構いませんが、上の例では処理する内容の部分に書いた、i + 1変数の集合として与えた1:5回、変数に対して繰り返し処理しています。

具体的には、次の繰り返しを実行します。

  1. i <- 0 で与えたi(値は0)に対して、まず1を加える... iの値は1になる
  2. i = 1に対し、さらに1を加える... i = 2
  3. i = 2に対し、さらに1を加える... i = 3
  4. i = 3に対し、さらに1を加える... i = 4
  5. i = 4に対し、さらに1を加える... i = 5

繰り返しの回数が1:5であるため、繰り返しは5回で終わりました。そのため、最終的なiの値は5となったわけです。

繰り返し制御はプログラミングでもよく使われるものです。if構文同様、関数内でも上手にfor構文を使っていきましょう。

おわりに:mortar_board:

ここに書いたことはRプログラミングのほんの一部に過ぎません。しかし、Rプログラミング(関数を作成する)際には欠かせない大事なことです。更に詳しい内容については下記の参考文献やページを参考にしてください。また、誰かがAdvent Calendarに書いてくれることを期待します。

大きな声では言えませんが、今年自分はRに出会って4年目(?)にして、始めて自分で関数を書きました。上でも書いていますが、Rの良いところの一つとして自作関数を作成できる、ということがあります。データフレームの処理にしろ、作図にしろ、同様のスクリプトを使い回す機会があるのならば、関数としてまとめておくことをおすすめします。

関数が集まったら、ぜひGitHubにリポジトリを作成して公開しましょう。パッケージの配布は、R公式のCRANを通さなくても可能です。自分で書いた関数が、他人の役に立つのは嬉しいものです。また、誰かの関数作成の参考になるかもしれません。関数を作る作業は楽しい:icecream:です。

参考