這裡介紹 R 向量的詳細使用方式,以及如何運用矩陣與陣列處理高維度的資料。

向量(Vectors)

在 R 中要建立向量最常使用的方式就是使用 c 函數,例如:

c(1, 3, 5)
[1] 1 3 5

另外使用冒號運算子(:)也是很常用的向量建立方式:

1:5
[1] 1 2 3 4 5

除了整數向量之外,它也可以產生浮點數的向量:

4.3:8.3
[1] 4.3 5.3 6.3 7.3 8.3

合併各種向量,產生更長的新向量:

c(1:4, 8, 9, c(12, 23))
[1]  1  2  3  4  8  9 12 23

vector 函數可以用來建立特定類型與長度的向量,例如:

vector("numeric", 3)
[1] 0 0 0
vector("logical", 3)
[1] FALSE FALSE FALSE
vector("character", 3)
[1] "" "" ""
vector("list", 3)
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

使用 vector 所建立的向量,其內部的值都是 0FALSENULL 這類的空值。對於一些常用的變數類型,R 提供了比較簡潔的函數方便使用者呼叫:

numeric(3)
[1] 0 0 0
logical(3)
[1] FALSE FALSE FALSE
character(3)
[1] "" "" ""

list 並沒有提供類似的簡潔函數,執行 list(3) 所得到的結果跟這裡所預期的不同。

序列(Sequences)

冒號運算子可以讓我們快速建立簡單的向量,如果需要產生較複雜的向量,在 R 中有一系列的相關函數可以使用。

seq 是一個可產生各種序列的函數,例如:

seq(2, 5)  # 等同於 2:5
[1] 2 3 4 5

seq 的步長可以使用 by 參數指定:

seq(2, 5, by = 0.5)
[1] 2.0 2.5 3.0 3.5 4.0 4.5 5.0

也可以使用 length 指定序列的長度,讓 seq 自動計算步長:

seq(2, 5, length = 5)
[1] 2.00 2.75 3.50 4.25 5.00

以下是一些 seq 的使用範例:

seq(10) # 等同於 1:10
seq(from = -5, to = 5, by = 0.2)
seq(length = 51, from = -5, by = 0.2)
seq(1, 9, by = pi)

seq 函數是最一般性的序列產生函數,而 R 中還有幾個跟 seq 相關的函數,專門用於產生特定類型的序列。

seq.intseq 類似,可以產生一般的數值序列,但其執行效率較高。

seq.int(2, 5)
seq.int(2, 5, by = 0.5)
seq.int(2, 5, length = 5)

seq_len 可產生特定長度之序列,比較特別的地方是它可以處理長度為零的序列:

n <- 0
1:n
[1] 1 0
seq_len(n)
integer(0)

seq_along 可以產生一個跟輸入向量相同長度的序列,而序列的內容就是從 1 到輸入向量的長度值,例如:

input <- c("hello", "foo", "bar")
seq_along(input)
[1] 1 2 3

seq_along 比較常見的用法是配合迴圈一起使用:

for(i in seq_along(input)) {
  print(input[i])
}

迴圈的使用我們在後續的教學會說明,這裡只需要有概念即可。

向量長度

所有的向量都有一個長度屬性,記錄該向量所含有的元素數量,我們可以使用 length 函數來檢查向量的長度:

length(1:3)
[1] 3

邏輯向量的用法也相同:

length(c(TRUE, FALSE, NA))
[1] 3

至於字元向量的話,也可以使用 length 來檢查向量的長度:

str <- c("hello", "foo", "bar")
length(str)
[1] 3

length 函數所顯示的是向量中有幾個元素,如果想要知道字串的長度,可以使用 nchar 函數來計算:

nchar(str)
[1] 5 3 3

length 也可以讓使用者直接改變向量的長度屬性,但這樣的做法比較不常使用,而且會讓程式碼不易閱讀。如果將向量的長度降低,超出向量長度的元素會被直接移除:

x <- 1:10
length(x) <- 3
x
[1] 1 2 3

而如果增加向量的長度,多出來的元素都會是 NA

length(x) <- 6
x
[1]  1  2  3 NA NA NA

在處理大量運算,需要預先配置記憶體時,有時候就會使用增加向量長度的方式來處理。

向量元素名稱

