3 Rの制御構造

3.1 概要

このページでは、

について解説します。

3.2 if文

データ分析では、データの状況に応じて処理の流れを変えたい場面が数多くあります。Rでは「if文」を使うことで、プログラムに「もし~なら、こうしなさい」という条件分岐の指示を与えることができます。そのためにif, else、そしてifelseといった記号を使います。

3.2.1 if文:もし条件が真(TRUE)なら実行

if文は最も基本的な条件分岐の仕組みです。「指定した条件が満たされている場合のみ、特定の処理を実行したい」ときに使います。

基本的な構文:

if (条件式) {
  # 条件式が TRUE と評価された場合にのみ実行される処理
}
  • 条件式には、TRUE または FALSE (を返す条件式)を書きます。
  • 条件式が TRUE の場合だけ、{} ブロックで囲まれた中の処理が実行されます。
  • 条件式が FALSE の場合、{} の中の処理は無視(スキップ)されます。

具体例:

x = NA
y = 10
if (y == 10){
  x = 1
}

yの値が 10 の場合は、x に 1 を代入するという処理です。条件が満たされない場合はif文の{}内は実行されません。

以下の例では、データフレームに特定の名前の列があるかどうかを判定し、無ければそれを追加します。

data = data.frame(id = 1:3, price = c(150, 200, 120))
print(data)
##   id price
## 1  1   150
## 2  2   200
## 3  3   120

# amountという名前の列がなければそれを追加
if (!("amount" %in% colnames(data))) {
  data$amount = NA
}
print(data)
##   id price amount
## 1  1   150     NA
## 2  2   200     NA
## 3  3   120     NA

上記の条件式では、colnames()関数を使ってデータフレームに含まれる列名のベクトルを取得し、"amount"という値がその中にあるかを%in%演算子を使って判定しています。"amount" %in% colnames(data)だけだと、"amount"が存在する場合にTRUEを返します。そこで式全体に!を追加して、"amount"が存在しない場合にTRUEとなるようにします。

比較演算子

上記の例の==%in%などは比較演算子と呼ばれ、if文の条件式などでよく使われる。Rで用いられる比較演算子には以下のようなものがある。

Table 2.1: Rの比較演算子
演算子 構文 意味
> x > y xはyより大きいか
>= x >= y xはy以上か
< x < y xはyより小さいか
<= x >= y xはy以下か
== x == y xとyは等しいか
!= x != y xとyは等しくないか
%in% x %in% y xはyに含まれるか

以下は具体例。

x = 3
y = 5

y >= x
## [1] TRUE

x <= 1
## [1] FALSE

x == 3
## [1] TRUE

x == 3.0
## [1] TRUE

3 == 6/2
## [1] TRUE

"AAA" == "X"
## [1] FALSE

"AAA" != "X"
## [1] TRUE

a = 5
b = c(3, 4, 5, 6, 7)
a %in% b
## [1] TRUE

x = c("Bad", "Good", "Excellent")
x = factor(x, ordered = T, levels = c("Bad", "Good", "Excellent"))
x[1] < x[2]
## [1] TRUE

3.2.2 if…else文:条件が真(TRUE)か偽(FALSE)かで処理を分ける

「条件が満たされている場合はAの処理、満たされていない場合はBの処理」というように、2つの選択肢のどちらかを実行したい場合はelseを使います。

基本的な構文:

if (条件式) {
  # 条件式が TRUE の場合に実行される処理
} else {
  # 条件式が FALSE の場合に実行される処理
}
  • if の条件式が TRUE なら1つ目の{}内の処理 が実行されます。
  • if の条件式が FALSE なら else に続く2つ目の{}内の処理が実行されます。

具体例:

x = NA
y = 10
if (y == 10){
  x = 1
} else {
  x = 0
}

yの値が 10 の場合は、x に 1 を代入し、そうでない場合は 0 を代入する処理です。

別の例です。

condition = FALSE
data = data.frame(x = c(NA, NA, NA))
if (condition){
  data$x = 100 * 1:3
} else {
  data$x = 200 * 1:3
}
print(data)
##     x
## 1 200
## 2 400
## 3 600

condition という変数に論理値を入れておき、その変数の値を条件分岐に利用します。

3.2.3 else if文:3つ以上の選択肢で処理を分ける

「Aの場合は処理1、そうではなくBの場合は処理2、どちらでもない場合は処理3」のように、3つ以上の選択肢で処理を分けたい場合はelse ifを使います。

基本的な構文:

