はじめての R プログラミング

この Notebook は,

のプログラミング部分に相当する内容を R を使って書いてみたものです。

R は統計解析向けのプログラミング言語です。この Notebook では,プログラミング言語としての共通部分に焦点を絞って解説します。R 本来の得意分野である統計解析の分野は省いていますので,R そのものの入門というよりは,Jupyter Notebook での R の使用例として見ていただければと思います。

RStudio や R commander (Rcmdr) を駆使している方がほとんどかと思いますが,Jupyter Notebook でもできることをご確認いただければと思います。

では,はじめましょう。

はじめてのプログラミング

エンゲル係数とは,家計の消費支出に占める飲食費(食料費,食費)の割合(パーセント単位)です。

消費支出 100,000円,飲食費 40,000円の人のエンゲル係数を求める例。R では以下のような電卓的な使い方ですぐ答えが出ます。

本稿のように Jupyter Notebook 環境では,以下の1行を書いたあとに,Shift キーを押しながら Enter キー(または Return キー)を押して実行します。

In [1]:
40000 * 100/100000
40

ここからは,単に電卓的に使うだけではなく,応用・発展も見据えて以下のようなプログラム文を作成して実行することにします。

以下の例では,

  1. 1行目で変数 s1 に消費支出である 100000(円)という数値を代入し,
  2. 2行目で変数 s2 に飲食費である 40000(円)という数値を代入し,
  3. 3行目でエンゲル係数を計算して,変数 x1 に代入し,
  4. 4行目の print() 関数で表示させています。

R では代入演算子は <- (または =)を使うようです。

In [2]:
s1 <- 100000
s2 <- 40000
x1 <- s2*100/s1
print(x1)
[1] 40

エラーとデバグ

意に反して,プログラムがうまく動作しない場合があります。たとえば,上記の 3行目を以下のように書いた場合。

掛け算を示す演算子は *,割り算を示す演算子は / であるところを誤って,×÷ と書くと,これらの記号は R では演算子として定義されていないのでエラーとなります。このようなエラーは文法エラー と呼ばれ,Jupyter Notebook では,以下のように誤りやその箇所を指摘してくれます。

In [3]:
s1 <- 100000
s2 <- 40000
x1 <- s2 × 100 ÷ s1
print(x1)
Error in parse(text = x, srcfile = src): <text>:3:10:  想定外の入力です 
2: s2 <- 40000
3: x1 <- s2 ×
            ^
Traceback:

別の種類の誤りもあります。たとえば,「100」を「1000」と入力した場合...

In [4]:
s1 <- 100000
s2 <- 40000
x1 <- s2*1000/s1
print(x1)
[1] 400

上記の例では Error (文法エラー)の警告が出ませんが,答えは明らかに変です。(エンゲル係数の最大値は100だから。)

このような場合はコンピュータにとって誤りではなく,指示通りに計算し,その結果を表示します。 このようなプログラム作成者の不注意や勘違いによって生じるエラーには注意が必要です。

プログラムが正しく動作しない場合は,誤りの箇所を探し,修正して再度動作を確認します。正しく動作するようになるまで,プログラムを修正し,実行(Shift + Enter)します。この作業をデバグといいます。

データの型と変数

上記の例の 1行目では,s1 という名前の変数に 100000 という 数値を代入しています。変数名はアルファベットで始まる英数字列にします。大文字と小文字は区別されます。

変数 ss に文字列を代入する場合は,以下のように " で囲みます。

In [5]:
ss <- "エンゲル係数"
print(ss)
[1] "エンゲル係数"

式と演算

掛け算を示す演算子は *,割り算を示す演算子は /。足し算は +,引き算は - です。

文字列の連結には + ではなく,paste() 関数または cat() 関数を使います。以下の例を参照。

In [6]:
x0 <- "エンゲル"
y0 <- "係数"
paste(x0, y0, sep="")
cat(x0, y0, sep="")
'エンゲル係数'
エンゲル係数

参考:+ の再定義で文字連結

どうしても + で文字列連結をしたい!という場合には,既存の + 演算子を以下のように再定義するという方法もあるようです。

In [7]:
"+" <- function(e1, e2) {
    if (is.character(c(e1, e2))) {
        paste(e1, e2, sep = "")
    } else {
        base::"+"(e1, e2)
    }
}
In [8]:
x0 + y0
x0 + 2021
'エンゲル係数'
'エンゲル2021'

ですが,思わぬ副作用もありそうなので,元に戻しておきましょう。

In [9]:
rm("+")

文字列と数値を連結するときも,paste() 関数を使います。

