本篇介紹 R 的列表變數與 data frames 的使用方式。

R 列表變數

R 的列表(list)變數類似向量,內含多個元素,不過跟向量不同的是列表是一種復合型的變數,其中的每個元素可以是不同的類型,我們可以將各式各樣不同類型的變數儲存在一個列表變數中。

建立列表變數

列表變數可以使用 list 函數來建立,而其內容的指定方式跟 c 函數類似,但 list 可以允許各種不同類型的資料混合使用,例如向量、矩陣、字元變數等,甚至是函數也可以:

x.list <- list(1:3, "G.T.Wang", matrix(3:6, nrow = 2), sin)
x.list
[[1]]
[1] 1 2 3

[[2]]
[1] "G.T.Wang"

[[3]]
     [,1] [,2]
[1,]    3    5
[2,]    4    6

[[4]]
function (x)  .Primitive("sin")

我們也可以為列表的每個元素命名:

x.list <- list(
  seq = 1:3,
  name = "G.T.Wang",
  mat = matrix(3:6, nrow = 2),
  fun = sin)
x.list
$seq
[1] 1 2 3

$name
[1] "G.T.Wang"

$mat
     [,1] [,2]
[1,]    3    5
[2,]    4    6

$fun
function (x)  .Primitive("sin")

或是在建立列表之後,再使用 names 函數指定每個元素的名稱:

names(x.list) <- c("seq", "name", "mat", "fun")

列表變數的元素中也允許納入其他的列表變數,形成巢狀的資料結構:

y.list <- list(
  var1 = list( name = "pi", val = pi),
  var2 = list( name = "e", val = exp(1))
)
y.list
$var1
$var1$name
[1] "pi"

$var1$val
[1] 3.141593

$var2
$var2$name
[1] "e"

$var2$val
[1] 2.718282

列表的巢狀結構層數並沒有什麼限制,不管要建立幾層都沒問題。

如果列表的巢狀結超過數萬層以上,R 可能會產生錯誤訊息,不過這個限制在實際的情況下不太容易發生。

列表變數的索引

列表變數跟一般的向量一樣可以使用 [] 來存取其中的元素,可使用的索引類型有正整數、負整數、邏輯向量或元素名稱:

x.list[1:3]
$seq
[1] 1 2 3

$name
[1] "G.T.Wang"

$mat
     [,1] [,2]
[1,]    3    5
[2,]    4    6
x.list[-4]
$seq
[1] 1 2 3

$name
[1] "G.T.Wang"

$mat
     [,1] [,2]
[1,]    3    5
[2,]    4    6
x.list[c(TRUE, TRUE, TRUE, FALSE)]
$seq
[1] 1 2 3

$name
[1] "G.T.Wang"

$mat
     [,1] [,2]
[1,]    3    5
[2,]    4    6
x.list[c("seq", "name", "mat")]
$seq
[1] 1 2 3

$name
[1] "G.T.Wang"

$mat
     [,1] [,2]
[1,]    3    5
[2,]    4    6

以上幾種存取方式所得到的結果都會是一個新的列表變數,如若希望取得一個元素並保持原有的型態,可以改用 [[]] 運算子,這個運算子可以使用正整數或元素名稱來提取對應的列表元素:

x.list[[2]]
[1] "G.T.Wang"
x.list[["name"]]
[1] "G.T.Wang"

對於有名稱的列表元素,也可以使用錢字號運算子($)加上元素名稱來存取:

x.list$name
[1] "G.T.Wang"

使用錢字號運算子的方式跟 [[]] 運算子一樣也可以保留元素原本的型態,而且還有其他的優點,例如在 R 中打字的時候,我們可以輸入元素名稱的開頭幾的字母,再按下 Tab 鍵讓 R 自動補齊剩餘的名稱,另外 R 也允許使用名稱開頭的幾個字母來存取元素(前提是輸入的字母要讓 R 足以辨識要指定的元素):

x.list$n
[1] "G.T.Wang"

巢狀的列表結構可以使用多個 [[]][] 運算子堆疊來存取,常見的方式有以下幾種:

