--- 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] ![](https://i.imgur.com/wUeUwLd.png)[^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>