R 的向量有一個很特別的地方就是每一個元素都可以有一個自己的名稱,替向量的元素標上個別的名稱可以讓程式碼更容易被閱讀。在建立向量時,我們可以使用 name = value 的方式指定元素的名稱:

c(foo = 2, bar = 4)
foo bar
  2   4

如果遇到空白或是其他特殊字元,可以使用雙引號將名稱包起來:

c(foo = 2, bar = 4, "hello world" = 6, 8)
        foo         bar hello world
          2           4           6           8

一般的向量可以使用 names 函數來指定向量的名稱:

x <- 1:4
names(x) <- c("foo", "bar", "hello world", "")
x
        foo         bar hello world
          1           2           3           4

若要取得向量元素的名稱,可以使用 names 函數:

names(x)
[1] "foo"         "bar"         "hello world" ""

如果遇到沒有名稱的向量,names 會傳回 NULL

names(1:10)
NULL

索引向量

若要存取向量中部分的元素,可以使用索引向量(indexing vectors)配合中括號([])來達成,而索引向量的使用方式有四種,分別為邏輯向量、正整數向量、負整數向量與字元向量,以下是這四種索引向量的使用方式。

邏輯向量

這種情況下,索引向量必須和被挑選元素的向量長度一致。向量中對應索引向量為 TRUE 的元素將會被選出,而那些對應 FALSE 的元素則被忽略。例如:

x <- c(1, 2, NA,4)
y <- x[!is.na(x)]

這會將 x 向量中非 NA 的元素選出來,並依照原本的順序指定給 y,若 x 中包含 NA 元素,則所得到的 y 向量長度會比 x 向量的長度短。

另一個較複雜的例子:

x <- c(0, -1, -2, NA, 4, 6)
x <- x + 1
z <- x[(!is.na(x)) & x > 0]

這個例子是把 x 每個元素都加一,然後選出非 NA 且大於 0 的元素,依照原本的順序指定給 z

正整數向量

這種情況下,索引向量中的每個元素必須是 {1, 2, …, length(x)} 的其中一個(length(x) 為向量 x 的長度),索引向量中索引對應的元素將會被選出,並且依照索引向量中的順序傳回,這種索引向量的長度沒有限制,所選出的向量長度會與索引向量的長度相同,例如 x[6] 表示 x 的第六個元素,此外也可以重複取出多個元素:

x <- 10:20
y <- x[1:5]
z <- x[ c(1, 2, 3, 1, 2, 3) ]

這樣所得到的 yx 的前 5 個元素,而 z 則為 c(10, 11, 12, 10, 11, 12)

以下是一個更複雜的例子:

labs <- c("x", "y")[rep(c(1, 2, 2, 1), times = 4)]

會產生一個長度為 16,由 "x", "y", "y", "x" 重複四次而構成的向量。

rep() 為重複某個向量的函數。

負整數向量

這種索引向量與正整數向量規則一樣,負數的意思是將指定的元素排除,將剩餘的元素選出,例如:

x <- 10:20
y <- x[-(1:5)]

所得到的 yc(15, 16, 17, 18, 19, 20)

字元向量

這種索引向量只能用在可以用 names 屬性區別其元素的向量,在使用之前必須以 names 函數先設定 names 屬性,例如:

fruit <- c(5, 10, 1, 20)
names(fruit) <- c("orange", "banana", "apple", "peach")
lunch <- fruit["apple"]

使用字元向量取出多個元素:

dinner <- fruit[c("orange", "peach")]

使用字串向量的好處就是容易記,這在使用 data frames 的時候效果比較明顯。

以上就是四種索引向量的使用方式。

索引運算式也可以用在指定向量的元素上,在這種情況下只有那些被索引向量指定的元素會被更動,例如:

x <- c(1, 2, NA, NA, 3)
x[is.na(x)] <- 0

只會將 x 向量中的 NA 元素設為 0,其餘的元素不變。而

y <- c(-1, -2, 0, 1, 2)
y[y < 0] <- -y[y < 0]

是將 y 取絕對值,與下面這個作法相同

y <- c(-1, -2, 0, 1, 2)
y <- abs(y)

例外狀況

雖然索引向量的使用方式有好多種,通常一個問題可以有好多種解決方案,但是這幾種索引向量不可以隨意混用,例如若把正整數向量與負整數向量混用的話,會產生錯誤,而且也不具任何意義:

x <- 6:10
x[c(2, -2)]

這樣會產生錯誤:

Error in x[c(2, -2)] : 只有負數下標中才能有 0

如果索引向量中含有缺失值 NA,則篩選出來的向量中對應的位置也會是 NA

x[c(1, NA, 5)]
[1]  6 NA 10

這是邏輯向量的例子:

x[c(TRUE, FALSE, NA, FALSE, TRUE)]
[1]  6 NA 10

缺失值如果跟負整數向量混用的話,也會造成沒有意義的錯誤:

x[c(-2, NA)]
Error in x[c(-2, NA)] : 只有負數下標中才能有 0

如果正整數索引向量指定的索引超出向量的長度,就會產生 NA(不會有錯誤訊息):

x[7]
[1] NA

這種狀況雖然不會產生錯誤,但是在撰寫程式時建議還是確保索引向量不要超過向量為佳,這樣可以降低程式出錯的機率。

如果使用浮點數作為索引向量的值,這些浮點數小數點以下的值會被捨去,自動被轉換為整數,例如:

x[2.7] # 2.7 轉為 2
[1] 7
x[-2.7] # -2.7 轉為 -2
[1]  6  8  9 10

which 函數

which 函數可以檢查邏輯向量,傳回該向量中所有 TRUE 元素的位置,例如:

x <- 10:20
which(x %% 7 == 3)
[1] 1 8

這個例子是利用 which 找出 x 向量中所有除以 7 餘數為 3 的元素位置。

另外 which.minwhich.max 分別等同於 which(min(x))which(max(x)),不過其的執行效率更好:

which.min(x)
[1] 1
which.max(x)
[1] 11

重複向量

在處理向量的運算時,如果遇到長度不同的向量,R 就會將長度較短的向量自動重複,直到其長度跟最長的向量相同為止,例如:

1:3 + 1:9
[1]  2  4  6  5  7  9  8 10 12

這裡的 1:3 向量長度為 3,而 1:9 向量長度為 9,所以 R 會將 1:3 重複 3 次,變成 c(1, 2, 3, 1, 2, 3, 1, 2, 3) 之後,再跟 1:9 進行運算。

當遇到向量與常數相加時,也是依據同樣的規則處理:

1:5 + 1
[1] 2 3 4 5 6

這裡的 1 在 R 中其實是一個長度為 1 的向量,而為了要跟 1:5 這個長度為 5 的向量進行運算,R 會將 1 自動重複 5 次,變成 c(1, 1, 1, 1, 1) 之後再進行運算。

如果遇到最長向量長度不是較短向量長度的整數倍時,最後一次的向量重複內容就會不完整(超過的部分會捨去),並且也會產生警告訊息:

1:2 + 1:5
[1] 2 4 4 6 6
Warning message:
In 1:2 + 1:5 : 較長的物件長度並非較短物件長度的倍數

以這個例子來說,1:2 會重複 2.5 次,變成 c(1, 2, 1, 2, 1) 之外再進行運算。

雖然 R 可以自動處理向量長度不同的問題,但是除了將向量加上一個常數之外,通常不建議過度運用這樣的特性,因為這會造成程式碼在閱讀上的混淆,也容易產生 bugs,最好的方式還是建立相同長度的向量後再進行運算。

如果要產生重複性的向量,可以使用 rep 這個函數,其第一個參數是要重複的向量,而第二個參數則是重複次數:

rep(1:4, 3)
[1] 1 2 3 4 1 2 3 4 1 2 3 4

如果要讓每一個元素個別重複之後再串接起來,可以使用 each 參數:

rep(1:4, each = 3)
[1] 1 1 1 2 2 2 3 3 3 4 4 4

也可以透過向量的方式指定重複次數,讓每一個元素重複不同的次數:

rep(1:4, 4:1)
[1] 1 1 1 1 2 2 2 3 3 4

另外也可以直接指定輸出的向量長度,讓 rep 自動計算重複的次數:

rep(1:4, length.out = 7)
[1] 1 2 3 4 1 2 3

rep.int 是一個執行效率較高的版本,大部分的狀況下可以代替 rep

rep.int(1:4, 3)
[1] 1 2 3 4 1 2 3 4 1 2 3 4