y.list[[1]][2]
$val
[1] 3.141593
y.list[["var1"]]["val"]
$val
[1] 3.141593
y.list[[1]][[2]]
[1] 3.141593
y.list[["var1"]][["val"]]
[1] 3.141593
y.list[[c(1, 2)]]
[1] 3.141593
y.list[[c("var1", "val")]]
[1] 3.141593

原子變數與遞迴變數

R 的變數可分為原子變數(atomic variable)與遞迴變數(recursive variable)兩種,向量、矩陣與字元等這類較簡單的變數屬於原子變數,而列表這種複合型的變數則屬於遞迴變數。

原子變數與遞迴變數的主要差異在於原子變數中的元素都必須是同一種類型,而遞迴變數的元素則允許各類型變數混雜。

我們可以使用 is.atomicis.recursive 來檢查變數是屬於哪一種:

is.atomic(list())
[1] FALSE
is.recursive(list())
[1] TRUE

列表的維度與運算

列表變數的長度(元素個數)可以使用 length 函數取得:

x.list <- list(1:3, "G.T.Wang", matrix(3:6, nrow = 2), sin)
length(x.list)
[1] 4

length 函數在計算巢狀結構的列表長度時,只會傳回第一層的長度:

y.list <- list(
  var1 = list( name = "pi", val = pi),
  var2 = list( name = "e", val = exp(1))
)
length(y.list)
[1] 2

列表只有長度的屬性,沒有維度的屬性:

dim(x.list)
NULL

nrowncolNROWNCOL 這幾個函數若作用在列表變數上,其效果跟一般向量一樣:

nrow(x.list)
NULL
ncol(x.list)
NULL
NROW(x.list)
[1] 4
NCOL(x.list)
[1] 1

由於列表的元素是由各種不同類型的變數所組成,所以列表變數之間不能直接互相進行運算,我們只能將列表的元素取出後,再單獨進行運算:

z.list <- list(c(1:3), c(2:4))
z.list[[1]] + z.list[[2]]
[1] 3 5 7

向量與列表的轉換

若要將向量變數轉換為列表變數,可以使用 as.list 函數:

x.vector <- c(3, 6, 8)
as.list(x.vector)
[[1]]
[1] 3

[[2]]
[1] 6

[[3]]
[1] 8

如果一個列表中每個元素都是很單純數值或字元等變數,就可以使用 as.numericas.character 這類的函數將其直接轉換為一般的向量:

as.numeric(list(3, 6, 8))
[1] 3 6 8

如果列表中的元素不是單純的純量變數,就要改用 unlist 函數:

y.list <- list(
  foo = 2,
  bar = c(3, 6, 9)
)
unlist(y.list)
foo bar1 bar2 bar3
   2    3    6    9

合併列表

若要將多個列表變數合併成一個,可以使用 c 函數:

c(list(a = 1, b = 2), list(c = 3), list(4))
$a
[1] 1

$b
[1] 2

$c
[1] 3

[[4]]
[1] 4

如果使用 c 函數合併列表與向量變數,則所有的向量變數會先自動被轉換成列表之後,才跟其餘的列表合併:

c(list(a = 1, b = 2), 3, 4)
$a
[1] 1

$b
[1] 2

[[3]]
[1] 3

[[4]]
[1] 4

NULL 空值

NULL 是用來表示空值的一個特殊值,在列表變數中比較常出現,而 data frames 與函數中也會用到(這些我們在後面的內容會再說明)。

有時候在建立列表變數時,我們會需要預先指定列表變數中應該包含哪一些元素名稱,但是在建立變數時並不知道元素的內容,這時候就可以將未知的元素設定為 NULL,例如:

x.list <- list(
  foo = "G. T. Wang",
  bar = NULL,
  foo.bar = NULL
)

NULLNA 有點相似,但兩個是不同的,最大的差異是 NA 是一個純量值,而 NULL 則不佔任何空間:

length(NULL)
[1] 0
length(NA)
[1] 1

要檢查一個變數值是否為 NULL 可以使用 is.null 函數:

is.null(NULL)
[1] TRUE
is.null(NA)
[1] FALSE

如果使用 is.na 來測試 NULL 是沒有意義的,因為 NULL 的長度為 0,沒有東西可以拿來檢查是否為 NA

is.na(NULL)
Warning message:
In is.na(NULL) : is.na() 套用到非串列或向量類型 'NULL'