In [10]:
s1 <- 100000
s2 <- 40000
x1 <- s2*100.0/s1
z1 <- paste("エンゲル係数は", x1, "です。")
cat(z1)
エンゲル係数は 40 です。

主な演算子

加減乗除の演算子 +, -, *, /

優先的に計算したい箇所は丸括弧 ( )で グループ化します。

In [11]:
2 + (50 - 5 * 6)/4
7

除算 / は常に浮動小数点数を返します。 冪乗は ^ です。(** も可。)

In [12]:
17 / 3
2^4
2**4
5.66666666666667
16
16

対話型プログラム

上記の例では,プログラムの中で消費支出や飲食費を決めていましたが,今度はプログラムを実行する人が入力できるようにしてみます。

readline() 関数は入力された「文字列」を返すので,数値として計算するために as.numeric() で数値(浮動小数点数)に変換します。

In [13]:
s1 <- readline("消費支出? ")
s2 <- readline("飲食費? ")
x1 <- as.numeric(s2)*100/as.numeric(s1)
cat("エンゲル係数は", x1, "です。")
消費支出? 100000
飲食費? 40000
エンゲル係数は 40 です。

関数の定義と呼び出し

(これまでの例では1回しか計算していませんが)よく使う計算を「関数」として定義する例です。

以下の例では,1行目から4行目でエンゲル係数を計算する関数 calc() を定義し,8行目でその関数を呼び出しています。

# から始まる行はコメント(実行に影響しない注釈)です。

In [14]:
calc <- function(s1, s2){
    # 消費支出 s1 円と飲食費 s2 円からエンゲル係数を計算する。
    return(as.numeric(s2)*100/as.numeric(s1))
}

s1 <- readline("消費支出? ")
s2 <- readline("飲食費? ")
x1 <- calc(s1, s2)
paste("エンゲル係数は", x1)
消費支出? 120000
飲食費? 54000
'エンゲル係数は 45'

いったん定義された関数は,以下のように何回でも利用できます。

In [15]:
calc(120000, 54000)
45

(練習)

消費支出を入力すると,エンゲル係数が 20 ~ 40 となる飲食費を表示するプログラムをつくりなさい。

組み込み関数を使う

関数は上記の例のように自分で定義することもできますが,あらかじめ用意されている「組み込み関数」を使うこともできます。

以下では,組み込み関数の例として,

  • それ以下の整数を返す floor()
  • 整数部分を返す trunc()

を使用しています。細かい話ですが floor()trunc() では負の実数について取り扱いが異なることがわかります。

  • ちなみに,小数点以下を切り上げる関数は ceiling() です。
In [16]:
floor(12.3)
trunc(12.3)
floor(-12.3)
trunc(-12.3)
12
12
-13
-12

エンゲル係数を計算する部分はすでに関数 calc() として定義していますが,プログラム全体を関数 engel() として定義してしまいます。

In [17]:
engel <- function(){
    s1 <- readline("消費支出? ")
    s2 <- readline("飲食費? ")
    x1 <- calc(s1, s2)
    cat("エンゲル係数は", floor(x1), "です。(小数点以下切り捨て)")
}
In [18]:
engel()
消費支出? 130000
飲食費? 43000
エンゲル係数は 33 です。(小数点以下切り捨て)

(練習)

小数点以下を切り上げたエンゲル係数の値を表示させるように上記のプログラムを変更しなさい。

参考:数値の丸めと四捨五入

R の組み込み関数に round() があります。数値を丸める関数ですが,いわゆる四捨五入とはちょっと違うようです。

以下の例からわかるように,小数点以下が 0.5 未満の場合と 0.5 を超える場合は四捨五入と同様の丸め方ですが,ちょうど 0.5 のときは,偶数に丸められていることがわかります。

これを偶数への丸めといい,端数が 0.5 より小さいなら切り捨て,端数が0.5 より大きいなら切り上げ,端数がちょうど 0.5 なら切り捨てと切り上げのうち結果が偶数となる方へ丸めます。

In [19]:
cat(round(12.49), round(12.50), round(12.51), sep=",   ")
12,   12,   13
In [20]:
cat(round(13.49), round(13.50), round(13.51), sep=",   ")
13,   14,   14

(練習)

四捨五入した値を表示する関数をつくりなさい。

条件分岐

条件分岐とは,例えばエンゲル係数の値によって実行文を変えることです。例を示します。

関数 engel() を以下のように変更します。

