Rで関数を利用する

Rには様々な関数があらかじめ用意されている。

例えば、特定の正規分布から無作為に値を抽出するための、rnorm() という関数がある。 rnorm の r は random、norm は nomal(正規分布)を意味する。 この関数は、次のように使う。

x <- rnorm(n = 5, mean = 5, sd = 2)
x
## [1] 8.364601 2.282085 5.736146 3.084380 4.585513

このように、平均 (mean) \(\mu=5\)、標準偏差 (sd) \(\sigma =2\)、の正規分布から、サンプルサイズ \(n=5\) のサンプルが無作為に選ばれる。

関数を使う場合は、通常関数にいくつかの値を渡す必要がある。rnorm() の場合、nmeansd の3つの値が渡されている。関数に入力するこれらの値は、引数(引数、arguments)と呼ばれる。 指定すべき引数を指定しないと、エラーになる。試しに、R のコンソールに rnorm() とだけ入力してみよう。

rnorm()
## Error in rnorm(): argument "n" is missing, with no default

このように、エラーメッセージが表示される。 このエラーは、「引数 n が指定されていない」と苦情を述べているので、n さえ指定すれば動くはずである。

rnorm(n = 2)
## [1] -0.8193019  1.4539893

今度は、問題なく動いた。しかし、rnorm() の引数は3つあるはずである。他の2つは指定しなくてもよいのだろうか。 今度は、カッコも外して rnorm と入力してみよう。

rnorm
## function (n, mean = 0, sd = 1) 
## .Call(C_rnorm, n, mean, sd)
## <bytecode: 0x7f9c1c682ca0>
## <environment: namespace:stats>

このように、関数の名前のみを括弧を付けずに入力すると、関数の定義が表示される。 ここで、出力された結果の第1行に注目すると、function(n, mean = 0, sd = 1) と書かれている。 先ほど説明したように、n、mean、sd が引数であるが、mean には0が、sd には1が割り当てられているのに対し、n は単独で存在する。meanの0や sdの1 のように、関数の定義で割り当てられている数値は、デフォルト値 (default value) と呼ばれる。デフォルト値は、引数が明示的に示されていない場合に使われる。したがって、rnorm(n = 2)rnorm(n = 2, mean = 0, sd = 1) として実行される。つまり、rnorm() はデフォルトでは標準正規分布から無作為に値を抽出する。 関数を使う際にデフォルト値がない引数の値を指定しないと、先ほど見たようなエラーが出る。Rの関数には、第1引数の指定が必須で、第2引数以降にはデフォルト値があるものが多い。ただし、第1、第2、\(\dots\)というのは、定義されている順番である。

引数を定義と同じ順番で指定すれば、引数名は省略できる。

set.seed(2016-04-27)         # 乱数の種を設定する
rnorm(n = 5, mean = 2, sd = .1)
## [1] 2.042295 1.888696 1.968404 2.014278 2.124739
set.seed(2016-04-27)         # 上と同じ種を使う
rnorm(5, 2, .1)
## [1] 2.042295 1.888696 1.968404 2.014278 2.124739

引数名が明示されていれば、順番は影響しない。

set.seed(2016-04-27)         # 乱数の種を設定する
rnorm(5, -5, 2)
## [1] -4.154097 -7.226075 -5.631923 -4.714446 -2.505227
set.seed(2016-04-27)         # 上と同じ種を使う
rnorm(sd = 2, n = 5, mean = -5)
## [1] -4.154097 -7.226075 -5.631923 -4.714446 -2.505227

慣習として、第1引数は引数名を書かずに最初に記述し、第2引数以降だけ記述することが多い。つまり、

rnorm(5, mean = 3, sd = .5)
## [1] 2.737499 3.398254 3.618264 2.362968 2.753790

のような書き方をすることが多い。

関数が結果として出力するものを、関数の返り値または戻り値 (return) と呼ぶ。 関数は返り値を返すことで終了する。

Rで関数を作る

R では、簡単に自前の関数を作ることができる。 関数を作るためには、function() を使う。

まず、二次関数 \(f(x) = x^2 + 6x + 9\) を定義してみよう。 実際に定義する前に考えるべきことは、

  1. 引数は何か
  2. 返り値は何か

ということである。