將列表的元素指定為 NULL 的話,可以將該元素從列表變數中移除(縱使該元素原本就是 NULL 也一樣):

x.list <- list(
  foo = "G. T. Wang",
  bar = NULL,
  foo.bar = NULL
)
x.list
$foo
[1] "G. T. Wang"

$bar
NULL

$foo.bar
NULL
x.list$foo.bar <- NULL
x.list
$foo
[1] "G. T. Wang"

$bar
NULL

如果要將列表的某個元素值設定為 NULL,而不是將其移除,可以使用 list(NULL)

x.list["foo"] <- list(NULL)
x.list
$foo
NULL

$bar
NULL

Pairlists

在 R 中還有另外一種類似列表變數的 pairlist,它主要用於 R 函數參數的傳遞,一般的使用者不會直接使用到 pairlist,唯一比較有可能用到的情況是使用 formals 函數的時候,這個函數會傳回指定函數的參數:

sd.args <- formals(sd)
sd.args
$x

$na.rm
[1] FALSE
class(sd.args)
[1] "pairlist"

pairlist 變數在使用上跟列表變數幾乎相同,唯一的差別只在於空的 pairlist 會傳回 NULL,而空的列表就是一個單純空的列表:

pairlist()
NULL
list()
list()

Data Frames

R 的 data frame 是一個用來儲存類似 Excel 表格的變數類型,它跟矩陣類似,不過 data frame 的每個行(column)可以儲存不同變數類型的資料,甚至非狀巢結構的列表亦可。

建立 Data Frames

我們可以使用 data.frame 函數來建立 data frame 變數:

x.data.frame <- data.frame(
  x = letters[1:6],
  y = rnorm(6),
  z = runif(6) > 0.5
)
x.data.frame
  x            y     z
1 a -2.055503994  TRUE
2 b  0.114532213 FALSE
3 c -0.622876848  TRUE
4 d -1.207309249  TRUE
5 e -0.004488973 FALSE
6 f -0.549760562 FALSE
class(x.data.frame)
[1] "data.frame"

雖然一個 data frame 中每行(column)的資料類型可以是不同的,但是在同一行裡面的資料一定要是相同的資料類型。

上面產生的這個 data frame 中,並沒有明確指定每一列的名稱,這時候 R 會自動將每一列從 1 開始編號,作為預設的名稱。

在建立 data frame 時,如果任何一個向量有被指定每個元素的名稱時,R 會依據第一個這樣具有名稱的向量,為 data frame 的列命名。

y <- rnorm(5)
names(y) <- month.name[1:5]
data.frame(
  x = letters[1:5],
  y = y,
  z = runif(5) > 0.5
)
         x          y     z
January  a  1.0538679  TRUE
February b -0.6289093  TRUE
March    c -0.2401373  TRUE
April    d -1.3266327 FALSE
May      e -1.1100246 FALSE

如果不想要讓 R 自動依照向量的元素名稱來指定 data frame 列名稱,可以將 row.names 指定為 NULL

data.frame(
  x = letters[1:5],
  y = y,
  z = runif(5) > 0.5,
  row.names = NULL
)
  x          y     z
1 a  1.0538679 FALSE
2 b -0.6289093  TRUE
3 c -0.2401373  TRUE
4 d -1.3266327 FALSE
5 e -1.1100246 FALSE

我們也可以利用 row.names 參數明確指定 data frame 的列名稱:

data.frame(
  x = letters[1:5],
  y = y,
  z = runif(5) > 0.5,
  row.names = c("Taipei", "Hsinchu", "Taichung", "Tainan", "Kaohsiung")
)
          x          y     z
Taipei    a  1.0538679  TRUE
Hsinchu   b -0.6289093 FALSE
Taichung  c -0.2401373 FALSE
Tainan    d -1.3266327 FALSE
Kaohsiung e -1.1100246  TRUE

列名稱也可以在 data frame 建立之後,再使用 rownames 函數來指定或是取得,另外其行(column)名稱則可使用 colnames 函數,或是使用 dimnames 一次存取所有的行與列的名稱。

rownames(x.data.frame)
[1] "1" "2" "3" "4" "5" "6"
colnames(x.data.frame)
[1] "x" "y" "z"
dimnames(x.data.frame)
[[1]]
[1] "1" "2" "3" "4" "5" "6"

