第 9 章 Iteration
當我們對一系列的資料進行相同的處理程序時,迴圈(loop)是相當有用的工具。
針對一系列的「學號」各別進行「平均成績計算」
針對本班所有「學號」各別進行「課堂練習Github Commit查詢有效出席次數」
迴圈三元素:
sequence:針對的「一系列對象」
body:每個對象要做的事
output:做完的成品要如何儲存
針對一系列的「學號」各別進行「平均成績計算」:
sequence: 一系列學號(「全部可能學號」為sequence source)
body: 各別進行「平均成績計算」
output: 所有學號的平均成績
針對本班所有「學號」各別進行「課堂練習Github Commit查詢有效出席次數」
sequence: 一系列學號(「全部可能學號」為sequence source)
body: 各別進行「課堂練習Github Commit查詢有效出席次數」
output: 所有學號有效出席次數
針對sequence中每個元素進行body程序,我們使用以下語法,該語法也稱為for迴圈(for loop):
for(sequence){
body
}
9.1 Sequence
sequence_source <- c("A","B","C")
以下介紹三種sequence產生方式:
i in sequence_source
:產生{sequence_source[[1]],sequence_source[[2]],sequence_source[[3]]}
序列。body中只要出現
i
即代表上述序列的「單一」元素,for loop會不斷遞換i
值且重覆執行body直到序列最後一個元素;即用i=sequence_source[[1]]
完成body程序,再換i=sequence_source[[2]]
完成body程序,再換i=sequence_source[[3]]
完成body程序。。
i in c(1:3)
:產生{1,2,3}
序列,用來對應sequence_source元素粹取時個別元素位置。body中只要出現
i
即代表上述序列的「單一」元素,即用i=1
完成body程序,再換i=2
完成body程序,再換i=3
完成body程序。body中使用
sequence_source[[i]]
來取出sequence_source序列個別元素。
i in seq_along(sequence_source)
:產生{1,2,3}
序列
(序列長度依sequence_source元素個數計)。body中只要出現
i
即代表上述序列的「單一」元素;即用i=1
完成body程序,再換i=2
完成body程序,再換i=3
完成body程序。body中使用
sequence_source[[i]]
來取出sequence_source序列個別元素。
範例1
執行以下程序得到transcriptDataFinal資料及平均成績計算函數gpa_fun()
:
load(url("https://www.dropbox.com/s/duh5aaqgl2f5m3z/loopTranscriptData.Rda?raw=1"))
studentIds <- unique(transcriptDataFinal$學號) # 全部可能學號
studentIds5 <- studentIds[1:5] # 前5筆學號
任務:針對studentIds5裡的學號一一進行
print()
。
9.1.1 作法一:
Sequence: i in studentIds5
Body: i
依序來自於{studentIds5[[1]],...,studentIds5[[5]]}
集合
for(i in studentIds5){
print(i)
}
9.1.2 作法二:
Sequence: i in c(1:5)
Body: i
依序來自於{1,2,3,4,5}
集合
for(i in c(1:5)){
print(i)
}
studentIds5[[i]]
才會依序來自於{studentIds5[[1]],...,studentIds5[[5]]}
for(i in c(1:5)){
print(studentIds5[[i]])
}
9.1.3 作法三:seq_along()
seq_along()
依input物件元素個數產生對應的整數向量,相當於c(1:length(input))
Sequence: i in seq_along(studentIds5)
Body: i
依序來自於{1,2,3,4,5}
集合
studentIds5[[i]]
才會依序來自於{studentIds5[[1]],...,studentIds5[[5]]}
for(i in seq_along(studentIds5)){
print(studentIds5[[i]])
}
大部份時候老師喜歡用
seq_along()
產生序列。由於
seq_along()
只是i
由1開始遞換不同數字,進行「一一執行body程序」的動作,在設計上可以先以i <- 1
開始,確認body程序正確,再改成for loop寫法。
如:針對studentIds5裡的學號一一進行print()
。
i <- 1
print(studentIds5[[i]])
沒問題後,改成for loop
for(i in seq_along(studentIds5)){
print(studentIds5[[i]])
}
任務:print出studentIds5裡「每個」學號的gpa,使用gpa_fun()
。
令
i <- 1
,print出studentIds5裡「第i
個」學號的gpa。使用for loop, print出studentIds5裡「每個」學號的gpa。
基本for loop寫作步驟:
定義/找出 sequence source.
把任務定義在對「第
i
個」且令i <- 1
以確認body內容.- 使用以下for loop模式,完成對「每個」的任務。
for(i in seq_along(sequence_source)){
body
}
範例2
執行以下程序:
gmailAddress <- c(
"tim@gmail.com",
"anita",
"yellen@gmail.com",
"huang@gmail.com"
)
任務:一一檢查gmailAddress每個元素,若沒有寫“@gmail.com”則加上。
任務:檢查gmailAddress中「每個」元素,若沒有寫“@gmail.com”則加上。
Sequence source是什麼?
完成以下body程序:
令
i <- 1
,檢查gmailAddress中「第i個」元素,若沒有寫“@gmail.com”則加上。完成for loop任務:檢查gmailAddress中「每個」元素,若沒有寫“@gmail.com”則加上。
9.2 Output
有時我們想把body程序中的某些值視為output value存起來,這時可依output value的type/mode, 透過vector()
函數先產生一個空的output value儲存物件來儲存for loop中的output value。
vector(mode,length)
mode: 儲存容器物件要存的output value類型(“character”, “numeric”,“integer”, “logical”,“list”)
length: 儲存容器物件要存多少個output value。
以下是針對不同資料類型用來存5個值的容器物件:
saveCharacter <- vector("character",5)
saveCharacter
saveNumeric <- vector("numeric",5)
saveNumeric
saveLogical <- vector("logical",5)
saveLogical
saveList <- vector("list",5)
saveList
某個output value要存在容器那個位置,用元素值取代的概念來取代該位置的預設值為output value。
# 把"你好"存在saveCharacter第3個位置
saveCharacter # 儲存前
saveCharacter[[3]] <- "你好"
saveCharacter # 儲存後
# 把 72.3 存在saveNumeric第3個位置
saveNumeric # 儲存前
saveNumeric[[3]] <- 72.3
saveNumeric # 儲存後
# 把 TRUE 存在saveLogical第3個位置
saveLogical # 儲存前
saveLogical[[3]] <- TRUE
saveLogical # 儲存後
# 把 list(姓名="小明",電話="02-86741111") 存在saveList第3個位置
saveList # 儲存前
saveList[[3]] <- list(姓名="小明",電話="02-86741111")
saveList # 儲存後
在for loop裡,若有output value要存,每個i均會有儲存的必要,因此多數時候vector(mode, length)
中的length為sequence source的元素個數,可使用:
length(sequence_source)
取得總個數。
有儲存需求的for loop:
決定儲存容器要存的「資料型態」及「資料個數」。
找出sequence source。
Body內容:
先將任務中的「每個」改成「第
i
個」以確認body內容任務中有儲存需要時,存在儲存容器的「第
i
個」位置
- 組合「每個」任務的for loop,使用以下模式:
saveContainer <- vector(mode,length)
for(i in seq_alog(sequence_source)){
body
}
範例3
任務: 將transcriptDataFinal資料中每個學號的平均成績算出來並儲存(使用
gpa_fun()
)
決定儲存容器要存的「資料型態」及「資料個數」。
資料型態:平均成績是numeric。
資料個數:每個學號會有一筆要存,所以總學號數是總資料個數。
找出sequence source:這裡是「不重覆」的所有可能學號
studentIds <- unique(transcriptDataFinal$學號) # sequence_source
saveGPAs <- vector("numeric",length(studentIds)) # saveContainer
- 先將任務中的「每個」改成「第
i
個」以確認body內容,要先令i <-1
:這裡即是把「第i
個」學號的平均成績算出來並儲存。
i <- 1
saveGPAs[[i]] <- gpa_fun(studentIds[[i]])
body為i <- 1
以後的所有程序
- 組合「每個」任務的for loop,使用以下模式:
saveContainer <- vector(mode,length)
for(i in seq_alog(sequence_source)){
body
}
studentIds <- unique(transcriptDataFinal$學號) # sequence_source
saveGPAs <- vector("numeric",length(studentIds)) # saveContainer
for(i in seq_along(studentIds)){
saveGPAs[[i]] <- gpa_fun(studentIds[[i]])
}
儲存容器若是對應sequence source,將儲存容器元素依sequence source命名會更容易了解資料的對應關係。
saveGPAsWithNames <- saveGPAs
names(saveGPAsWithNames) <- studentIds
saveGPAs[1:5] # 元素沒有名字
saveGPAsWithNames[1:5] # 元素有名字
執行以下程序下載GitHub使用者r-hub在自己的cranlogs repo的commits記錄: https://github.com/r-hub/cranlogs/commits/master
load(url("https://www.dropbox.com/s/qnz9paigf61yjus/commitHistory.Rda?raw=1"))
其中:
author: 為其中commit元素底下的author底下的 name元素值(為方便討論以下用->表示為
commit->author->name)。committer: commit-> committer-> name元素值。
commitDate: commit-> commiter-> date元素值。
任務一:存下來每個commit的author。
任務二:存下來每個commit的committer。
任務三:存下來每個author與committer同人的commitDate。