この例の場合、引数は \(x\)、返り値は \(f(x)\) である。 すると、関数は以下のように定義できる。

my_quad <- function(x) {
    # 二次関数
    # 引数: x = 実数
    # 返り値: f(x)
    y <- x^2 + 6*x + 9
    return(y)
    y <- 0     # 余分な行をあえて書いておく
}

これで関数が定義できた。現時点では定義しただけなので、結果は何も表示されない(RStudio では、右上の Environment のFunctions に定義した関数が表示されるはず)。 定義を確認してみよう。

my_quad
## function(x) {
##     # 二次関数
##     # 引数: x = 実数
##     # 返り値: f(x)
##     y <- x^2 + 6*x + 9
##     return(y)
##     y <- 0     # 余分な行をあえて書いておく
## }

確かに定義できていることがわかる。

実際に使ってみると、

my_quad(0)
## [1] 9
my_quad(-3)
## [1] 0
my_quad(10)
## [1] 169

のようになる。関数は return() を評価した時点で終了するので、関数の定義の最後の行にある y <- 0 は無視されている。

関数の形状を確かめたいときは、curve() が便利である。

curve(my_quad, from = -8, to = 2, col = "royalblue", lwd = 2,
      ylab = "f(x)", main = "自分で定義した関数 my_quad")

関数を定義する練習

上の例からわかる通り、Rでは簡単に自前の関数を作ることができる。 2回以上同じ計算や作業を繰り返すときは、関数を作ってしまった方が楽だし、 間違いも減らせるので、関数を作るのに早く慣れた方がよい。

練習として、もう少し複雑な関数を定義してみよう。 ここでは、階乗を計算する関数を作る。 Rにはあらかじめ factorial() という関数が用意されているので、実際にはそれを使った方がよいが、 あくまで練習と割り切って自分で定義してみよう。 階乗とは、 \[n! = n (n-1) (n-2) \cdots 1\] となる計算で、\(n\) は非負の整数、\(0!=1\) とする。

とりあえず作ってみる

とりあえず関数を作ってみよう。引数は \(n\)、返り値が\(n!\)となる関数を作る。 1から順番に数をかけ合わせていくという方法を試してみよう。

my_fact <- function(n) {
    # 階乗を計算する関数
    # arg: n = a non-negative integer
    # return: n!
    output <- 1
    for (i in 1:n) {
        output <- output * i
    }
    return(output)
} 

このように、for を使ってループを実現することができる。 for の動きは、以下の例でわかるだろう。

a <- 1:10
for (i in 1:10) {
    cat(paste(a[i], "sheep, ", sep = " "))
}
## 1 sheep, 2 sheep, 3 sheep, 4 sheep, 5 sheep, 6 sheep, 7 sheep, 8 sheep, 9 sheep, 10 sheep,

とりあえず定義ができたので、いくつかの値で計算してみよう。

my_fact(5)
## [1] 120
my_fact(3)
## [1] 6
my_fact(1)
## [1] 1

とりあえず、動いているようである。しかし、

my_fact(0)
## [1] 0

という結果は、定義に合わない。どうやらこの関数は、\(n=0\) の場合にうまく動かないようである。

そこで、\(n=0\) の場合だけ場合を分けて定義してみよう。

my_fact <- function(n) {
    # 階乗を計算する関数
    # arg: n = 非負の整数
    # return: nの階乗
    output <- 1
    if (n > 0) { # n > 0 の場合のみ実行
        for (i in 1:n) {
            output <- output * i
        }
    }
    return(output)
} 

新しい関数を使ってみよう。

my_fact(5)
## [1] 120
my_fact(3)
## [1] 6
my_fact(1)
## [1] 1
my_fact(0)
## [1] 1

今度は、うまく動いているようである。

不正な入力に備える

ここまでは、関数のユーザが定義された関数の正しい使い方を知っているという暗黙の仮定を置いていた。 しかし、その仮定がいつも成り立つとは限らない。例えば、本来は階乗で計算しないはずの、負の整数や小数が入力されたらどうなるだろうか。試してみよう。

my_fact(-5)
## [1] 1
my_fact(0.1)
## [1] 1
my_fact(3.82)
## [1] 6

