2 控制結構與函數

本章為 Zamora Saiz et al. (2020) 節 2.3 與 2.4 內容。

2.1 Conditionals

條件句的語法如下:

if ("condition is satisfied") {
  "do something"
}

例如:

x <- 3
if (x > 0) {
  print("Positive")
}
## [1] "Positive"

如果我們希望在條件沒有滿足時執行其他動作,就得使用 else,其語法如:

if ("condition is satisfied"){ 
  "do something"
} else {
  "otherwise do something else"
}

例如:

x <- 0
if (x > 0) {
  print("Positive")
} else {
  print("Negative")
}

上述的 codes 其實在邏輯上是正確的,但顯然與我們希望電腦能做的事有落差(\(x=0\) 非正也非負才對)。所以,如果有好幾個條件要確認,也能多用幾個 else blocks,如:

x <- 0
if (x > 0) {
  print("Positive")
} else if (x < 0) {
  print("Negative")
} else {
  print("Zero")
}

不過,要注意的是上述的結構在 R 的運行速度較慢,不如以 ifelse() 函數,把上述的結構寫成一行,其語法即:

ifelse("condition", "task if TRUE", "task if FALSE")

前開判別變數為正或負的條件結構也能寫成:

x <- 9
ifelse(x > 0, "Positive", "Negative")
## [1] "Positive"

只要在向量使用 binary operator,同樣的條件就也可以被確認多次。例如如果要確認向量中的元素是否小於 5,我們可以寫成:

ifelse((1 : 10) < 5, "Fail", "Pass")
##  [1] "Fail" "Fail" "Fail" "Fail" "Pass" "Pass" "Pass" "Pass" "Pass" "Pass"

2.2 Loops

for 用於重複次數預先指定的時候,while 則反之,將持續運行直到條件無法滿足。

2.2.1 for 迴圈

for 迴圈的語法為:

for ("counter" in "vector of indices") { 
  "do something"
}

例如印出 1–10 可以如此:

for (i in 1 : 10) {
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10

因為 [1] 出現了十次,我們可知這段程式碼有十次輸出,每次輸出包含一個 row,顯然這是 print(i) 運行十次的結果。在此迴圈中,每次執行都會使 i 的值增加,所以如果我們此時在 console 輸入 i,將會回傳 10,顯示 i 作為一個變數,其值已經變成 10 了。

那如果我們要把連續的整數和存成一個向量該怎麼做呢?

v <- c()

s <- 0
for (i in 1 : 10) {
  s <- s + i
  v[i] <- s
}
v
##  [1]  1  3  6 10 15 21 28 36 45 55

其中,s 的作用就是在迴圈結束後儲存剛剛的結果,以便與下一個數相加。如此,可以依序將 \(1\)\(1+2\)\(1+2+3\)\(\cdots{}\)\(1+\cdots{}+10\) 存到向量 v 並印出。

以下還有幾個 for 迴圈運作的簡單範例。例如印出 1–19 的奇數,可以:

odd <- 2 * (1 : 10) - 1
for (i in odd) {
  print(i)
}
## [1] 1
## [1] 3
## [1] 5
## [1] 7
## [1] 9
## [1] 11
## [1] 13
## [1] 15
## [1] 17
## [1] 19

我們也可以把 for 迴圈與 if 條件句結合,要把某個向量中低於某個數字的元素印出 FAIL,如:

for (i in 1 : 10) {
  if (i < 5) {
    print("Fail")
  } else {
    print("Pass")
  }
}
## [1] "Fail"
## [1] "Fail"
## [1] "Fail"
## [1] "Fail"
## [1] "Pass"
## [1] "Pass"
## [1] "Pass"
## [1] "Pass"
## [1] "Pass"
## [1] "Pass"

2.2.2 while 迴圈

除了 for 迴圈以外,還有 while 迴圈,其語法為:

while ("condition holds") { 
  "do something"
}

以下為一個 while 迴圈的簡單範例:

i <- 0
while (i < 10) {
  print(c(i,"is less than 10"))
  i <- i + 1
}
## [1] "0"               "is less than 10"
## [1] "1"               "is less than 10"
## [1] "2"               "is less than 10"
## [1] "3"               "is less than 10"
## [1] "4"               "is less than 10"
## [1] "5"               "is less than 10"
## [1] "6"               "is less than 10"
## [1] "7"               "is less than 10"
## [1] "8"               "is less than 10"
## [1] "9"               "is less than 10"

第一圈 i 為 0,運行到 print() 時將會印出包含 “0” 與 “is less than 10” 元素的向量,而 i <- i + 1 相當於計數器,第一個迴圈結束後,變數 i 就會變成 1,以此類推。在第十個迴圈結束後,變數 i 將會變成 10,而就不符合 while 迴圈繼續執行的條件了,迴圈至此終止。

2.3 Functions and Operators

2.3.1 創建新函數

R 本身就有很多預設的函數,如 mean() 可以取平均,sum() 可以加總,sqrt() 可以計算數字的平方根,log()exp() 可以計算數字的對數值與指數值,sin()cos()tan() 可以計算三角函數值,is.logical() 可以確認型態是否是 logical。

如果要創建我們自己的函數,其語法為:

function.name <- function(argument1, argument2,...) {
  "body function"
}

函數的名字隨意,只要沒有與既存的函數同名或以某些不合法的方式命名即可;引述的數量可多可少;body function 則裝載執行程序,裡頭用的值由引數而來,最後一行命令則會回傳成輸出。

例如若想新建一個函數來協助計算 \(f(x, y)=x^2 - \frac{y}{5}\) 的話,可以

f <- function(x, y) {
  x ^ 2 - y / 5  # the output is the evaluation of last line
}

或者我們也可以寫成:

f <- function(x, y) x ^ 2 - y / 5

執行的最後一行預設就是輸出了,我們不需要寫出 return,但也能把它寫出來,如:

f <- function(x, y) { 
  return (x ^ 2 - y / 5)
}

定義函數以後我們就可以使用此函數進行運算。

2.3.2 引數的預設值

函數不一定要輸入引數。如果引數有預設值,而未輸入引數的話,即隨預設值。創建一個有預設的引數的函數的語法,如:

funtion.name <- function(name.argument1=default.value1, 
                         name.argument2=default.value2,...) {
  "body function"
}

以下是一個能輸出連續的數字為向量或矩陣的簡單範例:

mat.vec <- function(a, b=2, flag=FALSE){ 
  if (flag) {
    matrix(1 : a, nrow=b)
  } else {
    1:a 
  }
}

此函數有三個變數:a 指矩陣或向量的元素個數;b 為如果我們想輸出的是矩陣的話其 rows 的數量;flag 決定是輸出矩陣還是向量,若 flag=TRUE,將會輸出包含 \(1\)-至 \(a\),而有 \(b\) 個 rows 的矩陣(預設為 b=2) ,若 flag=FALSE,將會輸出包含 \(1\)\(a\) 的向量(預設為 flag=FALSE)。有預設的引數還有一個好處,即就算我們漏輸入了、忘記了有哪些引數,程式碼還是能正常運行,而不會報錯。

函數能輸出的就只有單一個物件。所以如果想要輸出多個元素,可以使用 lists。例如我們可以創建一個函數,其回傳三項資訊:資料長度、總和與平均值,即:

items <- function(x) list(len=length(x),total=sum(x), mean=mean(x))

我們把向量 1:10 丟進此函數,可得:

data <- 1 : 10
result <- items(data)
result
## $len
## [1] 10
## 
## $total
## [1] 55
## 
## $mean
## [1] 5.5

如果我們想查看函數是如何構成的,可以直接輸入其名,如:

log
## function (x, base = exp(1))  .Primitive("log")

如果想要套用函數在多個元素時,可以使用 lapply(list, function),其會把函數套用到 vector 或 list 的所有元素。sapply() 則類似於 lapply(),不同的是會把輸出簡化成向量或矩陣,而不是元素。其使用範例如:

salutation <- function(x) print("Hello")
# Note that this output does not depend on the value of x
output <- sapply(1 : 5, salutation)
## [1] "Hello"
## [1] "Hello"
## [1] "Hello"
## [1] "Hello"
## [1] "Hello"

或者也可以簡化成:

output <- sapply(1 : 5, function(x) print("Hello"))
## [1] "Hello"
## [1] "Hello"
## [1] "Hello"
## [1] "Hello"
## [1] "Hello"

以內建的 cars datasets 為例,其有兩個變數:speeddist。當我們想要計算這兩者的算術平均,可以:

lapply(cars, mean)
## $speed
## [1] 15.4
## 
## $dist
## [1] 42.98

或者:

sapply(cars, mean)
## speed  dist 
## 15.40 42.98

參考文獻

Zamora Saiz, Alfonso, Carlos Quesada González, Lluís Hurtado Gil, and Diego Mondéjar Ruiz. 2020. An Introduction to Data Analysis in R: Hands-on Coding, Data Mining, Visualization and Statistics from Scratch. Use R! Cham: Springer International Publishing. https://doi.org/10.1007/978-3-030-48997-7.