「大人の教養・知識・気付き」を伸ばすブログ

一流の大人(ビジネスマン、政治家、リーダー…)として知っておきたい、教養・社会動向を意外なところから取り上げ学ぶことで“気付く力”を伸ばすブログです。目下、データ分析・語学に力点を置いています。今月(2022年10月)からは多忙につき、日々の投稿数を減らします。

MENU

プログラムとしてのRを学ぶ(その07/16)

 \mathrm{R}をプログラムとして見たときに注意・検討すべきところを学んでおきたい、ということで

を読んでいく。

7. プログラミング構造

 プログラミング言語としての\mathrm{R}言語の基本構造を扱う。

7.1 制御文

7.1.1 ループ

 \mathrm{R}では\mathrm{for}文でのループに加え、\mathrm{break}文でループを抜ける条件を加えた\mathrm{while}文および\mathrm{repeat}文を利用できる。
 ループで別途便利なのが、\mathrm{next}文である。これによりループの現在の反復の残りを飛ばして次の反復へ進むことができる。

7.1.2 非ベクトルに対するループ

 \mathrm{R}において非ベクトルに対する反復は以下のような方法がある:

  • \mathrm{lapply}()を用いる。ループの反復が互いに独立していれば任意の順序で実行できる
  • \mathrm{get}()を用いる。
# 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 算術演算子とブール演算子

演算
説明
\mathrm{x}%%\mathrm{y} 剰余
\mathrm{x}%/%\mathrm{y} 整数除算
\mathrm{x==y} 等価性のチェック
\mathrm{x\leq y} 以下のチェック
\mathrm{x\geq y} 以上のチェック
\mathrm{x}&&\mathrm{y} スカラに対する論理積
\mathrm{x}||\mathrm{y} スカラに対する論理和
\mathrm{x}&\mathrm{y} ベクトルに対する論理積
\mathrm{x}\mathrm{y} ベクトルに対する論理和
\mathrm{!x} ブール否定

7.3 引数のデフォルト値

 関数の引数にはデフォルト値を与えることができる。デフォルト値を与えている変数は、呼び出すときにその変数に引数を与えなくても、デフォルト値が与えられたものとして扱う。

7.4 戻り値

 関数の戻り値は任意のオブジェクトにすることができる。複数の戻り値がある場合には、リストなどに戻り値を配置する。
 \mathrm{R}の慣例では、\mathrm{return}()の呼び出しを避けるのが支配的である。それは、呼び出すと実行時間が長くなるためである。

