をプログラムとして見たときに注意・検討すべきところを学んでおきたい、ということで
を読んでいく。
前回
7. プログラミング構造
プログラミング言語としての言語の基本構造を扱う。
7.1 制御文
7.1.1 ループ
では文でのループに加え、文でループを抜ける条件を加えた文および文を利用できる。
ループで別途便利なのが、文である。これによりループの現在の反復の残りを飛ばして次の反復へ進むことができる。
7.1.2 非ベクトルに対するループ
において非ベクトルに対する反復は以下のような方法がある:
- を用いる。ループの反復が互いに独立していれば任意の順序で実行できる
- を用いる。
# get()を用いて文字列の名前のオブジェクトを取る u <- matrix(data = c(1,1,2,2,3,4),nrow = 3) v <- matrix(data = c(8,15,12,10,20,2),nrow = 3) # get()で文字列のオブジェクトの値を得る for(m in c("u","v")){ z <- get(m) print(lm(z[,2]~z[,1])) }
7.1.3 if-else
# if-elseの書き方 if(r==4){ x <- 1 }else{ x <- 3 y <- 4 }
7.2 算術演算子とブール演算子
演算 |
説明 |
---|---|
%% | 剰余 |
%/% | 整数除算 |
等価性のチェック | |
以下のチェック | |
以上のチェック | |
&& | スカラに対する論理積 |
|| | スカラに対する論理和 |
& | ベクトルに対する論理積 |
| | ベクトルに対する論理和 |
ブール否定 |
7.3 引数のデフォルト値
関数の引数にはデフォルト値を与えることができる。デフォルト値を与えている変数は、呼び出すときにその変数に引数を与えなくても、デフォルト値が与えられたものとして扱う。
7.4 戻り値
関数の戻り値は任意のオブジェクトにすることができる。複数の戻り値がある場合には、リストなどに戻り値を配置する。
の慣例では、の呼び出しを避けるのが支配的である。それは、呼び出すと実行時間が長くなるためである。
# 戻り値はreturn()を呼び出して値を返すことができる。 # 呼び出さないと最後の実行した分の値を返す oddcount <- function(x){ k <- 0 for(n in x){ if(n %% 2==1)k <- k +1 } k # return()は実行時間が長くなるので避ける } formals(oddcount) # function()オブジェクトの第1引数(仮引数リスト) body(oddcount) # function()オブジェクトの第2引数(関数自体)
7.5 関数はオブジェクト
関数はクラスのオブジェクトであり、他のオブジェクトと同様にほとんどの場所で利用できる。
は2つの引数を持つ。1つ目は作成している関数の仮引数リストであり、2つ目は関数本体(クラス)である。
関数はオブジェクトであるから、関数を引数として新たに関数を定義することも、複数の関数を要素に持つリストをループすることもできる。
g <- function(x) x^2 h <- function(g,x) g(x) h(g,2)
関数およびも置換関数に利用できる。
g <- function(x){} body(g) <- quote(2*x + 3) # gのbodyを{return(2*x+3)}とする:quote()とすること g g(3)
7.6 環境とスコープの問題
関数(のマニュアルではクロージャという。)の構成要素には、引数および本体に加え、環境がある。
7.6.1 トップレベル環境
関数はトップレベル(インタープリターコマンドプロンプト)で作成されているため、トップレベル環境(_)を持つ。
# 環境を調べる w <- 12 f <- function(y){ d <- 8 h <-function(){ return(d*(w*y)) } h() } environment(f) # ls():環境のオブジェクトを調べる ls() ls.str() # より詳細を返す
7.6.2 スコープ階層
はよりも、スコープ(グローバルかローカルか)は階層的である。
7.6.3 ls()
関数内から引数なしでを呼び出すと、現在のローカル変数の名前を返す。引数を付けると、呼出しチェーン内の任意のフレームのローカルな名前を出力する。
7.6.4 関数にはほぼ副作用はない
関数には概して副作用はない。関数内のコードは非ローカル変数を読み取ることは出来る一方で書き込むことはできない。
7.7 ポインタはあるか?
にはポインタや参照に当たる変数は存在しない。これにより、たとえばでは引数を直接する操作はできない。一例として、であれば、以下のように直接的に引数を操作できる。
x = [13, 5, 12] x.sort() print(x)
しかしではこれができず、代わりに再割り当てを行う。
x <- c(13,5,12) sort(x) x # x自体は何も変わっていない
7.8 上位レベルへの書き込み
環境階層の或るレベルに存在するコードは、そのレベル以上のすべての変数を読み取ることができる一方で、標準的な <- 演算子を用いて上位レベルの変数に直接書き込むことは出来ない。
グローバル変数に書き込みたければ、スーパーアサインメント演算子<<-もしくは関数を用いる。
7.8.1 スーパーアサインメント演算子を用いた書き込み
例として以下を考える。
two <- function(u){ u <<- 2*u z <- 2*z } x <- 1 z <- 3 print(u) two(x) z u
このとき、
はの実引数だが、呼出し後も値はのままである。なぜならば、この値は仮引数にコピーされ、関数内でははローカル変数として扱われる。このためが変更されてもは変更されない。 | |
2つのは互いに全く関係がない。1つはトップレベルであり、もう1つはのローカルである。ローカル変数への変更はグローバル変数には影響しない。 | |
は、を呼び出す前にはトップレベル変数としては存在していない。そのためエラーメッセージが発生する。しかし内のスーパーアサインメント演算子でトップレベル変数として作成され、これはの呼び出し後に確認できる。 |
である。
7.8.2 assign()を用いた書き込み
関数を用いて上位レベルの変数に書き込むこともできる。
two <- function(u){ assign("u",2*u,pos = .GlobalEnv) z <- 2*z } x <- 1 z <- 3 two(x) x u
7.8.3 グロ-バル変数を扱うタイミング
グローバル変数はよく全面禁止することを推奨する議論があるが、実際にはにおいては柔軟に利用される場合がある。たとえばリストの格納値に関数を適用する際、グローバル変数を使った方が分かりやすくなる場合がある。
# 分かりにくい例 f <- function(list_xy){ list_xy$x <- ... list_xy$y <- ... return(list_xy) } lxy$x <- ... lxy$y <- ... lxy <- f(lxy) ... <- lxy$x ... <- lxy$y # 改定案 f <- function(){ x <<- ... y <<- ... } x <- ... y <- ... f() #x,yを変更する ... <- x ... <- y
7.9 再帰
再帰関数は自分自身を呼び出す。再帰の大まかな考え方は以下のとおりである:
再帰関数を用いたタイプの問題の解決方法
- タイプの元々の問題を1つ以上のタイプの小さな問題に分割する
- のなかで小さな問題それぞれに対してを呼び出す
- の中で上記の結果を統合して元の問題を解決する
qs <- function(x){ if(length(x) <= 1) return(x) pivot <- x[1] therest <- x[-1] sv1 <- therest[therest < pivot] sv2 <- therest[therest >= pivot] sv1 <- qs(sv1) sv2 <- qs(sv2) return(c(sv1,pivot,sv2)) }
再帰は2つの欠点を持つ:
7.10 置換関数
には関数の呼び出しの結果に値を割り当てることができる場合がある。
names(vector_x) <- c("a","b","ab") # 上記は以下に等しい x <- "names<-"(x,value = c("a","b","ab"))
7.11 関数コードを作成するためのツール
一時的に必要な短い関数を記述するための簡単な方法を考える。
7.11.2 edit()関数
また関数を用いてコードを編集して再割り当てすることもできる。
f1 <- edit(f1)
7.13 匿名関数
関数オブジェクトは、割り当てをしなくても利用できる。このときその関数オブジェクトを匿名関数と呼ぶ。関数を用いる際などには匿名関数を用いればよい。