# 関数の自作 ---- # ここまではRが用意したコマンド(関数)を利用していろいろな処理を行う方法を説明してきた. # このような用意されたコマンド以外に自分で必要なコマンドを関数として作成することもできる. # まずプログラミングの基礎として,自作関数を取り上げる. # 表計算ソフトでもよく利用するデータ列の平均値を求める関数はRでは mean() として用意されている. # 例えば,次の2行を実行してみよう. a=1:10 mean(a) # そうすると1から10までの平均値が表示される. # ちなみにオブジェクト a を使わずに直接 mean() の引数に書いても同じ結果が得られる mean(1:10) # これと同じ処理をする関数を自作してみよう(実用性はありません,練習のためだけです.用意されているものはありがたく使ってください) # まず最初に関数の名前を決める(ここでは myMean にする) # 次に関数の引数の有無を決める.引数を使う場合はオブジェクトとして指定(ここでは x とする) # function() コマンドを使って定義 # myMean = function( x ) { 中身 } # 中身の処理の部分を作成する(ここではオブジェクト x の総和を計算して,それを x の個数で割る) # 総和は sum() コマンドを,オブジェクトの長さ(個数)は length() コマンドを利用する # sum(x)/length(x) # 計算結果を戻すときは return() コマンドを利用する # これらをまとめて書くと myMean = function(x){ return(sum(x)/length(x)) } # これらをRunするとEnvironmentのFunctionsにmyMeanが追加される # 確認のため次の行をRunすると,作った内容が表示される myMean # 動作確認のため次のコマンドをRunして mean() と同じ結果が得られることを確かめる myMean(a) # なお return() の引数に計算式を直接書いたが,わかりにくければ一旦 y というオブジェクトを使って次のように書いてもよい myMean1 = function(x){ y = sum(x)/length(x) return(y) } # 演算子 ---- # 数学や算数でおなじみの+や-などは演算子と呼ばれ,オブジェクト間の演算(計算処理)を実行する # 演算子は算術演算子,比較演算子と論理演算子に大別できる # 算術演算子はいわゆる四則演算で既におなじみのものだと思う # 演算子で気をつけておかないと行けないことは優先順位が存在することである # たとえば加減算(+,-)と乗除算(*,/)では乗除算の方が優先順位が高いため先に計算される 2+4/2 # を実行するとわかるが,2+4ではなく,4/2が先に計算され,その後に2+2が計算される # 順序をきちんと指定したければ (2+4)/2 # のように半角の括弧でくくる必要がある # べき乗は ^ あるいは ** 2**3 # 整数除算(ある数を割ったときの商と余の商)は %/%,剰余は %% 5 %/% 2 5 %% 2 # 比較演算子は演算子の左右の大小関係,等価関係を評価する演算子 5 > 2 3 == 4 4 >= 4 # 比較演算子は算術演算子よりも優先順位が低い.先に算術演算子が計算されてから比較演算子が計算される 2 + 3 == 3 + 4 # 比較演算子の計算結果は TRUEかFALSEだが,算術演算子と混合して使うと TRUEは1,FALSEは0として扱われる 2 + (3 == 3) + 4 # (3 == 3) は TRUEなので1として扱われる # 論理演算子は論理型のオブジェクトの間の計算を行う # 論理積 &&, &,論理和 ||, |(lとかIではない.shiftキーを押して\バックスラッシュか円マーク),否定 ! がある # 論理積は演算子左右がともにTRUEのときだけTRUEになる c(T,T,F,F) & c(T,F,T,F) # 論理和は演算子左右のどちらかがTRUEであればTRUEになる c(T,T,F,F) | c(T,F,T,F) # & と && の違いはベクトル処理なのか単独オブジェクトの処理なのかの違い c(T,T,T) && c(F,T,T) c(T,T,T) & c(F,T,T) # なお論理型以外のオブジェクトは0はFALSEとして,それ以外はTRUEとして扱われる # ! は否定演算子で論理値を反転させる !T # 否定演算子は論理積,論理和よりも優先順位が高い # 演算子の優先順位をまとめると # べき乗 # 整数除算,整数剰余 # 乗算,除算 # 加算,減算 # 論理否定 # 比較演算子 # 論理積,論理和 # 同じグループ内では,式の中で左側にある方が優先 2 * 3 ** 2 # べき乗>乗除算 5 * 4 %/% 3 # 整数除算,整数剰余>乗除算 4 / 2 * 4 !1-1 !T == F T == T & F == F # プログラムの基本(構造化プログラミング) ---- # R言語に限らず一般的なプログラム言語では次の3つの制御構造を組み合わせることでプログラムを作成することができる. # ダイクストラ(Edsger W. Dijkstra)が提唱した構造化プログラミング(Structured Programming)と呼ばれる手法 # 3つの構造とは,「順次」,「反復」,「分岐」 # 複雑な処理もこれらを適切に組み合わせることによって記述(プログラミング)可能 # ただし最適(処理速度が高速,コードサイズが最小など)であることは必ずしも保証されず,誰が見ても理解しやすい(レビューしやすい)ことに重点が置かれている(私はそこに価値があると思います) # Rによる構造化プログラミングの例 ---- # Rでのプログラミングを例にして,構造化プログラミングの順次,反復,分岐を説明していく # 順次 ---- # 順次は制御構造の中でもっともわかりやすく単純である. # 要するに,書いてある順番に一つずつ実行する. a=1:10 mean(a) # まず1つ目の a=1:10 が実行されて,それに引き続いて2つ目の mean(a) が実行される. # 先ほどの関数 myMean1() の定義の中身の部分も順次の構造になっている. # 反復 ---- # 反復は同じ処理のグループを決められた条件の間,繰り返すというものである. # Rでは for() コマンドを用いるとよい # 何か適当なオブジェクトを繰り返し回数を数えるために使用し,次のように書けばよい # for( オブジェクト in 繰り返し用のベクトル ) { 繰り返すコマンド } # 繰り返し用のベクトルから1つずつ要素が取り出されて,それがオブジェクトに代入される. # ベクトルの最後の要素まで取り出したらおしまい # 1から10までの和を求めるなら,次のようにすればよい sum1 = 0 for( n in 1:10 ) { sum1 = sum1 + n } # 最初に sum1 を初期化して(ここでは0),sum1 に n から要素を1つずつ取り出して足したものを次の sum1 とする # 結果を格納するオブジェクトは初期化しておかないと使われていたら計算結果がおかしくなる # もちろん上で使った sum(1:10) でも同じ結果になる # 繰り返し用のベクトルは c() コマンドで与えてもOK.例えば sum1 = 0 for( n in c(1, 3, 4, 2) ) { sum1 = sum1 + n } print(sum1) # オブジェクトの値をConsoleに表示したいときは,print() コマンドを使って,表示したいオブジェクトを引数に書けばよい # 例えば上で使った sum() コマンドを for() を使って書くと次のようになる mySum = function(x){ m = 0 for( n in x ) { m = m + n } return(m) } # またベクトルを作る c() コマンドと組み合わせると規則性のあるベクトルを作ることができる j = numeric() for( n in 1:10 ) { j = c(j, n) } # この場合オブジェクト j は何も数値が入っていない状態に初期化したいので numeric() コマンドを利用する # 上の例の sum1 = 0, m = 0 も sum1 = numeric(), m = numeric() でもよい # また j = c(j, n) はベクトル j に n に入っている値を追加していく操作になる # 次のコマンドを順に実行してみると様子がわかる j = numeric() j = c(j, 1) ; print(j) j = c(j, 2) ; print(j) j = c(j, 3) ; print(j) # 誕生日のパラドクス ---- # さてここで以前動画で紹介した誕生日のパラドクスを計算してみよう # 複数の人がいたとき,その中に同じ誕生日の人がいる確率を計算する関数を作ってみよう # ここでは同じ誕生日の人がいる確率を直接求めるのでなく,同じ誕生日の人がいない確率を求めて,それを1から引いて求める(余事象の確率) # 1人であれば同じ誕生日の人はいない(当たり前) # 2人であれば,2人目の人は1人目以外の誕生日であれば同じにならない,つまり 365日中,365-1= 364日であればよい # 3人であれば,3人目は1人目と2人目以外の誕生日であれば同じにならない,365日中363日.以下同様 # 一般化してn人であれば,n-1番目までの人と異なる誕生日であれば同じにならない,365日中(365-n+1)=(366-n) # なのでn人目の人が同じ誕生日にならない確率は (366-n)/365 で計算できる # また1人目が同じ誕生日とならず,2人目が同じ誕生日にならない確率は互いに影響しないので,それぞれの確率の積で計算できる(独立事象の確率) # (366-1)/365*(366-2)/365 # これを for() を使って書いてやればよい birthdayParadox1 = function(x){ prob = 1 for( n in 1:x ) { prob = prob*(366-n)/365 } return(1-prob) } # 上の birthdayParadox 関数は同席する人数に対して同じ誕生日の人がいる確率を計算してくれるが,1人からその人数までの確率をベクトルで返してくれると表やグラフを作成するときに便利である # 改造方法としては,for() コマンドで誕生日が同じにならない確率を計算しているときに,別のオブジェクトにベクトルの形で代入しておけばよい birthdayParadox2 = function(x){ prob = 1 vprob = numeric() for( n in 1:x ) { prob = prob*(366-n)/365 vprob = c(vprob, prob) } return(1-vprob) } # なおRではベクトル型のオブジェクトを定数から引くと,定数からベクトルの各要素を引いた結果のベクトルが得られる # どうせならデータフレームで1列目が人数,2列目が確率だと便利になるので,さらに改造してやる birthdayParadox3 = function(x){ prob = 1 vprob = numeric() for( n in 1:x ) { prob = prob*(366-n)/365 vprob = c(vprob, prob) } bp = data.frame(人数=1:x, 確率=1-vprob) return(bp) } # この誕生日のパラドクスをデータフレームで返す関数は,必ず1人から指定した人数までの確率を計算するようになっている. # グラフなどに使う場合は,50人を超えたあたりからはほぼ同じような確率になるため,例えば10人おきで十分である # そこで引数として任意のベクトル(例えば,1から50でその後は60, 70, 80, 90, 100など)を指定できるようにさらに改造してやる. birthdayParadox4 = function(x){ prob = 1 vprob = numeric() vprob1 = numeric() for( n in 1:max(x) ) { prob = prob*(366-n)/365 vprob = c(vprob, prob) } for( n in 1:length(x) ) { vprob1 = c(vprob1, vprob[x[n]]) } bp = data.frame(人数=x, 確率=1-vprob1) return(bp) } # max() は引数の最大値を求めるコマンド # とりあえず指定する人数の最大値まで一応確率を計算しておいて,実際に戻り値に渡すのは引数で指定された人数に対するものだけを選ぶようにして実現している # 分岐 ---- # 分岐は条件に従って,実際に実行する処理を選択するというものである. # 例えば,代打を決めるとき,右投手ならば左打者,左投手ならば右打者にするなどである(私はあんまり信じてませんが) # Rでは if() コマンドと else を用いて次のように記述する # if( 条件 ) { 条件成立時の処理 } else { 条件不成立時の処理 } # 左投手を表すオブジェクト handed に"T"か"F"が入力されたとき,打者の別を表示する関数は次のようになる pinchHitter = function(handed){ if( handed == T ) { print("right-handed") } else { print("left-handed") } print("どっちでもあんまり関係ないけど") } # if() ブロックの次の print() コマンドは handed の値によらず必ず実行されているところも注意! # 条件でオブジェクトがある値に等しいことを表すときは,半角の等号を2つ書く(比較演算子) # == は左辺と右辺が等しい時に論理値T(TRUE),等しくないときにF(FALSE)を返す 1==1 1==2 1<2 1!=2 # 条件は論理型を返すコマンドが書かれていればよい # 成立時,不成立時の処理ともコマンドが一つなら,{ } は省略可能 # なので,次のように書いてもOK pinchHitter = function(handed){ if( handed ) print("right-handed") else print("left-handed") print("どっちでもあんまり関係ないけど") } # 閑話休題: if-else の書き方の注意 ---- # 上の例では関数の内部として if-else を記述したが,コンソールから直接入力する際には若干注意が必要となる # 次の例を見てもらいたい handed = F if( handed == T ) { print("right-handed") } # { }はこの場合はあってもなくてもよい else { print("left-handed") } # Rstudio で Run した場合や,このソースからコピペで Console に貼り付けた場合は正しく動作して left-handed と表示されるはずである # ただし Console で直接入力していった場合は,if に続く "}" を入力した時点で何も表示されず終了してしまう # これは else を持たないと判断されてしまうためである # それを回避するためには handed = F if( handed == T ) { print("right-handed") } else { print("left-handed") } # のように "}" の後で改行せずに引き続き else を書いてやればよい # 誕生日のパラドクス ---- # 先の誕生日のパラドクスを計算する関数は引数が自然数でないと正しく動作しない # そこで自然数以外の引数が入力されたときにエラーメッセージを表示して終了するように改造してみる # まず引数のタイプが整数 "integer" であることを確認するために typeof() を利用する # また1以上であることを確認するためには引数の最小値を求めるコマンド min() で1以上であることを判断する # ただしこの場合は整数でないか,あるいは1未満のいずれかに当てはまれば,エラーを表示するようにする # 例えば引数が x だとすると if( typeof(x) != "integer" | min(x)<1 ) return("引数は自然数のベクトルでないとあかんで") # | は「または」を表し,どちらかがT(TRUE)であれば結果がT(TRUE)になる(論理演算子) 1!=2 | 2==3 # ちなみに同時に成立を表す「かつ」は,&(論理演算子) 1!=2 & 2==3 # エラーを加えた改良版は次のようになる # なお if() コマンドの不成立時の処理の else を書いていないのは,成立時にエラーを表示したら関数の処理が終了するため # else を使うなら prob = 1 から return(bp) までを { } でくくる(略) birthdayParadox = function(x){ if( typeof(x) != "integer" | min(x)<1 ) return("引数は自然数のベクトルでないとあかんで") prob = 1 vprob = numeric() vprob1 = numeric() for( n in 1:max(x) ) { prob = prob*(366-n)/365 vprob = c(vprob, prob) } for( n in 1:length(x) ) { vprob1 = c(vprob1, vprob[x[n]]) } bp = data.frame(人数=x, 確率=1-vprob1) return(bp) } # ただし上記の birthdayParadox に例えば birthdayParadox(23) # とすると「引数は自然数のベクトルでないとあかんで」が表示される # これは 23 が integer でなく,double となっているためである typeof(23) # 23 を integer として識別するさせるためには次のようにすればよい as.integer(23) 23L birthdayParadox(as.integer(23)) # for 以外の反復 ---- # Rには上で説明した for() 以外にも反復処理を行うことができる関数が用意されている. # その1つは while() である(たぶん,通常のプログラム言語の反復のイメージに近いと思う) # 例えば上で使った1から10までの和を求める処理は次のように書ける sum1=0 n=1 while(n<=10) { sum1 = sum1+n n=n+1 } print(sum1) # while の引数(上の例では n<=10)が真(つまり T)の間,続くブロックを繰り返す # またもう1つ別の関数としては,repeat がある # 同じ例は次のように書ける sum1=0 n=1 repeat { if(n<=10) { sum1 = sum1 + n n=n+1 } else break } print(sum1) # repeat は延々続くブロックを繰り返し続けるので,ブロックの中に繰り返しから抜けるための break が必須となる # break の働き ---- # 上の repeat のところで触れた break は繰り返し制御(for, while, repeat など)から抜ける働きがある. # ただし,繰り返し制御が入れ子(ネスト構造)になっている場合,その break が置かれた繰り返し制御のみを抜けるところに注意する必要がある # 次の例で確認してもらうとよい i=1; j=1; k=integer() for(i in 1:9) { for(j in 1:9) { if(i