# 戻り値は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 関数はオブジェクト

 \mathrm{R}関数は\mathrm{function}クラスのオブジェクトであり、他のオブジェクトと同様にほとんどの場所で利用できる。
 \mathrm{function}()は2つの引数を持つ。1つ目は作成している関数の仮引数リストであり、2つ目は関数本体([tex:\mathrm{expression}クラス)である。
 関数はオブジェクトであるから、関数を引数として新たに関数を定義することも、複数の関数を要素に持つリストをループすることもできる。

g <- function(x) x^2
h <- function(g,x) g(x)
h(g,2)

 関数\mathrm{formal}()および\mathrm{body}()も置換関数に利用できる。

g <- function(x){}

body(g) <- quote(2*x + 3) # gのbodyを{return(2*x+3)}とする:quote()とすること

g
g(3)

7.6 環境とスコープの問題

 関数(\mathrm{R}のマニュアルではクロージャという。)の構成要素には、引数および本体に加え、環境がある。

7.6.1 トップレベル環境

 関数\mathrm{f}()はトップレベル(インタープリタコマンドプロンプト)で作成されているため、トップレベル環境(\mathrm{R}_\mathrm{GlobalEnv})を持つ。

# 環境を調べる
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 ポインタはあるか?

 \mathrm{R}にはポインタや参照に当たる変数は存在しない。これにより、たとえば\mathrm{R}では引数を直接する操作はできない。一例として、\mathrm{Python}であれば、以下のように直接的に引数を操作できる。

x = [13, 5, 12]
x.sort()
print(x)

 しかし\mathrm{R}ではこれができず、代わりに再割り当てを行う。

x <- c(13,5,12)
sort(x)
x # x自体は何も変わっていない

7.8 上位レベルへの書き込み

 環境階層の或るレベルに存在するコードは、そのレベル以上のすべての変数を読み取ることができる一方で、標準的な <- 演算子を用いて上位レベルの変数に直接書き込むことは出来ない。
 グローバル変数に書き込みたければ、スーパーアサインメント演算子<<-もしくは\mathrm{assign}()関数を用いる。

7.8.1 スーパーアサインメント演算子を用いた書き込み

 例として以下を考える。

two <- function(u){
  u <<- 2*u
  z <- 2*z
}

x <- 1
z <- 3

print(u)
two(x)
z
u

このとき、

\mathrm{x} \mathrm{x}\mathrm{two}()の実引数だが、呼出し後も値は1のままである。なぜならば、この値は仮引数\mathrm{u}にコピーされ、関数内では\mathrm{u}はローカル変数として扱われる。このため\mathrm{u}が変更されても\mathrm{x}は変更されない。
\mathrm{z} 2つの\mathrm{z}は互いに全く関係がない。1つはトップレベルであり、もう1つは\mathrm{two}()のローカルである。ローカル変数への変更はグローバル変数には影響しない。
\mathrm{u} \mathrm{u}は、\mathrm{two}()を呼び出す前にはトップレベル変数としては存在していない。そのためエラーメッセージが発生する。しかし\mathrm{two}()内のスーパーアサインメント演算子でトップレベル変数として作成され、これは\mathrm{two}()の呼び出し後に確認できる。

である。

7.8.2 assign()を用いた書き込み

 \mathrm{assign}()関数を用いて上位レベルの変数に書き込むこともできる。

two <- function(u){
  assign("u",2*u,pos = .GlobalEnv)
  z <- 2*z
}

x <- 1
z <- 3

two(x)
x
u
7.8.3 グロ-バル変数を扱うタイミング

 グローバル変数はよく全面禁止することを推奨する議論があるが、実際には\mathrm{R}においては柔軟に利用される場合がある。たとえばリストの格納値に関数を適用する際、グローバル変数を使った方が分かりやすくなる場合がある。

# 分かりにくい例

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 再帰

 再帰関数は自分自身を呼び出す。再帰の大まかな考え方は以下のとおりである:

再帰関数\mathrm{f}()を用いたタイプ\mathrm{X}の問題の解決方法

  • タイプ\mathrm{X}の元々の問題を1つ以上のタイプ\mathrm{X}の小さな問題に分割する
  • \mathrm{f}()のなかで小さな問題それぞれに対して\mathrm{f}()を呼び出す
  • \mathrm{f}()の中で上記の結果を統合して元の問題を解決する
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 置換関数

 \mathrm{r}には関数の呼び出しの結果に値を割り当てることができる場合がある。

names(vector_x) <- c("a","b","ab")
# 上記は以下に等しい
x <- "names<-"(x,value = c("a","b","ab"))

7.11 関数コードを作成するためのツール

 一時的に必要な短い関数を記述するための簡単な方法を考える。

7.11.1 テキストエディタ統合開発環境

 テキストエディタなどではファイルから関数を読み込むのに\mathrm{source}()関数を用いればよい。

7.11.2 edit()関数

 また\mathrm{edit}()関数を用いてコードを編集して再割り当てすることもできる。

f1 <- edit(f1)

7.12 独自定義の二項演算

 独自の演算子を定義することもできる。

"%a2b%" <- function(a,b) return(a + 2*b)
3 %a2b% 5

7.13 匿名関数

 関数オブジェクトは、割り当てをしなくても利用できる。このときその関数オブジェクトを匿名関数と呼ぶ。\mathrm{apply}()関数を用いる際などには匿名関数を用いればよい。

プライバシーポリシー お問い合わせ