第 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產生方式:

  1. 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程序。。

  2. 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序列個別元素。

  3. 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()

  1. i <- 1,print出studentIds5裡「第i個」學號的gpa。

  2. 使用for loop, print出studentIds5裡「每個」學號的gpa。

基本for loop寫作步驟:

  1. 定義/找出 sequence source.

  2. 把任務定義在對「第i個」且令i <- 1以確認body內容.

  3. 使用以下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”則加上。

  1. Sequence source是什麼?

  2. 完成以下body程序:

    i <- 1,檢查gmailAddress中「第i個」元素,若沒有寫“@gmail.com”則加上。

  3. 完成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:

  1. 決定儲存容器要存的「資料型態」及「資料個數」。

  2. 找出sequence source。

  3. Body內容:

    • 先將任務中的「每個」改成「第i個」以確認body內容

    • 任務中有儲存需要時,存在儲存容器的「第i個」位置

  4. 組合「每個」任務的for loop,使用以下模式:
saveContainer <- vector(mode,length)
for(i in seq_alog(sequence_source)){
  body
}

範例3

任務: 將transcriptDataFinal資料中每個學號的平均成績算出來並儲存(使用gpa_fun()

  1. 決定儲存容器要存的「資料型態」及「資料個數」。

    • 資料型態:平均成績是numeric。

    • 資料個數:每個學號會有一筆要存,所以總學號數是總資料個數。

  2. 找出sequence source:這裡是「不重覆」的所有可能學號

studentIds <- unique(transcriptDataFinal$學號) # sequence_source
saveGPAs <- vector("numeric",length(studentIds)) # saveContainer
  1. 先將任務中的「每個」改成「第i個」以確認body內容,要先令i <-1:這裡即是把「第i個」學號的平均成績算出來並儲存。
i <- 1
saveGPAs[[i]] <- gpa_fun(studentIds[[i]])

body為i <- 1以後的所有程序

  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。