rep_len 是另一個高執行效率的版本:

rep_len(1:4, 7)
[1] 1 2 3 4 1 2 3

矩陣與陣列

R 中的向量(vectors)是用來儲存一維資料的變數類型,而如果需要儲存二維以上的資料,就要使用陣列(arrays)來處理,而二維的陣列就是我們所熟知的矩陣(matrices)。

建立矩陣與陣列

R 的矩陣可以使用 matrix 函數建立,例如:

matrix(1:6, nrow = 2, ncol = 3)

這樣就會建立一個 2 x 3 的矩陣:

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

byrow 參數可以調整資料排列的方向:

matrix(1:6, nrow = 2, ncol = 3, byrow = TRUE)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6

矩陣的行與列可以使用 dimnames 參數指定名稱:

matrix(1:6, nrow = 2, ncol = 3,
  dimnames = list(c("row1", "row2"),
  c("C.1", "C.2", "C.3")))
     C.1 C.2 C.3
row1   1   3   5
row2   2   4   6

多維度的陣列則是使用 array 函數來建立:

array(1:24, dim = c(4, 3, 2))
, , 1

     [,1] [,2] [,3]
[1,]    1    5    9
[2,]    2    6   10
[3,]    3    7   11
[4,]    4    8   12

, , 2

     [,1] [,2] [,3]
[1,]   13   17   21
[2,]   14   18   22
[3,]   15   19   23
[4,]   16   20   24

多維度的陣列也可以使用 dimnames 參數指定每個維度的資料名稱:

array(1:24, dim = c(4, 3, 2), dimnames = list(
  X = c("A1","A2","A3","A4"),
  Y = c("B1", "B2", "B3"), Z = c("C1", "C2")))
, , Z = C1

    Y
X    B1 B2 B3
  A1  1  5  9
  A2  2  6 10
  A3  3  7 11
  A4  4  8 12

, , Z = C2

    Y
X    B1 B2 B3
  A1 13 17 21
  A2 14 18 22
  A3 15 19 23
  A4 16 20 24

事實上二維的陣列就是矩陣,不管使用 matrix 或是 array 來產生,結果都是一樣的:

x.matrix <- matrix(1:6, nrow = 2, ncol = 3)
x.array <- array(1:6, dim = c(2, 3))
identical(x.matrix, x.array)
[1] TRUE

如果我們檢查 x.array 的類型,會發現它實際上就是一個矩陣變數:

class(x.array)
[1] "matrix"

nrowncol 函數可以檢查矩陣的列數與行數:

nrow(x.matrix)
[1] 2
ncol(x.matrix)
[1] 3

nrowncol 函數若用於多維度的陣列,會傳回前兩個維度的長度。

x.array <- array(1:60, dim = c(3, 4, 5))
nrow(x.array)
[1] 3
ncol(x.array)
[1] 4

length 函數也可以用於矩陣或是陣列,他會計算矩陣或陣列中所有元素的個數(也就是所有維度長度的乘積):

length(x.matrix)
[1] 6
length(x.array)
[1] 60

若要改變矩陣或陣列的維度,可以使用 dim 指定新的維度:

x.matrix <- matrix(1:12, nrow = 4, ncol = 3)
dim(x.matrix) <- c(2, 6)
x.matrix
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    3    5    7    9   11
[2,]    2    4    6    8   10   12

甚至可以透過改變維度,將二維矩陣轉換為高維度的陣列:

dim(x.matrix) <- c(2, 3, 2)
x.matrix
, , 1

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

, , 2

     [,1] [,2] [,3]
[1,]    7    9   11
[2,]    8   10   12

nrowncoldim 這幾個函數如果用在一維的向量時,會傳回 NULL,如果想要避免這個問題,可以改用 NROWNCOL 函數,這兩個函數的作用跟 nrowncol 相同,但是當遇到一維的向量時也可以傳回有意義的值:

x.vector <- 1:5
nrow(x.vector)
NULL
NROW(x.vector)
[1] 5
ncol(x.vector)
NULL
NCOL(x.vector)
[1] 1

行、列與維度的名稱

矩陣與陣列各個維度的名稱可以使用 rownamescolnamesdimnames 函數來取得:

x.matrix <- matrix(1:6, nrow = 2, ncol = 3,
  dimnames = list(c("row1", "row2"),
  c("C.1", "C.2", "C.3")))
