vector 是 R 裡面最「簡單」的資料結構。有時候我們需要比較更複雜的資料結構處理我們遇到的資料,例如,我們或許需要儲存不同資料類型或是具有階層結構的資料。面對這兩種需求,vector 無能為力,因此需要用到 R 的 list。
list() 去製造出 list。list() 的使用方式很類似用來製造 vector 的 c(),但與 c() 不同的是,list()
list() 裡面可以放入另一個 list()#> [[1]]
#> [1] TRUE
#>
#> [[2]]
#> [1] 1 2 3
#>
#> [[3]]
#> [1] "Hello"
#> $kai
#> [1] TRUE
#>
#> $joy
#> [1] 1 2 3
#>
#> $jess
#> [1] "Hello"
#> [[1]]
#> [1] 1.1
#>
#> [[2]]
#> [[2]][[1]]
#> [1] 2.1
#>
#> [[2]][[2]]
#> [1] "Hello"通常我們會習慣為 list 加上名字 (names),幫助我們更容易處理這種比較複雜的資料結構
[]: 與 vector 一樣,我們可以透過 lst[<char vector of names>]、lst[<integer vector>] 或 lst[<logical vector>] 去 subset list
#> $single
#> [1] FALSE
#> $single
#> [1] FALSE
#>
#> $tags
#> [1] "ig" "selfie"
#> $age
#> [1] 20
#>
#> $tags
#> [1] "ig" "selfie"就像 vec[<some vector>] 會回傳一部分的 vector (sub-vector);lst[<some vector>] 也會回傳一部分的 list (sub-list)。換言之,使用 [] 時,回傳值的資料結構不會改變。
我們可以將 list 想像成一列火車,每節車廂是一個長度為 1 的 sub-list,車廂裡面是這個 sub-list 儲存的值。欲取得 sub-list,使用的是 [];欲取得 sub-list 裡面的值 (i.e. 脫去外層的 list),需使用 [[]]
# 回傳 sub-list
typeof(ben["tags"])
ben["tags"]
# 回傳 list 之內的「值」,在此為一個 char vector
typeof(ben[["tags"]])
ben[["tags"]]#> [1] "list"
#> $tags
#> [1] "ig" "selfie"
#>
#> [1] "character"
#> [1] "ig" "selfie"
lst[["<name>"]] 有另一種更簡便的寫法:lst$<name>, e.g. ben[["tags"]] 可改寫成 ben$tagslist as a train analogy
a_lst <- list(name = "ben",
info = list(age = 20,
tags = c("ig", "selfie")))
# Get "selfie"
a_lst[['info']]
a_lst[['info']][['tags']]
a_lst[['info']][['tags']][2]#> $age
#> [1] 20
#>
#> $tags
#> [1] "ig" "selfie"
#>
#> [1] "ig" "selfie"
#> [1] "selfie"
# Another way to get "selfie"
a_lst['info'][[1]]
a_lst['info'][[1]]['tags'][[1]]
a_lst['info'][[1]]['tags'][[1]][2]#> $age
#> [1] 20
#>
#> $tags
#> [1] "ig" "selfie"
#>
#> [1] "ig" "selfie"
#> [1] "selfie"
#> $age
#> [1] 20
#>
#> $tags
#> [1] "ig" "selfie"
#>
#> [1] "ig" "selfie"
#> [1] "selfie"
#> $age
#> [1] 20
#>
#> $tags
#> [1] "ig" "selfie"
#>
#> [1] "ig" "selfie"
#> [1] "selfie"
上週介紹的條件式 (if-else) 讓我們可以依據不同狀況執行不同的程式碼,藉此能幫助我們寫出更有彈性的程式。迴圈讓我們能重複執行某一區塊的程式碼,如此就不需要重複寫出相同的程式碼。
R 有 for 與 while 迴圈。一般而言,在資料分析時非常少會用到 while 迴圈,因此實習課不作介紹,有興趣的同學可自行參考線上教材或教科書。
for loop 的結構如下
for loop 會使 {} 內的程式碼重複執行數次,其次數等於 <vector> 的長度;並且,在第 n 次開始執行 {} 內的程式碼前,會將 <vector> 裡的第 n 個元素指派給 <變數>。所以在第一次迴圈時,可透過 <變數> 取得 <vector> 中的第一個元素;在第二次迴圈時,可取得 <vector> 中的第二個元素;依此類推,最後一次迴圈則可以透過 <變數> 取得 <vector> 中的最後一個元素。
vec <- c("謝", "老師", "好", "帥")
for (word in vec) {
# Will execute 4 times,
# each time a new value from `vec` will be assigned to `word`
print(word)
}#> [1] "謝"
#> [1] "老師"
#> [1] "好"
#> [1] "帥"
<var> in <vector>)。但因為 R 向量式程式語言的特性,R 的 for 迴圈很容易改寫成其它更方便的型態。有時候我們需要知道迴圈進行到 <vector> 的第幾個元素,這時候通常會使用 seq_along(<vector>) 去製造出與 <vector> 等長的整數序列 (e.g. seq_along(c('a', 'b', 'c')) 會回傳 1 2 3),如此我們便可知道進行到第幾次迴圈,也可透過 <vector>[i] 取得與該次迴圈對映的元素。
#> [1] "1 謝"
#> [1] "2 老師"
#> [1] "3 好"
#> [1] "4 帥"
vec <- c("謝", "老師", "好", "帥")
for (i in seq_along(vec)) {
print(vec[i])
# Print `?` in the last loop
if (i == length(vec)) {
print('?')
}
}#> [1] "謝"
#> [1] "老師"
#> [1] "好"
#> [1] "帥"
#> [1] "?"我們也可以透過 names() 在 for loop 裡使用 <vector> 的 names 屬性:
vec <- c(Monday = "rainy", Tuesday = "cloudy", Wednesday = "sunny")
for (name in names(vec)) {
print(paste0(name, ' was ', vec[name], '.'))
}#> [1] "Monday was rainy."
#> [1] "Tuesday was cloudy."
#> [1] "Wednesday was sunny."break & next: 進階迴圈控制 (有興趣者自行參考)有時候,我們需要對 for loop 有「更多的控制」。前面在 for loop 中使用到條件式即是一個例子。但常常條件式本身的功能並不足夠:執行迴圈時,在符合特定條件下,
有時候我們會希望能忽略一次迴圈中「所有尚未被執行的程式碼」,這時就會使用到 next:
# 使用 next '忽略一次' 迴圈
for (i in 1:10) {
if (i == 5) {
print("Skipping print(i) on line 194")
next
}
print(i)
}#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] "Skipping print(i) on line 194"
#> [1] 6
#> [1] 7
#> [1] 8
#> [1] 9
#> [1] 10有時我們需要跳出整個迴圈,亦即不再執行 for loop 裡面的程式碼。這時就會使用到 break:
# 使用 break 跳出整個迴圈
for (i in 1:10) {
if (i == 5) {
print("Breaking out the for loop")
break
}
print(i)
}#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] "Breaking out the for loop"上週我們使用過 3 個長度為 4 的 vector 來儲存關於 4 個人 (kai, jessy, joy, ben) 的資料。但使用這種方式儲存資料似乎有些違反直覺,因為它將關於一個人的資訊 (name, age, about) 分開來儲存在 3 個獨立的 vector。
對於這種彼此之間具有關聯的資料,一種更好的方式是將它們儲存在一起,因為這不只幫助我們在「程式上」更容易去操弄這筆資料,更讓我們能以「階層組織」去「想像」我們的資料。這裡我們使用 list 去改寫上週的資料:
member <- list(
kai = list(age = 40, about = "a professor"),
jessy = list(age = 20, about = "a cat lover"),
joy = list(age = 18, about = "a hacker"),
ben = list(age = 19, about = "a YouTuber")
)
member#> $kai
#> $kai$age
#> [1] 40
#>
#> $kai$about
#> [1] "a professor"
#>
#>
#> $jessy
#> $jessy$age
#> [1] 20
#>
#> $jessy$about
#> [1] "a cat lover"
#>
#>
#> $joy
#> $joy$age
#> [1] 18
#>
#> $joy$about
#> [1] "a hacker"
#>
#>
#> $ben
#> $ben$age
#> [1] 19
#>
#> $ben$about
#> [1] "a YouTuber"
for (name in names(member)) {
someone <- member[[name]]
# 將組成句子的片語儲存於 char vector `phrases`
phrases <- c(name, someone$about, "is quite young.")
if (someone$age > 35) {
phrases[3] <- "should be wise."
}
# 在各片語之間插入逗點,形成一個句子
sentence <- paste0(phrases, collapse = ', ')
print(sentence)
}#> [1] "kai, a professor, should be wise."
#> [1] "jessy, a cat lover, is quite young."
#> [1] "joy, a hacker, is quite young."
#> [1] "ben, a YouTuber, is quite young."data frame 是 R 語言非常重要的資料結構,它造就了 R 強大的表格式資料處理能力
data frame 是一種二維的資料結構。這種資料結構基本上與我們熟悉的 Excel (或 google 試算表) 非常類似:
A data frame looks like an Excel Spreadsheet
data frame 的每一橫列 (row) 皆是一筆資料 (e.g. 一位受訪者所填的問卷)
data frame 的每一(直)欄 (column) 代表一個變項 (e.g. 問卷上的某個題目)
data frame 例子
我們可以使用 tibble 套件的 tibble()1 建立 data frame。上圖中的 data frame 例子即可由下方的程式碼所建立:
library(tibble)
df <- tibble(name = c("kai", "jessy", "joy", "ben"),
age = c(40, 20, 18, 19),
grad = c(FALSE, TRUE, FALSE, TRUE))
df#> # A tibble: 4 x 3
#> name age grad
#> <chr> <dbl> <lgl>
#> 1 kai 40 FALSE
#> 2 jessy 20 TRUE
#> 3 joy 18 FALSE
#> 4 ben 19 TRUE
tibble() 裡的每個 vector 對映到 data frame 中的一欄 (column)。因此 data frame 中不同欄的資料類型可能不同,但每一欄 (變項) 內的資料類型必須相同 (因為 vector 只能儲存相同的資料類型)。下方的指令可用於檢視 data frame 的資訊
#> [1] 4
#> [1] 3
#> [1] 4 3
#> [1] "name" "age" "grad"
#> Observations: 4
#> Variables: 3
#> $ name <chr> "kai", "jessy", "joy", "ben"
#> $ age <dbl> 40, 20, 18, 19
#> $ grad <lgl> FALSE, TRUE, FALSE, TRUE
data frame 的篩選 (subsetting) 與 vector 和 list 類似,差別只在於 data frame 屬於二維的資料結構,因此需要提供 2 個 vector 進行資料的篩選:
df[<vector 1>, <vector 2>]
在這裡,<vector 1> 篩選的是「列 (row)」,亦即,<vector 1> 決定要篩選出哪幾個觀察值 (observations)。<vector 2> 篩選的則是「欄 (column)」,亦即,<vector 2> 決定要篩選出哪些變項 (variables)。
以這種語法進行篩選,回傳的一定是 data frame2,即使只有篩選出一個值 (e.g. df[1, 1])
#> # A tibble: 1 x 1
#> name
#> <chr>
#> 1 jessy
#> # A tibble: 1 x 2
#> name age
#> <chr> <dbl>
#> 1 jessy 20
#> # A tibble: 1 x 3
#> name age grad
#> <chr> <dbl> <lgl>
#> 1 jessy 20 TRUE
若想要從 data frame 裡面篩選出 vector (取得「火車車廂」內的值),則要使用前面提過的 $ 或 [[]]:
df[["<column_name>"]]
df[[<column_index>]]
df$<column_name>
#> [1] 40 20 18 19
#> [1] 40 20 18 19
#> [1] 40 20 18 19篩選 data frame 而回傳 vector 是個很實用的技巧,因為我們可以使用這個回傳的 vector 當作我們進一步篩選 data frame 的依據,例如:
#> [1] TRUE TRUE FALSE FALSE
#> # A tibble: 2 x 3
#> name age grad
#> <chr> <dbl> <lgl>
#> 1 kai 40 FALSE
#> 2 jessy 20 TRUE
#> # A tibble: 2 x 3
#> name age grad
#> <chr> <dbl> <lgl>
#> 1 joy 18 FALSE
#> 2 ben 19 TRUE
#> # A tibble: 2 x 3
#> name age grad
#> <chr> <dbl> <lgl>
#> 1 kai 40 FALSE
#> 2 jessy 20 TRUE透過這個技巧,R 能幫助我們快速篩選出需要的資料,例如,我們可以結合 age 與 grad 兩個變項,篩選出「小於 20 歲且為研究所學生」的 data frame:
#> # A tibble: 1 x 3
#> name age grad
#> <chr> <dbl> <lgl>
#> 1 ben 19 TRUER 的內建函數 data.frame() 是最常被用於建立 data frame 的函數。tibble 套件裡的 tibble() 則與 data.frame() 的功能幾乎一樣,只是 tibble() 更改了 data.frame() 裡面常令使用者感到困惑的一些預設行為。
tibble 套件為 Tidyverse 套件群的一員,目的是為了解決 base R 眾多函數紛亂不一致的問題 (e.g. 命名不一致、預設行為不一致、類似的函數回傳值不一致等)。↩
這只有使用 tibble 套件的 tibble() 所回傳的 data frame (class 為 tbl_df, tbl 與 data.frame) 才有這種特性。使用 R 的內建函數 data.frame() 所建立的 data frame 或是 R 的內建資料集 (class 只有 data.frame) 則會根據所篩選出之資料的形狀的不同而回傳不同的資料結構 (有可能是 data.frame 或是 vector)↩