if (条件式1) {
  # 条件式1が TRUE の場合の処理
} else if (条件式2) {
  # 条件式1が FALSE で、かつ条件式2が TRUE の場合の処理
} else {
  # 上の全ての条件式が FALSE だった場合の処理
}
  • 条件は上から順番に評価されます。
  • 最初に TRUE となったブロックの処理だけが実行され、残りの else if や else の中にある処理は無視されます。

4つの条件に分岐させる場合は以下のようにします。else ifのブロックを増やします。

if (条件式1) {
  # 条件式1が TRUE の場合の処理
} else if (条件式2) {
  # 条件式1が FALSE で、かつ条件式2が TRUE の場合の処理
} else if (条件式3) {
  # 条件式1と条件式2が FALSE で、かつ条件式3が TRUE の場合の処理
} else {
  # 上の全ての条件式が FALSE だった場合の処理
}

具体例を以下に示します。製品データを評価し、状況に応じて4種類の異なる優先度(priority)を付与しています。

data = data.frame(
  id = "P-101", # 製品ID
  sales = 1200, # 売上個数
  stock = 30 # 在庫数
)

if (data$sales >= 1000 & data$stock <= 50) {
  # 条件1: 売上が1000個以上 かつ 在庫が50個以下
  data$priority = "Urgent Restock"  # 緊急補充
} else if (data$sales >= 1000) {
  # 条件2: (上記以外で) 売上が1000個以上
  data$priority = "High Performer" # 人気商品
} else if (data$stock <= 50) {
  # 条件3: (上記以外で) 在庫が50個以下
  data$priority = "Monitor Inventory" # 在庫監視
} else {
  # 条件4: 上記のどの条件にも当てはまらない場合
  data$priority = "Standard" # 標準
}

print(data)
##      id sales stock       priority
## 1 P-101  1200    30 Urgent Restock

3.2.4 ifelse()関数:データ列全体に一括で条件分岐を適用する

if文は一度に1つの条件しか評価できません。データフレームの各行に対して同じ条件で判定を行い、その結果を新しい列として追加したい場合、ifelse()関数が非常に便利です。

基本的な構文:

ifelse(条件式, 条件がTRUEの時の値, 条件がFALSEの時の値)
  • if文とは異なり、これは関数です。
  • ベクトル(データフレームの列など)を対象に、要素ごとに条件を判定できます。
  • 結果として、判定結果が格納された新しいベクトルを返します。

具体例:

# 単一の値を判定する例
x = 1
y = ifelse(x == 1, 10, 20)
print(y)
## [1] 10

# ベクトルを判定する例
x = c(1, 2, 3, 4, 5)
y = ifelse(x > 3, 1, 0)
print(y)
## [1] 0 0 0 1 1

# データフレームの列を判定する例
data = data.frame(
  id = 1:5,
  score = c(95, 72, 88, 55, 81)
)
data$result = ifelse(data$score >= 60, "合格", "不合格")
print(data)
##   id score result
## 1  1    95   合格
## 2  2    72   合格
## 3  3    88   合格
## 4  4    55 不合格
## 5  5    81   合格

以下のように、if文の中身が1行だけの場合などは、ifelse関数を使うことで短くコードを書けて便利です。

# if文を使う場合
x = 1
y = NA
if(x == 1){
  y = 10
}

# ifelse文を使って上と同じ処理を書いた例
x = 1
y = ifelse(x == 1, 10, NA)

なお、ifelse文は条件式がTRUEかどうかで2種類に処理を条件分けできる関数ですが、以下のようにifelse文をネストすることで3条件での分岐を作ることもできます。

ifelse(条件1, 条件1がTRUEの時, ifelse(条件2, 条件2がTRUEの時, 条件2がFALSEの時))

条件1がFALSEの場合の箇所にifelse文を入れればよいということです。以下は具体例です。

x = 2
y = ifelse(x == 1, 11, ifelse(x == 2, 13, 17))
print(y)
## [1] 13

xが1ならyは11、xが2ならyは13、xが1と2以外ならyは17という条件分岐が1行で書けました。

3.3 for文

for文を使うことで、一定の作業を繰り返し実行することができます。for文は「指定した一連のデータ(シーケンス)の要素を、一つずつ取り出して処理を繰り返す(ループする)」という仕組みです。

基本的な構文:

for (変数 in シーケンス) {
  # シーケンスの各要素を使って繰り返したい処理
  # '変数'には、繰り返しの度にシーケンスの要素が一つずつ順に使用される
}
  • 変数: forループの各回で、シーケンスから取り出された要素を一時的に格納しておくための変数。この変数名は自由に決めることができます。
  • シーケンス: 繰り返しの対象となるベクトルやリスト。例:1:5c("A", "B", "C")など。