[[2]]
[1] "x" "y" "z"

大部分適用於矩陣的函數,都可以直接套用至 data frame 上使用:

nrow(x.data.frame)
[1] 6
ncol(x.data.frame)
[1] 3
dim(x.data.frame)
[1] 6 3

若使用 length 函數檢查 data frame 的話,會傳回跟 ncol 函數一樣的值,不是所有 data frame 的元素個數,而 names 作用在 data frame 上則跟 colnames 一樣。

length(x.data.frame)
[1] 3
names(x.data.frame)
[1] "x" "y" "z"

在建立 data frame 時可以允許使用不同長度的向量,R 會自動將較短的向量重複使用:

data.frame(
  x = c("One", "Two"),
  y = 1:3,
  z = rnorm(6)
)
    x y          z
1 One 1 -0.6910683
2 Two 2  0.7902759
3 One 3 -1.8949503
4 Two 1  0.4802724
5 One 2  0.8673821
6 Two 3 -0.8795439

如果有些較短的向量其長度不是最長向量長度的整數倍,就會產生錯誤:

data.frame(
  x = c("One", "Two"),
  y = 1:3,
  z = rnorm(5)
)
Error in data.frame(x = c("One", "Two"), y = 1:3, z = rnorm(5)) :
  arguments imply differing number of rows: 2, 3, 5

Data Frame 索引

data frame 的索引使用方式有許多種,向量和矩陣所使用的四種索引(正整數、負整數、邏輯與字元向量)都可以直接用於 data frame:

x.data.frame <- data.frame(
  x = letters[1:6],
  y = rnorm(6),
  z = runif(6) > 0.5
)
x.data.frame[2:4, 2]
[1] -0.2310849  1.0684388  0.9391086
x.data.frame[2:4, -2]
  x     z
2 b  TRUE
3 c  TRUE
4 d FALSE
x.data.frame[c(FALSE, TRUE, TRUE, TRUE, FALSE), c("x", "z")]
  x     z
2 b  TRUE
3 c  TRUE
4 d FALSE

只有取出 data frame 的單一行(column)時,傳回的資料類型會是向量,但若是取出多行時,傳回的資料就會是另一個 data frame:

class(x.data.frame[2:4, 2])
[1] "numeric"
class(x.data.frame[2:4, -2])
[1] "data.frame"

如果只需要選取單一行的資料,可以使用列表變數的索引方式(雙中括號 [[]] 加上正整數或名稱、錢字號 $ 加上名稱),以下幾種寫法都是相同的。

x.data.frame$x
[1] a b c d e f
Levels: a b c d e f
x.data.frame[[1]]
[1] a b c d e f
Levels: a b c d e f
x.data.frame[["x"]]
[1] a b c d e f
Levels: a b c d e f

如果要取出一行中部分的元素,可以再堆疊一個中括號來指定元素的索引:

x.data.frame$x[2:4]
[1] b c d
Levels: a b c d e f
x.data.frame[[1]][2:4]
[1] b c d
Levels: a b c d e f
x.data.frame[["x"]][2:4]
[1] b c d
Levels: a b c d e f

如果想要篩選 data frame 中的資料,可以使用條件判斷式再配合索引的方式來處理:

x.data.frame[x.data.frame$y > 0 | x.data.frame$z, "x"]
[1] a b d e f
Levels: a b c d e f

如果不想使用這麼複雜的寫法,也可以改用 subset 函數,它的功能相同,但是寫法較簡潔:

subset(x.data.frame, y > 0 | z, x)
  x
1 a
2 b
4 d
5 e
6 f

subset 函數第一個參數是要進行篩選的 data frame 變數,而第二個參數是指定列(row)的索引,第三個參數則是行(column)的索引,若最後一個行索引參數省略,就會取出所有行中的資料。

基本 Data Frame 操作

data frame 跟矩陣一樣可以使用 t 函數來轉向:

x.data.frame <- data.frame(
  x = letters[1:6],
  y = rnorm(6),
  z = runif(6) > 0.5
)
t(x.data.frame)

不過將 data frame 轉向之後,所有的資料都會換轉為同一種類型,而且轉換之後的變數會是一個矩陣。