このように、いずれの場合も値を返すが、これらの値は正しくない。 これらの入力値に対応する階乗の値を私たちは定義していない。 今のままでは、この関数のユーザが、返された値が正しいと誤解してしまう恐れがある。 そこで、不正な入力に備える必要がある。

Rで関数を定義するときは、stop() を使って不正な入力に対してエラーメッセージを表示することができる。メッセージの内容も自分で書ける。ここでは、負の値と小数に備えることにしよう。

my_fact <- function(n) {
    # 階乗を計算する関数
    # arg: n = 非負の整数
    # return: nの階乗
    
    # 負値の入力に備える
    if (n < 0) stop(message = "n は非負です")
    
    # 小数の入力に備える
    if (n != round(n)) stop(message = "n は整数です")
    
    output <- 1
    if (n > 0) { # n > 0 の場合のみ実行
        for (i in 1:n) {
            output <- output * i
        }
    }
    return(output)
} 

定義ができたので、動作を確認してみよう。

my_fact(-5)
## Error in my_fact(-5): n は非負です
my_fact(0.1)
## Error in my_fact(0.1): n は整数です
my_fact(3.82)
## Error in my_fact(3.82): n は整数です
my_fact(5)
## [1] 120

これで、階乗の定義ができた。

ベクトルの入力に対処する

とりあえず階乗関数はできたが、複数の階乗を一度に計算することはできるだろうか。 例えば、5!, 7!, 10! を一度に計算することはできるだろうか。 Rに備え付けの関数を使うと、

factorial(c(5, 7, 10))
## [1]     120    5040 3628800

のようにきちんと動くが、私たちの関数は、

my_fact(c(5, 7, 10))
## Warning in if (n < 0) stop(message = "n は非負です"): the condition has
## length > 1 and only the first element will be used
## Warning in if (n != round(n)) stop(message = "n は整数です"): the condition
## has length > 1 and only the first element will be used
## Warning in if (n > 0) {: the condition has length > 1 and only the first
## element will be used
## Warning in 1:n: numerical expression has 3 elements: only the first used
## [1] 120

となって、5!しか返さない。まだ改良がいるようである。

警告 (warning) メッセージを読むと、どうやら 条件の真偽を判定する if がベクトルに対応できていないようなので、そこを改良する。

my_fact <- function(n) {
    # 階乗を計算する関数
    # arg: n = 非負の整数
    # return: nの階乗
    
    # 入力されたベクトルの長さを調べる
    n_len <- length(n)
    # 出力ベクトルを用意する
    output <- rep(NA, n_len)
    
    # 入力ベクトルの値ごとに階乗を計算する
    for (i in seq_along(n)) {
        if (n[i] < 0) {
            output[i] <- NaN  # エラーの代わりに数が定義されないことを示す
        } else if (n[i] != round(n[i])) {
            output[i] <- NaN  # エラーの代わりに数が定義されないことを示す
        } else {
            output[i] <- 1
            if (n[i] > 0) { # n > 0 の場合のみ実行
                for (j in 1:n[i]) {  # 既に i は使われているので、jを使う
                    output[i] <- output[i] * j
                }
            }
        }
    }
    return(output)
} 

定義ができたので、動作を確認してみよう。

my_fact(c(5, 7, 10, -4.7))
## [1]     120    5040 3628800     NaN

望み通りの働きをしてくれているようである。

再帰呼び出し

すでにきちんと動作する関数はできたが、もう少し洗練された定義を考えてみよう。

ここまでは、forループで「1から順番にn までかける」操作を実現したが、関数で繰り返しを実行するときは、再帰 (recursion) と呼ばれる方法が用いられることがある。入力値は1つで、ユーザが不正な入力をしないと仮定し、再帰呼び出しを用いて階乗を定義し直す。

rec_factorial <- function(n) {
    if (n == 0) return(1)  #  0! = 1
    return( n * rec_factorial(n - 1) ) # 再帰呼び出し
}

ここでは、\(n! = n \cdot (n-1)!\) という事実が利用されている。 動作を確認してみよう。

my_fact(5)
## [1] 120
my_fact(0)
## [1] 1
my_fact(10)
## [1] 3628800

このように、再帰呼び出しを使うとよりシンプルな定義ができる。

授業の内容に戻る