rownames(x.matrix)
[1] "row1" "row2"
colnames(x.matrix)
[1] "C.1" "C.2" "C.3"
dimnames(x.matrix)
[[1]]
[1] "row1" "row2"

[[2]]
[1] "C.1" "C.2" "C.3"

如果是陣列的話,用法也相同:

x.array <- array(1:24, dim = c(4, 3, 2), dimnames = list(
  X = c("A1","A2","A3","A4"),
  Y = c("B1", "B2", "B3"), Z = c("C1", "C2")))
rownames(x.array)
[1] "A1" "A2" "A3" "A4"
colnames(x.array)
[1] "B1" "B2" "B3"
dimnames(x.array)
$X
[1] "A1" "A2" "A3" "A4"

$Y
[1] "B1" "B2" "B3"

$Z
[1] "C1" "C2"

陣列索引

矩陣與高維度的陣列的索引用法跟一維的向量類似,只是索引的維度變高而已:

x.matrix[2, 1]
[1] 2
x.array[3, 2, 2]
[1] 19

也可以拿數值索引與維度的名稱混合使用:

x.matrix[2, c("C.2", "C.3")]
C.2 C.3
  4   6
x.array[3, c("B2", "B3"), "C2"]
B2 B3
19 23

若不指定維度的索引,就會選取整個維度的所有資料:

x.matrix[2, ]
C.1 C.2 C.3
  2   4   6
x.array[3, 2, ]
C1 C2
 7 19
x.array[3, , ]
    Z
Y    C1 C2
  B1  3 15
  B2  7 19
  B3 11 23

合併矩陣

假設我們有兩個矩陣:

x.matrix1 <- matrix(1:6, nrow = 3, ncol = 2)
x.matrix2 <- matrix(11:16, nrow = 3, ncol = 2)

如果使用 c 函數合併這兩個矩陣的話,所有的資料都會被轉換為一維的向量:

c(x.matrix1, x.matrix2)
[1]  1  2  3  4  5  6 11 12 13 14 15 16

cbindrbind 則可以讓資料保持矩陣的結構來合併:

cbind(x.matrix1, x.matrix2)
     [,1] [,2] [,3] [,4]
[1,]    1    4   11   14
[2,]    2    5   12   15
[3,]    3    6   13   16
rbind(x.matrix1, x.matrix2)
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6
[4,]   11   14
[5,]   12   15
[6,]   13   16

陣列的運算

矩陣在搭配四則運算子(+-*/)時,會對矩陣中個別元素進行運算:

x.matrix1 + x.matrix2
     [,1] [,2]
[1,]   12   18
[2,]   14   20
[3,]   16   22
x.matrix1 * x.matrix2
     [,1] [,2]
[1,]   11   56
[2,]   24   75
[3,]   39   96

若要將矩陣轉置,可以使用 t 函數:

t(x.matrix1)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6

而矩陣的乘法運算子是 %*%(內積)與 %o%(外積):

x.matrix1 %*% t(x.matrix1)
     [,1] [,2] [,3]
[1,]   17   22   27
[2,]   22   29   36
[3,]   27   36   45
1:3 %o% 4:6
     [,1] [,2] [,3]
[1,]    4    5    6
[2,]    8   10   12
[3,]   12   15   18

矩陣的外積也可以使用 outer 函數,它跟 %o% 運算子是一樣的:

outer(1:3, 4:6)
     [,1] [,2] [,3]
[1,]    4    5    6
[2,]    8   10   12
[3,]   12   15   18

冪次運算子(^)作用在矩陣上的時候,也是會對個別元素進行運算,所以在解反矩陣時,不能使用矩陣 -1 次方的方式計算:

m <- matrix(c(1, 0, 1, 5, -3, 1, 2, 4, 7), nrow = 3)
m ^ -1
     [,1]       [,2]      [,3]
[1,]    1  0.2000000 0.5000000
[2,]  Inf -0.3333333 0.2500000
[3,]    1  1.0000000 0.1428571

反矩陣要使用 solve 函數來計算:

m.inv <- solve(m)
m.inv
     [,1] [,2] [,3]
[1,]  -25  -33   26
[2,]    4    5   -4
[3,]    3    4   -3
m %*% m.inv
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1