若要結合多個 data frames,可以使用 cbindrbindrbind 在結合多個 data frames 時,會自動判斷每個行的名稱,將相同名稱的行對應起來,行的排列順序不會影響合併結果:

x.data.frame <- data.frame(
  x = letters[1:6],
  y = rnorm(6),
  z = runif(6) > 0.5
)
y.data.frame <- data.frame(
  y = rnorm(6, 7, 9),
  z = runif(6) < 0.5,
  x = letters[7:12]
)
rbind(x.data.frame, y.data.frame)
   x           y     z
1  a  -0.3059567  TRUE
2  b   0.6109400  TRUE
3  c   0.5408157 FALSE
4  d   0.8020496 FALSE
5  e  -0.9348107 FALSE
6  f   1.2134915 FALSE
7  g  -0.3173948  TRUE
8  h  25.6336216  TRUE
9  i -16.4824389  TRUE
10 j   5.3940237  TRUE
11 k  11.9232029 FALSE
12 l  16.6851942  TRUE

cbind 函數在合併 data frames 時,並不會檢查行的名稱,所以要注意行名稱重複的問題:

cbind(x.data.frame, y.data.frame)
  x          y     z           y     z x
1 a -0.3059567  TRUE  -0.3173948  TRUE g
2 b  0.6109400  TRUE  25.6336216  TRUE h
3 c  0.5408157 FALSE -16.4824389  TRUE i
4 d  0.8020496 FALSE   5.3940237  TRUE j
5 e -0.9348107 FALSE  11.9232029 FALSE k
6 f  1.2134915 FALSE  16.6851942  TRUE l

如果兩個 data frames 要依照某個特定的行來合併資料,可以使用 merge 函數:

x.data.frame <- data.frame(
  x = letters[1:6],
  foo.x = rnorm(6),
  bar.x = runif(6) > 0.5
)
x.data.frame
  x       foo.x bar.x
1 a -0.06067850  TRUE
2 b -0.01201291  TRUE
3 c -0.28666529 FALSE
4 d  1.64905115 FALSE
5 e -0.39324483  TRUE
6 f  0.25352265  TRUE
y.data.frame <- data.frame(
  foo.y = rnorm(6, 7, 9),
  bar.y = runif(6) < 0.5,
  x = letters[3:8]
)
y.data.frame
       foo.y bar.y x
1 -2.9982815  TRUE c
2 -0.1559176 FALSE d
3 12.6673980  TRUE e
4  6.6994744  TRUE f
5  0.3809737 FALSE g
6  3.3026685 FALSE h

使用 mergex.data.framey.data.frame 依據 x 欄位結合:

merge(x.data.frame, y.data.frame, by = "x")
  x      foo.x bar.x      foo.y bar.y
1 c -0.2866653 FALSE -2.9982815  TRUE
2 d  1.6490511 FALSE -0.1559176 FALSE
3 e -0.3932448  TRUE 12.6673980  TRUE
4 f  0.2535227  TRUE  6.6994744  TRUE

在使用 merge 合併 data frames 時,如果有些 x 欄位的值只出現在其中一個 data frame 中,而在另外一個 data frame 中找不到這個值的話,這一筆資料就會被忽略,如果需要將這樣不完全的資料也都保留的話,可以加上 all = TRUE 參數:

merge(x.data.frame, y.data.frame, by = "x", all = TRUE)
  x       foo.x bar.x      foo.y bar.y
1 a -0.06067850  TRUE         NA    NA
2 b -0.01201291  TRUE         NA    NA
3 c -0.28666529 FALSE -2.9982815  TRUE
4 d  1.64905115 FALSE -0.1559176 FALSE
5 e -0.39324483  TRUE 12.6673980  TRUE
6 f  0.25352265  TRUE  6.6994744  TRUE
7 g          NA    NA  0.3809737 FALSE
8 h          NA    NA  3.3026685 FALSE

對於含有數值資料的 data frame,我們可以使用 colSumscolMeans 函數來計算整個行的總和與平均:

colSums(x.data.frame[, 2:3])
   foo.x    bar.x
1.149972 4.000000
colMeans(x.data.frame[, 2:3])
    foo.x     bar.x
0.1916620 0.6666667

若要計算列的總和與平均,則可使用 rowSumsrowMeans 函數,用法都是類似的。