簡単な具体例を以下に示します。

for (i in 1:5) {
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

data = c(10, 20, 30)
for (num in data) {
  print(num)
}
## [1] 10
## [1] 20
## [1] 30

ループの各回ごとにシーケンスの値が順に変数に代入され、その変数を使って{}内の処理が実行されます。

以下の例では、データフレームの列ごとにfor文で平均値を求める処理をしています。複数の科目(math, english, history)の点数が入ったデータフレームから各科目の平均点を計算し、別のデータフレームに結果をまとめています。

data = data.frame(
  student_id = c("S01", "S02", "S03", "S04", "S05"),
  math = c(85, 92, 78, 88, 76),
  english = c(72, 88, 95, 81, 79),
  history = c(68, 75, 81, 72, 85)
)
subject_means = data.frame()

for (subject in c("math", "english", "history")) {
  # 現在の科目(subject)の平均点を計算
  current_mean = mean(data[[subject]])
  # 科目名と計算した平均点を一時的なデータフレームにする
  temp_result = data.frame(subject_name = subject, average_score = current_mean)
  # 結果を格納用のデータフレームに追記していく
  subject_means = rbind(subject_means, temp_result)
}

print(subject_means)
##   subject_name average_score
## 1         math          83.8
## 2      english          83.0
## 3      history          76.2

subject という変数に科目名の文字列を入れています。列名の文字列を使ってデータフレームの列を取り出すためには2重の角括弧[[]]を使います。

3.4 while文

for文は「決まった回数」だけ処理を繰り返します。しかし、分析の中には「何回繰り返すかは分からないが、ある条件を満たすまで処理を続けたい」という場合があります。

例えば、「シミュレーションで特定の結果が出るまで試行を繰り返す」「データの合計値が目標を超えるまでデータを追加し続ける」といった場面です。このような、「終了条件」は決まっているが繰り返し回数が不定の場合に活躍するのがwhile文です。

基本的な構文:

while (条件式) {
  # 条件式が TRUE の間、ずっと繰り返される処理
  # 注意:ループ内で、いつか条件式が FALSE になるような操作が必要
}
  • 条件式: ループを続けるかどうかを判定する式。この式がTRUEを返す限り、{}の中の処理が何度も実行されます。

while文で最も注意すべきは「無限ループ」です。これは、()内の条件式がずっとにTRUEのままになり、while文の中の処理が無限に繰り返されて、プログラムが終わらなくなってしまう状態です。無限ループを防ぐには、ループ処理の中で条件式に関わる変数を変化させ、必ずいつかは条件がFALSEになるように設計する必要があります。

次の例では、変数 i の値が5未満である間、iの値を表示し、i を1ずつ増やしていきます。

i = 1
while (i < 5) {
  print(paste("現在のiの値は", i, "です。"))
  i = i + 1
}
## [1] "現在のiの値は 1 です。"
## [1] "現在のiの値は 2 です。"
## [1] "現在のiの値は 3 です。"
## [1] "現在のiの値は 4 です。"

ループごとに i の値を増やしていき、i が5になった時点でwhile文を抜けてプログラムが終了します。

上の例のi = i + 1という行をコメントアウトまたは削除して実行すると、無限ループになりプログラムが終了しなくなります。無限ループが発生した場合はConsole欄でキーボードのEsc(エスケープ)キーを押すか、またはRStudio上のストップボタン(Console欄に表示される赤色のボタン)を押すとプログラムを止めることができます。

次の例では、「日々の売上データをランダムに生成し、累計売上が目標の1000円を超えるまでデータを追加し続ける」というシミュレーションをwhile文で行っています。

target = 1000  # 目標金額
total = 0      # 累計売上の初期値
day = 0        # 経過日数のカウンター

while (total < target) {
  day = day + 1
  sales = runif(1, 50, 250)
  total = total + sales
  print(paste("Day", day, ":", "累計 =", round(total, 0), "円"))
}
## [1] "Day 1 : 累計 = 244 円"
## [1] "Day 2 : 累計 = 465 円"
## [1] "Day 3 : 累計 = 520 円"
## [1] "Day 4 : 累計 = 746 円"
## [1] "Day 5 : 累計 = 903 円"
## [1] "Day 6 : 累計 = 967 円"
## [1] "Day 7 : 累計 = 1058 円"

print(paste("目標達成までにかかった日数:", day, "日"))
## [1] "目標達成までにかかった日数: 7 日"
print(paste("最終的な累計売上:", round(total, 2), "円"))
## [1] "最終的な累計売上: 1058.35 円"

この例では、その日の売上を50円から200円の範囲でランダムに生成しているので、何日で目標を達成できるかは事前に分かりません。しかし「累計売上が1000円を超える」という終了条件は明確です。whileループの中でtotalの値を毎回更新することで、累計売上が目標金額以上になったらwhile文から抜けます。

3.5 breakとnext

for文やwhile文を使って繰り返し処理をする際に、ループの途中で流れを細かく制御したくなることがあります。その際に用いられるのがbreaknextです。

3.5.1 ループを途中で完了する break

for文やwhile文のループ処理を強制的に終了させるためにbreakを使います。特定の条件が満たされたらそれ以上ループを続ける必要がない場合に便利です。

具体的な使用例としては、「もしこの条件になったらループを中断する」という形でif文と組み合わせて使うといったケースなどがあります。

以下の例では、データの値を順に表示していき、欠損値が見つかったらその時点で作業を終了します。

data = c(22.1, 22.3, 21.9, NA, 22.5, 22.1)
for (i in 1:length(data)) {
  if (is.na(data[i])) {
    print(paste0(i, "つ目のデータに欠損値が見つかりました。"))
    break
  }
  print(paste0(i, "つ目のデータ: ", data[i]))
}
## [1] "1つ目のデータ: 22.1"
## [1] "2つ目のデータ: 22.3"
## [1] "3つ目のデータ: 21.9"
## [1] "4つ目のデータに欠損値が見つかりました。"

iが4の時、is.na()の条件が TRUE になったため、break が実行されました。その結果、5, 6個目のデータは処理されることなくループが終了しているのが分かります。

3.5.2 今回の処理だけをスキップする next

next は、ループ処理を中断するのではなく、現在の回の処理だけをスキップして次の回の処理に進ませるために使います。if文と組み合わせて、「もしこの条件になったら、今回の処理はここでやめて、次の要素の処理を始める」という形で使います。

以下の例では、売上データの中にマイナスの値が含まれています。nextを使い、この負の値は無視(スキップ)してプラスの値だけを合計しています。

sales = c(100, 250, -50, 120, -20, 300)
total = 0
for (s in sales) {
  if (s < 0) {
    next
  }
  total = total + s
  print(paste0(s, "円を加算しました。現在の合計: ", total, "円"))
}
## [1] "100円を加算しました。現在の合計: 100円"
## [1] "250円を加算しました。現在の合計: 350円"
## [1] "120円を加算しました。現在の合計: 470円"
## [1] "300円を加算しました。現在の合計: 770円"
print(paste0("最終的な合計売上: ", total, "円"))
## [1] "最終的な合計売上: 770円"

売上データが -50 と -20 だった回では、if (s < 0) の条件が TRUE になったため next が実行されます。これにより、total への加算処理や print 処理がスキップされ、次の回の処理に移っているのが分かります。

3.6 apply系の関数

for文はプログラミングの基本ですが、Rには繰り返し処理をより簡潔に書くためのapplyファミリーと呼ばれる一連の関数が用意されています。

ここではその代表例として、行列やデータフレームの行または列に対して一括で関数を適用するapply関数を紹介します。

apply関数の基本的な構文:

apply(X, MARGIN, FUN)
  • X: 対象となる行列やデータフレーム
  • MARGIN: 1なら行方向、2なら列方向に関数を適用
  • FUN: 適用したい関数(mean, sum, maxなど)

具体例として、以前にfor文を使って書いた「各科目の平均点を計算する」コードを、applyを使って書き換えてみます。

data = data.frame(
  math = c(85, 92, 78, 88, 76),
  english = c(72, 88, 95, 81, 79),
  history = c(68, 75, 81, 72, 85)
)

# --- forループを使う方法 ---
target = c("math", "english", "history")
for (sub in target) {
  # 各列の平均値を計算して表示
  print(mean(data[[sub]]))
}
## [1] 83.8
## [1] 83
## [1] 76.2

# --- apply関数を使う方法 ---
# MARGIN=2で「列」ごとに、mean(平均)関数を適用する
subject_means = apply(data, 2, mean)
print(subject_means)
##    math english history 
##    83.8    83.0    76.2

for文で数行かかっていた処理が、apply関数なら1行で書くことができ、結果も分かりやすくまとまっています。

for文は柔軟性が高くどんな処理も書けますが、データフレームの列や行に同じ処理を適用するような定型的な作業はapply関数を使うとより簡潔なコードになります。まずはfor文を使えるようにし、慣れてきたらapplyファミリーの関数を使った方法を使ってみるとよいでしょう。

3.7 確認問題

3.7.1 問題

変数 x に 10、変数 y に 5 を代入します。 以下の2つの比較演算を実行した結果、コンソールにはそれぞれ何と表示されますか?

x > y
x == y
解答
x = 10
y = 5

# 1. x は y より大きいか?
print(x > y)
## [1] TRUE

# 2. x と y は等しいか?
print(x == y)
## [1] FALSE

Rにおいて>==などの比較演算子は、2つの値を比較し、その結果が正しいか(TRUE)間違っているか(FALSE)を返します。この TRUE や FALSE を真理値(または論理値)と呼びます。

  • x > yは「10 は 5 より大きい」という比較です。これは正しい(真)ため、TRUE が返されます。
  • x == y は「10 と 5 は等しい」という比較です。これは間違い(偽)ため、FALSE が返されます。

3.7.2 問題

変数 score に 85 を代入します。 もし score が 80 よりも大きい場合だけ、「合格です」とコンソールに表示する if 文を使ったスクリプトを書いてください。

解答
score = 85

if (score > 80) {
  print("合格です")
}
## [1] "合格です"

3.7.3 問題

変数 age に 18 を代入します。 if と else を使い、age が 20 以上なら「成人です」、そうでなければ(20 未満なら)「未成年です」と表示するスクリプトを書いてください。

解答
age = 18

if (age >= 20) {
  print("成人です")
} else {
  print("未成年です")
}
## [1] "未成年です"

3.7.4 問題

変数 temp(温度)に 28 を代入します。 以下の3つの条件で分岐するスクリプトを if, else if, else を使って書いてください。

  • もし temp が 30 以上なら「暑い」
  • そうではなく、もし temp が 20 以上なら「快適」
  • それ以外(20 未満)なら「寒い」
解答
temp = 28

if (temp >= 30) {
  print("暑い")
} else if (temp >= 20) {
  print("快適")
} else {
  print("寒い")
}
## [1] "快適"

3.7.5 問題

変数 score に 70 を代入します。ifelse()関数を使い、score が 60 以上なら変数 result に “Pass” という文字列を、60 未満なら “Fail” という文字列を代入してください。 最後に result の中身を表示してください。

解答
score = 70
result = ifelse(score >= 60, "Pass", "Fail")
print(result)
## [1] "Pass"

3.7.6 問題

for 文を使い、10 から 15 までの数字を順番にコンソールに表示する(print する)スクリプトを書いてください。

解答
for (i in 10:15) {
  print(i)
}
## [1] 10
## [1] 11
## [1] 12
## [1] 13
## [1] 14
## [1] 15

3.7.7 問題

以下の df というデータフレームがあります。for 文を使って、df の score 列(70, 85, 90)の値を1つずつ取り出し、その値に 10 を足した結果をコンソールに表示してください。

df = data.frame(
  name = c("A", "B", "C"),
  score = c(70, 85, 90)
)
解答
df = data.frame(
  name = c("A", "B", "C"),
  score = c(70, 85, 90)
)

# df$score は c(70, 85, 90) というベクトルを返します
for (s in df$score) {
  # s には 70, 85, 90 が順番に入る
  result = s + 10
  print(result)
}
## [1] 80
## [1] 95
## [1] 100

3.7.8 問題

変数 count に 1 を代入します。 while 文を使い、count が 5 以下である間、count の値を表示し、その後 count の値を 1 増やす処理を繰り返してください。

解答
count = 1

while (count <= 5) {
  print(count)
  count = count + 1
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

3.7.9 問題

for 文を使って 1 から 10 までの数をループ処理します。 もしループ変数 i の値が 5 になったら、「5が見つかりました」と表示して break を使いループを途中で終了させてください。(i が 5 になるまでは i の値を表示してください。)

解答
for (i in 1:10) {
  if (i == 5) {
    print("5が見つかりました")
    break
  }
  print(paste("i は", i))
}
## [1] "i は 1"
## [1] "i は 2"
## [1] "i は 3"
## [1] "i は 4"
## [1] "5が見つかりました"

3.7.10 問題

for 文を使って 1 から 5 までの数をループ処理します。 もしループ変数 i の値が 3 だった場合、next を使ってその回の処理をスキップしてください。(i が 3 でない場合は、i の値をコンソールに表示してください。)

解答
for (i in 1:5) {
  if (i == 3) {
    next
  }
  print(paste("i は", i))
}
## [1] "i は 1"
## [1] "i は 2"
## [1] "i は 4"
## [1] "i は 5"