---
disqus: yueswater
---
# R 基礎技能:運算、流程控制與迴圈
{%hackmd @themes/orangeheart %}
<style>
.likecoin-button {
position: relative;
width: 100%;
max-width: 485px;
max-height: 240px;
margin: 0 auto;
}
.likecoin-button > div {
padding-top: 49.48454%;
}
.likecoin-button > iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
###### tags: `R Language`
## 基本數學運算(Mathematical Operation)
作為一門程式語言,`R`可以提供使用者進行簡單的數學運算,包含四則運算、三角函數、指數函數、對數函數等等。首先來介紹四則運算:
### 四則運算
假設我們想要計算:
$$
\frac{(1+2) \times 3}{4} - 5
$$
我們可以輸入以下指令
```r
(1 + 2) * 3 / 4 - 5
## [1] -2.75
```
其中`+`代表加法、`-`為減法、`*`為乘法、`/`為除法。另外,假設我們想要計算某個數字的冪次項,如
$$
2^{10}
$$
我們利用`**`或`^`代表次方
```r
2**10
## [1] 1024
2^10
## [1] 1024
```
而我們也可以利用`%/%`與`%%`分別計算商數跟餘數。
```r
7 %/% 3
## [1] 2
7 %% 3
## [1] 1
```
### 數學上的簡單函數
以下我們就利用程式碼來介紹一些簡單的函數:
```r
pi
## [1] 3.141593
sqrt(2)
## [1] 1.414214
exp(2)
## [1] 7.389056
log(2)
## [1] 0.6931472
sin(2)
## [1] 0.9092974
cos(2)
## [1] -0.4161468
tan(2)
## [1] -2.18504
```
## 資料型別與存儲方式(Data Types and Structures)
### 型別
在`R`語言中,儲存資料的型別有很多種,我們可以透過`typeof()`查詢某一變數的型別。[^1]例如:
```r
typeof(2.0) ## 為浮點數
## [1] "double"
typeof("Hello World!") ## 為字串
## [1] "character"
typeof(TRUE) ## 為布林值
## [1] "logical"
```
### 存儲方式
在分析或處理資料時,當然不可能只有一個變量,因此我們就需要一個儲存資料的「容器」。這些容器的形式有很多種,包含向量(vector)、矩陣(matrix)、陣列(array)、列表(list)、資料框(data frame)。
#### 向量
```r
x <- c(1,2,3,4,5)
print(x)
## [1] 1 2 3 4 5
```
這樣我們就將 1 到 5 這些數字存放到一個向量裡,注意到這裡我們使用`<-`來指派(assign)變數,並以`print()`將變數結果顯示出。而生成向量的方式還有很多種,例如
```r
w <- vector(mode = "numeric", length = 5)
print(w)
## [1] 0 0 0 0 0
x <- numeric(5)
print(x)
## [1] 0 0 0 0 0
y <- c(0, 0, 0, 0, 0)
print(y)
## [1] 0 0 0 0 0
z <- rep(0, 5)
print(z)
## [1] 0 0 0 0 0
```
同樣地,我們仍可以透過函數查詢資料儲存的方式,即`class()`。
#### 矩陣
接著我們來討論矩陣,假設給定以下矩陣:
$$
\begin{bmatrix}
1 & 2\\
3 & 4\\
5 & 6\\
\end{bmatrix}
$$
我們有下面幾種方式得到如上的結果。
```r
X <- c(1,2,3,4,5,6)
matX <- matrix(data=X, ncol=2, nrow=3, byrow=TRUE)
print(matX)
## [,1] [,2]
## [1,] 1 2
## [2,] 3 4
## [3,] 5 6
matY <- matrix(data=c(1,2,3,4,5,6), nrow=3, byrow=TRUE)
print(matY)
## [,1] [,2]
## [1,] 1 2
## [2,] 3 4
## [3,] 5 6
```
其中`ncol`代表要形成幾欄、`nrow`為幾行,而`byrow`則是將我們輸入進去的資料**以行排列**。
#### 陣列
由於矩陣一個坑只能填入一個蘿蔔,有時候當我們有更高維度的資料時便不好處理,故我們可以透過建立陣列解決上述問題。
```r
A <- array(data=1:12, dim=c(2,3,2))
A
## , , 1
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
## , , 2
## [,1] [,2] [,3]
## [1,] 7 9 11
## [2,] 8 10 12
```
其中的`dim`代表維度,`c(2,3,2)`各個數字代表:2 行、3欄、執行兩次(因為有 12 筆資料)。
#### 列表
不同於上述幾個儲存資料的方式,列表可以儲存**不同型別**的資料。假設 2022 年台大統計學暨實習這堂課的助教搜集了該班期中考的分數,並抽出四個人作為代表,其可以將結果儲存如下:
```r
student <- list(Students=c("大明","小華","阿天","阿國"),
Year=2022,
Score=c(15,95,66,78),
Uni="NTU",
Course="Statistics")
## student
## $Students
## [1] "大明" "小華" "阿天" "阿國"
## $Year
## [1] 2022
## $Score
## [1] 15 95 66 78
## $Uni
## [1] "NTU"
## $Course
## [1] "Statistics"
```
#### 資料框
當我們手中的資料為二維資料時,我們多半都會使用資料框儲存資料。
```r
wage <- data.frame(Name=c("大明","小華","阿天","阿國"),
Wage=c(23135,41465,35762,90424),
Gender=c(1,0,1,1))
wage
## wage
## Name Wage Gender
## 1 大明 23135 1
## 2 小華 41465 0
## 3 阿天 35762 1
## 4 阿國 90424 1
```
而我們可以利用`colnames()`與`rownames()`為我們的資料框進行命名。
```r
colnames(wage) <- c("姓名","薪資","性別")
wage
## wage
## 姓名 薪資 性別
## 1 大明 23135 1
## 2 小華 41465 0
## 3 阿天 35762 1
## 4 阿國 90424 1
```
### 索引
所謂索引,白話一點來說就是從資料中抓取我們想要分析的部分。而不同的資料儲存方式都有不同的索引方式,以下我們就來一一介紹。首先向量的索引方式最簡單:
```r
x <- c(1,2,3,4,5)
x[4]
## [1] 4
x[1:3]
## [1] 1 2 3
x[-1]
## [1] 2 3 4 5
```
其中`[1:3]`代表從向量中抓取第 1 至第 3 個元素,而`[-1]`則是從向量中刪除第 1 個元素。接著,矩陣跟陣列的索引方式十分相似:
```r
## 矩陣
X <- c(1,2,3,4,5,6)
matX <- matrix(data=X, ncol=2, nrow=3, byrow=TRUE)
print(matX)
## [,1] [,2]
## [1,] 1 2
## [2,] 3 4
## [3,] 5 6
matX[1,2]
## [1] 2
## 陣列
A <- array(data=1:12, dim=c(2,3))
print(A)
## [,1] [,2] [,3]
## [1,] 1 3 5
## [2,] 2 4 6
A[1,2]
## [1] 3
```
注意到 `[,]`的第一個元素必須填入行,第二個則為欄。最後則是列表與資料框,這兩個儲存方式的索引也是十分相似。
```r
## 列表
student <- list(Students=c("大明","小華","阿天","阿國"),
Year=2022,
Score=c(15,95,66,78),
Uni="NTU",
Course="Statistics")
print(student)
## $Students
## [1] "大明" "小華" "阿天" "阿國"
## $Year
## [1] 2022
## $Score
## [1] 15 95 66 78
## $Uni
## [1] "NTU"
## $Course
## [1] "Statistics"
student$Score
## [1] 15 95 66 78
## 資料框
wage <- data.frame(Name=c("大明","小華","阿天","阿國"),
Wage=c(23135,41465,35762,90424),
Gender=c(1,0,1,1))
print(wage)
## Name Wage Gender
## 1 大明 23135 1
## 2 小華 41465 0
## 3 阿天 35762 1
## 4 阿國 90424 1
wage$Name
## [1] "大明" "小華" "阿天" "阿國"
```
## 函式(function)
高中時期我們必定學過函數的概念,所謂函數就是描述一個對應關係的集合,即
$$
y= f(x)
$$
當我們將 $x$ 丟入函數 $f(\cdot)$ 中,便可以得到 $y$。而在程式語言中,函數又被稱為函式,其功能與目的大致與上述相同,不過更重要的一點是,畢竟程式是在幫助人類處理一個又一個複雜的任務,因此透過函式便可以將複雜的任務簡化,當下次要執行同樣動作的時候便可以呼叫函式。而一個良好的函式必須由以下幾個部分組成[^2]:
- 函式名稱(name):日後呼叫函式的方法。
- 參數(argument):又分為必填(required)與選填(optional)。
- 函式本體(body):要求程式執行的部分。
- 回傳值(return value)
而在`R`語言中,我們透過下方的架構建立函式:
```r
函式名稱 <- function(參數){
函式敘述式 1
函式敘述式 2
...
函式敘述式 n
return(回傳值)
}
```
我們以`summary()`這個函式為例。當我們拿到一筆資料,如果想要大致了解這筆資料的各種敘述統計量,在`R`中可以透過`summary()`這個函式達到上述的目的。我們使用 Heumann, Christian, 與 Micheal Schomaker Shalabh 兩位教授合著的 "*Introduction to statistics and data analysis*" (2016) 中的 `pizza delivery data` 作為資料,請各位至本教科書之[附錄官網](https://chris.userweb.mwn.de/book/)下載,或點擊[此連結](https://chris.userweb.mwn.de/book/pizza_delivery.csv)進行下載。當然,你也可以直接在 `R` 中使用下列方式讀取資料:
```r
data <- read.csv("https://chris.userweb.mwn.de/book/pizza_delivery.csv")
```
假設我們想要看這筆資料裡外送時間的敘述統計資料,我們可以這樣做:
```r
summary(data$time)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 12.27 30.06 34.38 34.23 38.58 53.10
```
但如果我們想要知道樣本數、變異數、四分位距,該怎麼辦呢?我們可以透過函式幫助我們完成這項任務。
```r
install.package("tidyverse")
library(tidyverse)
my.summary <- function(x){
obs <- as.integer(length(x))
mean <- mean(x)
median <- median(x)
IQR <- IQR(x)
sd <- sd(x)
output <- c(N=obs,
Mean=mean,
Median=median,
IQR=IQR,
Sd=sd) %>% round(3)
return(output)
}
```
接著,
```r
my.summary(data$time)
## N Mean Median IQR Sd
## 1266.000 34.230 34.382 8.516 6.461
```
## 流程控制(Flow of Control)
控制流程(也稱為流程控制)是電腦運算領域的用語,意指在程式執行時,個別的指令(或是陳述、子程式)執行或求值的順序。不論是在宣告式程式語言或是函式程式語言中,都有類似的概念。[^3]
[^4]
上述的圖片完美地描述流程控制的內涵:當`if`後方的邏輯判斷為真(true),便執行指定條件,若為偽(false)則不執行。
```r
x <- 1
if (x > 10){
print("大於10")
}else{
print("小於10")
}
## [1] "小於10"
```
你可以自己把`x`的數值改掉,測試看看在給定不同的`x`之下,結果會有何不同。另外,我們可以將 if-else 的敘述無限延伸,比如說回到上面統計學分數的例子,假設 90 分以上必定可以拿到 A+ 的等第,60 分以下則是不及格,那麼我們可以利用下面的方式來區分四位同學的分數:
```r
score <- 63
if(score >=90){
print("A+")
}else if(score>=60){
print("還需要加油!")
}else{
print("不及格")
}
## [1] "還需要加油!"
```
不過,你是否發現到一個問題,上面的這些方法只能對於一個元素進行判斷、檢查,但如果我們今天有 10 筆乃至於 10000 筆資料,不太可能一個一個輸入,於是我們有了`ifelse()`進行向量的判斷。
```r
student <- list(Students=c("大明","小華","阿天","阿國"),
Year=2022,
Score=c(15,95,66,78),
Uni="NTU",
Course="Statistics")
print(student)
## $Students
## [1] "大明" "小華" "阿天" "阿國"
## $Year
## [1] 2022
## $Score
## [1] 15 95 66 78
## $Uni
## [1] "NTU"
## $Course
## [1] "Statistics"
student$Score
## [1] 15 95 66 78
Scores <- as.vector(student$Score)
ifelse(Scores>=60,"及格","不及格")
## [1] "不及格" "及格" "及格" "及格"
```
## 迴圈(loop)
迴圈可以幫助我們執行一項又一項的重複性任務或動作,在`R`語言中,我們使用`for`迴圈來達成這個目標,其結構如下:
```r
for(單一變數 in 參數向量){
重複執行本體
}
```
比如一次印出 5 個`"Hi"`:
```r
for(i in 1:5){
print("Hi")
}
## [1] "Hi"
## [1] "Hi"
## [1] "Hi"
## [1] "Hi"
## [1] "Hi"
```
我們也可以將迴圈跟邏輯判斷的 if-else 合併使用,解決數學上的問題。例如只印出 1 到 100 的偶數應該怎麼做呢?就數學上而言,偶數是可以被 2 整除的整數,因此使用`%%`是一個最恰當的方式:
```r
for(i in 1:100){
if(i %% 2 == 0){
print(i)
}else{
print("不是偶數!")
}
}
## [1] "不是偶數!"
## [1] 2
## [1] "不是偶數!"
## [1] 4
## ...
## [1] "不是偶數!"
## [1] 98
## [1] "不是偶數!"
## [1] 100
```
另外,我們也可以用`while`來製作迴圈,`while`迴圈是在每次執行迴圈後判斷真偽,為真就繼續執行,為偽則結束執行迴圈。
```r
x <- 0
while(x <= 5){
print(x)
x <- x + 1
}
## [1] 0
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
```
如果遇到特殊條件想要跳出迴圈的話,我們可以在迴圈內加上 if-else判斷,並以`break`作為跳出的動作。
```r
for(i in 1:10000){
if(i == 5){
break
}
print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
```
而若遇到想要跳過迴圈的特殊條件,則使用`next`。
```r
for(i in 1:10){
if(i == 5){
next
}
print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 6
## [1] 7
## [1] 8
## [1] 9
## [1] 10
```
:::info
**小試身手:剪刀、石頭、布!**
請用`R`語言寫出一個程式,必須符合以下條件:
1. 每一次執行時要告訴使用者「**請按照以下提示出拳:石頭[1]、剪刀[2]、布[3]、退出[4]**」的資訊
2. 如果使用者輸入小於 $0$ 或大於 $4$ 的數字,跳出錯誤訊息並重新執行。
:::spoiler 查看參考答案
```r
while(TRUE){
print("請按照以下提示出拳:石頭[1]、剪刀[2]、布[3]、退出[4]")
usr <- as.integer(readline("請出拳!\n"))
cpu <- sample(1:3, 1)
if(usr == 4){
print("遊戲結束")
break
}
if(usr > 4 | usr < 0 | usr %% 1 != 0){
print("請按照遊戲規則出拳!")
}
else if((cpu == 1 & usr == 3) | (cpu == 2 & usr == 1) | (cpu == 3 & usr == 2)){
print("你贏了")
}
else if(cpu == usr){
print("平手!")
}
else{
print("你輸了嗚嗚嗚!")
}
}
```
:::
:::
[^1]:假設我們不知道函數的功能或應該在函數中輸入什麼內容,我們可以透過`help(某個函數)`或`?某個函數`得到使用說明。
[^2]:4 函數 | 資料科學與R語言. https://yijutseng.github.io/DataScienceRBook/function.html.
[^3]:“控制流程.” Wikipedia, Wikimedia Foundation, 19 Dec. 2021, https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E6%B5%81%E7%A8%8B.
[^4]:3 控制流程 | 資料科學與R語言. https://yijutseng.github.io/DataScienceRBook/controlstructure.html.
<div class="likecoin-embed likecoin-button">
<div></div>
<iframe scrolling="no" frameborder="0" src="https://button.like.co/in/embed/xiaolong70701/button?referrer=hackmd.io"></iframe>
</div>