In [21]:
engel <- function(){
    s1 <- readline("消費支出? ")
    s2 <- readline("飲食費? ")
    x1 <- calc(s1, s2)
    if (x1 >= 80) {
        cat("エンゲル係数は", round(x1), "です。飲食費を使いすぎです。")
    } else {
        cat("エンゲル係数は", round(x1), "です。") 
    }
}
In [22]:
engel()
消費支出? 100000
飲食費? 85000
エンゲル係数は 85 です。飲食費を使いすぎです。
In [23]:
engel()
消費支出? 100000
飲食費? 35000
エンゲル係数は 35 です。

条件分岐の if 文の書式は以下のようになっています。

if (条件式 1) {
    条件式 1 が満たされた場合に実行する文
    } else if (条件式 2) { 
    条件式 2 が満たされた場合に実行する文
    } else {
    それ以外の場合に実行する文
    }

(練習)

上記の条件分岐を,エンゲル係数が

  1. 80 以上の場合には「飲食費を使いすぎです。」
  2. 80 未満 40 以上の場合には「正常です。」
  3. 40 未満の場合には「もっと食べましょう。」

と表示させるように変更しなさい。

繰り返し処理

while

以下の例では,1行目で代入された消費支出に対してエンゲル係数を計算します。

1回だけ計算して表示するのではなく,2行目の飲食費の初期値(ここでは 40000 円)から,5行目の条件式(ここでは 80000 円以下)が成り立つ場合に,8行目にあるように 5000 円ずつ増やしながらエンゲル係数を表示します。

In [24]:
s1 <- 100000
s2 <- 40000
cat("消費支出", as.integer(s1), "円に対するエンゲル係数の値 \n")

while (s2 <= 80000) {
    x1 = calc(s1, s2)
    cat("   飲食費", s2, "円の場合: ", x1, "\n")
    s2 <- s2 + 5000
}
消費支出 100000 円に対するエンゲル係数の値 
   飲食費 40000 円の場合:  40 
   飲食費 45000 円の場合:  45 
   飲食費 50000 円の場合:  50 
   飲食費 55000 円の場合:  55 
   飲食費 60000 円の場合:  60 
   飲食費 65000 円の場合:  65 
   飲食費 70000 円の場合:  70 
   飲食費 75000 円の場合:  75 
   飲食費 80000 円の場合:  80 

while 文の書式は以下のとおりです。

while (条件式){
    条件式が成り立つ場合に実行される文
}

for

同様の繰り返し処理を for 文を使って行うこともできます。

for 文の書式は以下のとおりです。

for (i in istart:iend) {
    i が istart から iend までの間,実行される文
    }

istart から iend まで増分 istep ごとにする場合は,seq() 関数を使って

for (i in seq(istart, iend, istep)) {
    i が istart から iend までの間,実行される文
    }
In [25]:
s1 <- 100000
cat("消費支出", as.integer(s1), "円に対するエンゲル係数の値 \n")

for (s2 in seq(40000, 80000, 5000)) {
    x1 = calc(s1, s2)
    cat("   飲食費", s2, "円の場合: ", x1, "\n")
}
消費支出 100000 円に対するエンゲル係数の値 
   飲食費 40000 円の場合:  40 
   飲食費 45000 円の場合:  45 
   飲食費 50000 円の場合:  50 
   飲食費 55000 円の場合:  55 
   飲食費 60000 円の場合:  60 
   飲食費 65000 円の場合:  65 
   飲食費 70000 円の場合:  70 
   飲食費 75000 円の場合:  75 
   飲食費 80000 円の場合:  80 

(練習)

以下の値を求めるプログラムを作成せよ。

$$\sum_{n = 1}^{10} n^2$$

参考:上記の(練習)の別解

上記の(練習)は,while または for を使った繰り返し処理の練習を想定しているが,R ではベクトルを使って以下のように解くことができる。

In [26]:
# ベクトルをつくる
a <- c(1:10)
a
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
In [27]:
# ベクトルの各成分を2乗して和をとる
sum(a^2)
385

おまけ

ちょっとだけ R 的な例

In [28]:
身長 <- c(168.5, 172.8, 159.0)
体重 <- c(69.5, 75.0, 56.5)
X <- data.frame(身長, 体重)
X
A data.frame: 3 × 2
身長体重
<dbl><dbl>
168.569.5
172.875.0
159.056.5
In [29]:
X$性別 <- c("男", "男", "女")
X
A data.frame: 3 × 3
身長体重性別
<dbl><dbl><chr>
168.569.5
172.875.0
159.056.5
In [30]:
X$BMI <- round(X$体重 / (X$身長 / 100) ^ 2)
X
A data.frame: 3 × 4
身長体重性別BMI
<dbl><dbl><chr><dbl>
168.569.524
172.875.025
159.056.522

棒グラフも描けます

In [31]:
x <- rnorm(1000000)
hist(x)