0%

R 語言教學入門|向量操作與資料結構基礎

在 R 語言中,「向量」(vector) 是最基本也最常見的資料結構。如果說資料分析是一場廚藝比賽,那麼向量就像是你手上的食材,沒了它,什麼料理也做不出來。無論你要分析學生的分數、病人的年齡,或是某家公司的每日股價,這些資料幾乎都會以向量的形式存在。

向量基本觀念

在數學裡面,向量就是一列數字組成的集合,而這些數字可以是整數、小數、甚至是複數。舉例來說:
$$
\vec{v} =
\begin{bmatrix}
1\
2\
3
\end{bmatrix}
$$
這是一個三維的列向量,裡面包含三個數值。我們也可以把它視為「一排有順序的資料」,每一個位置都有一個對應的值,而這樣的資料結構,在資料分析與統計學中非常常見。至於說為什麼向量這麼重要?R 是一門以統計為核心的語言,而統計的本質就是「資料的集合」。從最簡單的平均數、中位數,到複雜的迴歸分析、主成分分析,幾乎每一個步驟都在處理一組一組的資料——這些資料,基本上就是向量。

1
2
score <- c(80, 85, 90, 95, 100)
print(score)
[1]  80  85  90  95 100

這串數字,就是一個數值型的向量,其中包含 5 個元素,分別是 80、85、90、95 和 100。我們使用 c() 函數建立了這個向量,c 是 concatenate 的縮寫,代表將這些資料「合併」成一個向量。其實不只是數字,像是文字也可以變成向量。例如紀錄學生的姓名:

1
2
name <- c("小明", "小美", "小強")
print(name)
[1] "小明" "小美" "小強"

這是一個由字串組成的向量。重點在於:向量裡的元素,型別必須一致。必須注意的是,向量不是清單,也不是表格,有些初學者會把向量與之後才會提到的資料表(data frame)搞混。舉例來說,如果你同時有一個人的姓名、年齡與分數:

1
2
3
name <- c("小明", "小美")
age <- c(18, 19)
score <- c(90.2, 85.3)

這三個向量分別記錄了三種資料,但它們彼此是平行的,還不是一個「資料表」。資料表是更複雜的結構,我們後面會講。但在這之前,我們要先把向量的概念掌握清楚。回憶上一篇檢查型別的工具,我們仍可以應用 class() 來檢查向量屬於哪一種型別,也就是整坨向量裡面的型別是什麼。此外,我們也可以使用 length() 來看有多少個元素。以上面的三個變數 nameagescore 為例,我們可以逐一檢查型別與裡面含有多少元素。

1
2
3
4
5
6
7
8
9
10
11
# 檢查 name
print(class(name))
print(length(name))

# 檢查 age
print(class(age))
print(length(age))

# 檢查 score
print(class(score))
print(length(score))
[1] "character"
[1] 2
[1] "numeric"
[1] 2
[1] "numeric"
[1] 2

這兩個函數是我們日常資料處理的好幫手,任何一個向量拿到手,第一件事就該是檢查它的型別與長度,幫助我們知道該怎麼處理它。另外,若想知道向量的「內容與結構」,則可用 str() 函數來處理:

1
print(str(name))
 chr [1:2] "小明" "小美"
NULL

向量基本資料類型

在上一節我們提到,R 語言中的向量是由「相同型別的資料」所組成的一串資料。這裡的「相同型別」,其實就是向量的基本資料型別。也就是說,不論是數字、文字還是布林值,一旦它們被放進向量中,就必須保持一致,這是 R 語言處理資料的基本規則之一。

數值型

數值型資料就是最直觀的數字資料,例如溫度、分數、收入、距離等等。這類型是最常用的向量型別:

1
2
3
height <- c(160, 170, 180, 190)
print(height)
print(class(height))
[1] 160 170 180 190
[1] "numeric"

你會注意到,就算是整數,R 還是預設將它們視為 numeric(數值型),也就是所謂的雙精度浮點數(double precision)。如果真的需要整數型別,在上一篇文章提到可以在數字後面加上 L

字串型

當把名字、地址、國家名稱這類資料存進向量時,它們會被當作文字資料,也就是字串向量。

1
2
3
country <- c("Taiwan", "Japan", "Korea")
print(country)
print(class(country))
[1] "Taiwan" "Japan"  "Korea" 
[1] "character"

再次提醒:所有的字串都必須加上雙引號 " 或單引號 ',否則 R 會誤以為你是要引用一個變數名稱。

布林值

向量也可以存布林值,也就是 TRUEFALSE。這在資料分析中非常重要,例如用來標示是否符合某條件、是否遺失、是否為男性等:

1
2
3
pass <- c(TRUE, FALSE, TRUE, TRUE)
print(pass)
print(class(pass))
[1]  TRUE FALSE  TRUE  TRUE
[1] "logical"

型別自動轉換(Type Coercion)

那如果你不小心把不同型別的資料放在同一個向量裡,會發生什麼事?

1
2
3
mixed <- c(1, TRUE, "apple")
print(mixed)
print(class(mixed))
[1] "1"     "TRUE"  "apple"
[1] "character"

可以看到,R 自動將所有元素提升為字串,因為這是向量內部保持「同型別」的策略。在混合型別時,R 會依照以下規則做「型別轉換」:

1
logical < numeric < character

也就是說,一旦有字串出現,其他資料都會轉成字串;有數值但也有邏輯值,邏輯值就會變成數字(TRUE → 1,FALSE → 0):

1
2
3
num_logic <- c(TRUE, 3.5, FALSE)
print(num_logic)
print(class(num_logic))
[1] 1.0 3.5 0.0
[1] "numeric"

這種「隱性轉換」有時候是好事,能讓程式自動運作;但有時也可能導致錯誤或分析失真。因此,在寫程式時要有意識地檢查向量的型別,避免錯誤發生。而如果我們需要檢查整個向量是否為特定型別,一樣可以用 is.xxx() 函數來確認,這點與檢查變數的方法一致,故不在此贅述。

向量產生函式 c()

生活中,我們經常會做出「列清單」這件事。舉例來說,你去超市買東西前,可能會先寫下購物清單:

牛奶、雞蛋、吐司、蘋果

這張清單就是一個典型的「向量」:一組有順序、性質一致(都屬於購物項目)的資料。而在 R 裡,我們產生這種資料清單的工具,就是一個簡單卻非常強大的函數:c()。你可以想像它是一把膠水,把你提供的資料一個個黏在一起,組成一條向量。雖然上一小節已經給出簡單的範例,但畢竟關於 c() 的用法還有很多,因此會多花一點篇幅做講解。

建立數值向量

假設我們正在記錄一組分數,例如某學生五次小考的成績,我們可以用 c() 來產生一個數值向量:

1
2
scores <- c(90, 85, 88, 92, 87)
print(scores)
[1] 90 85 88 92 87

我們也可以建立一個分數組成的向量,例如令向量 $x$ 為一個實數向量,即 $x \in \mathbb{R}$,其中 $x$ 定義為:
$$
x = \left\{\frac{1}{k}, k = 1, 2, \cdots, 10\right\}
$$
我們可以這樣建立:

1
2
x = c(1/1, 1/2, 1/3, 1/4, 1/5, 1/6, 1/7, 1/8, 1/9, 1/10)
print(x)
 [1] 1.0000000 0.5000000 0.3333333 0.2500000 0.2000000 0.1666667 0.1428571
 [8] 0.1250000 0.1111111 0.1000000

建立整數向量

如果你很在意資料是否為整數(例如你在算人數、樓層數),那麼可以在數字後面加上 L 來強制設定為整數型:

1
2
floors <- c(1L, 2L, 3L)
print(floors)
[1] 1 2 3

這樣 R 就會把這些值存為 integer 而非 float。

建立字串向量

建立一個字串向量方法與數值向量差不多,只是把裡面的元素替換成字串而已。例如可以建立一個變數 student_name,裡面存放學生的名字,有 Anthony、Mason、Hsin、Jason。

1
2
student_name <- c("Anthony", "Mason", "Hsin", "Jason")
print(student_name)
[1] "Anthony" "Mason"   "Hsin"    "Jason"  

字串向量經常會作為後續比對、分類、或是文字探勘分析的依據。舉例來說,可以用字串向量來檢查一段文章裡面是否有特定字詞不斷地重複出現,進而做出詞頻分析,又或者是情緒分析等。

建立邏輯向量

邏輯向量最常被使用的地方就是進行篩選。由於邏輯向量裡的元素全部都是邏輯值,因此可以透過「是」、「否」的特性,將資料中的特定內容選出來或是排除掉,在資料分析、資料前處理或是報表產出時非常有用。舉一個生活化的例子來說明:你最近開始記帳,並想追蹤哪些日子花了超過 100 元。你先記錄下連續五天的支出情況:

1
spending <- c(80, 120, 95, 150, 60)

接下來,你想標示出哪些日子是「超過 100 元」,這就可以透過條件判斷產生一個邏輯向量:

1
2
overspent <- spending > 100
print(overspent)
[1] FALSE  TRUE FALSE  TRUE FALSE

這個 overspent 向量的每個位置,就對應 spending 中的每一筆資料。「超過 100」的那天會是 TRUE,其他則是 FALSE

向量連接

有時候,我們並不是一次就擁有所有資料,而是一天一天慢慢累積、一件事一件事慢慢記錄。這時候,你就會發現 c() 函數的另一個強大用途:向量可以不斷擴充、一直接下去。來看看這個例子:你想記錄最近每次喝咖啡的花費,但不是一次輸入完,而是每次消費後就更新一次資料。

1
coffee_spend <- c(80)  # 第一天喝咖啡,花了 80 元

第二天你又喝了一杯,這次花了 65 元,可以這樣更新:

1
coffee_spend <- c(coffee_spend, 65)

接下來幾天:

1
coffee_spend <- c(coffee_spend, 70, 90, 60)

現在我們來看看整體花費紀錄:

1
print(coffee_spend)
[1] 80 65 70 90 60

每一次你使用 c(舊資料, 新資料),就像是把一個新項目貼在日記本的最後一頁,日積月累,就成了一份有價值的生活紀錄。

向量基本操作

日常生活中,我們經常在腦中進行各種運算,例如:拿著發票比對中獎號碼、看著天氣預報推測明天會不會下雨、比較今天與昨天的股價變化等。這些都是一種「比較」或「運算」的邏輯思維。而在程式語言中,這種運算邏輯也有一套清楚且一致的語法來進行描述,R 語言當然也不例外。上一篇提到對於變數的基本操作,同樣也可以應用在向量上,不過會比變數的操作更多、功能更廣。

索引(Indexing)

在 R 語言中,索引是操作資料的核心技巧之一,讓我們能夠從資料中選取特定位置的元素。索引可以想像成超市貨架上的編號標籤,透過這些「標籤」,我們才能準確拿到自己要的商品。對向量進行索引時,最常見的方式就是用中括號 [],像這樣:

1
2
x <- c(10, 20, 30, 40, 50)
print(x[2]) # 取出第二個元素:20
[1] 20

R 的索引是從 1 開始的(不像 Python 是從 0 開始),這點非常重要。如果你想一次取出多個位置的元素,可以給一個向量:

1
print(x[c(1, 3, 5)])   # 取出第1、3、5個元素
[1] 10 30 50

當然,也可以取出某標籤至某標籤的元素,像是:

1
print(x[1:3])   # 取出第1個至第3個元素
[1] 10 20 30

如果想要刪掉特定元素,可以用在索引值前加上負號,而如果想要一次移除多個元素,可以用 c() 進行處理

1
2
3
print(x[-1])   # 移除第1個元素
print(x[c(-2, -3)]) # 移除第2個與第3個元素
print(x[-c(2, 3)]) # 移除第2個與第3個元素
[1] 20 30 40 50
[1] 10 40 50
[1] 10 40 50

向量算數操作

最直覺的運算莫過於數學四則運算,而在 R 中也幾乎與我們平常的筆算方式完全一致。這些符號既可以對「純數字」進行運算,也可應用在「向量」上,值得一提的是,R 中的運算是向量化(vectorized)的,表示你可以直接對整個向量進行運算,不需要像某些語言那樣寫迴圈來逐一處理,這是 R 的一大優勢。

1
2
3
4
5
x <- c(1, 2, 3, 4)
print(x + 1) # 每個元素都加 1
print(x * 2) # 每個元素都乘 2
print(x ^ 2) # 每個元素平方
print(x %% 2) # 每個元素除以 2 的餘數
[1] 2 3 4 5
[1] 2 4 6 8
[1]  1  4  9 16
[1] 1 0 1 0

向量關係操作

當我們想比較某個值是否大於、等於、或不等於某個值時,就需要使用關係運算子。這類運算的結果會產生「布林值」(TRUEFALSE),方便我們在後續進行條件判斷或篩選。

1
2
x <- c(1:10)
print(x %% 2 == 0) # 符合偶數的條件
 [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE

可以注意到在上面的例子中,我們用 : 建立一個 1 到 10 的向量,這樣建立向量既快速又不容易出錯,只要在冒號前加上起始值,後面放上結束值即可。含有布林值的向量非常適合用在資料篩選上,例如想找出所有成績超過 60 分的學生:

1
2
score <- c(55, 67, 45, 90)
print(score[score > 60]) # 取出大於 60 分的元素
[1] 67 90

向量邏輯運算

除了比較大小,我們還可以對邏輯向量進行操作,在處理多條件判斷時,邏輯運算特別實用,例如:

1
2
3
4
x <- 1:5
y <- c(3, 2, 4, 5, 1)

print((x > 2) & (y < 4)) # 將兩個條件交集比對
[1] FALSE FALSE FALSE FALSE  TRUE

結果會是一個邏輯向量,說明每個元素是否同時滿足兩個條件。這在資料篩選或模型指定時都非常重要。需要特別小心的是:&&&||| 雖然長得很像,但行為其實很不一樣。簡單來說:

  • &|逐一比對向量所有元素
  • &&||只比對第一個元素,多用於 if 條件語句。

💻 小試身手

小明經營一家水果店,這週他銷售了以下數量的水果(每種水果一天賣出一個數字):

1
2
apple <- c(10, 12, 8, 15, 9)     # 每天賣出的蘋果數量(週一到週五)
banana <- c(5, 6, 7, 4, 3) # 每天賣出的香蕉數量

蘋果一顆賣 20 元,香蕉一根賣 12 元。請你完成以下幾個小任務:

  1. 計算每天蘋果的銷售金額(每顆 20 元);
  2. 計算每天香蕉的銷售金額(每根 12 元);
  3. 計算每天的總營業額(蘋果 + 香蕉);
  4. 請問哪一天營業額超過了 300 元?
點我看答案

首先我們先定義蘋果與香蕉的數量:

1
2
apple <- c(10, 12, 8, 15, 9)     # 每天賣出的蘋果數量(週一到週五)
banana <- c(5, 6, 7, 4, 3) # 每天賣出的香蕉數量

R 語言的向量特性讓我們可以「一次算完一整週的金額」,而不需要使用 for 迴圈。

  • 蘋果一顆賣 20 元 → apple * 20
  • 香蕉一根賣 12 元 → banana * 12

這樣就能得到每一天的營業額(蘋果與香蕉分開算):

1
2
3
4
5
apple_revenue <- apple * 20
print(apple_revenue)

banana_revenue <- banana * 12
print(banana_revenue)
[1] 200 240 160 300 180
[1] 60 72 84 48 36

蘋果的營收向量加上香蕉的營收向量,就是每天的總營業額:

1
2
total_revenue <- apple_revenue + banana_revenue
print(total_revenue)
[1] 260 312 244 348 216

我們要判斷「哪些天總營業額超過 300 元」,這時候就用邏輯比較運算符號 >

1
print(total_revenue > 300)
[1] FALSE  TRUE FALSE  TRUE FALSE

由此可以看出第二天與第四天營業額超過 300 元。

其他向量特殊操作

在 R 中還有一些符號非常常見但容易忽略,例如:

  • %in%:用來測試一個元素是否包含在某個集合中
  • is.na():判斷是否為缺失值
  • any() / all():用來測試布林向量中是否有任何或全部為 TRUE
1
2
x <- c(1, 2, 3, 4, 5)
print(x %in% c(2, 4)) # 判斷向量 x 的元素有哪些在 [2, 4] 這個向量中
[1] FALSE  TRUE FALSE  TRUE FALSE

向量元素命名與進階索引技巧

在 R 的世界裡,向量是一切的起點,而命名與索引,就是讓這些數值資料變得「有意義」、「可操作」的兩把鑰匙。這一節我們將從命名說起,然後一路帶到索引的各種方式與技巧。學會這一節,你不只能「看得懂資料」,還能「選得到你想要的資料」。

給向量加上名字

我們可以把向量想成是一排排沒有名字的箱子,每個箱子裡放了一個數字。雖然可以透過位置(也就是索引)去找資料,但如果箱子上有標籤,我們會不會更容易辨識?這就是「向量元素命名」的目的。命名的方法有兩種,第一種是在建立向量時直接命名:

1
2
student_A <- c(age = 50, height = 183, weight = 70)
print(student_A)
   age height weight 
    50    183     70 

每個元素不再只是第 1、2、3 個數,而是有了明確的「意義」標籤,如此命名對於資料語意化、分析變數對應特別重要。第二種方式則是事後使用 names() 指定命名:

1
2
3
student_A <- c(25, 183, 70)
names(student_A) <- c("age", "height", "weight")
print(student_A)
   age height weight 
    25    183     70 

這種方式可以應用在需要將一組資料標上「欄位名稱」時,非常直觀且彈性高。不過有時候不想要名字了,怎麼辦?我們可以透過兩種方式處理:用 unname() 回傳不含名稱的純向量,或是用 names() 指定向量的名字為 NULL

1
2
3
4
5
6
# 方法一
print(unname(student_A))

# 方法二
names(student_A) <- NULL
print(student_A)
[1]  25 183  70
[1]  25 183  70

另外,如果今天有另一筆向量,其元素的順序與既有的向量欄位名稱吻合,則可以對齊兩筆資料的欄位名稱:

1
2
3
4
5
student_A <- c(age = 50, height = 183, weight = 70) # 先定義 student A
student_B <- c(28, 175, 80) # 再定義 student B

names(student_B) <- names(student_A) # 直接用 student A 的欄位名稱
print(student_B)
   age height weight 
    28    175     80 

字串索引

前面我們提到,向量可以透過數字進行索引,但如果今天向量已經有欄位了,那麼就可以直接用欄位名稱作為索引值。因此必須要搭配前面介紹的命名功能,才可以用名稱來選資料,而不用記得是哪個位置:

1
2
3
4
5
scores <- c(93.2, 34.4, 54.6, 73.7)
names(scores) <- c("Anthony", "Mason", "Hsin", "Jason")

print(scores["Mason"])
print(scores[c("Anthony", "Hsin")])
Mason 
 34.4 
Anthony    Hsin 
   93.2    54.6 

邏輯索引

我們可以用條件篩選資料,這是資料篩選中最實用的技巧之一。搭配布林判斷,便可以選出「符合條件」的元素:

1
2
3
4
5
price_change <- c(NA, 0.31, -0.42, NA, 0.53, -0.9, NA)

print(price_change[!is.na(price_change)]) # 去除缺失值
print(price_change[price_change > 0 & !is.na(price_change)]) # 篩選大於 0 且非 NA 的資料

[1]  0.31 -0.42  0.53 -0.90
[1] 0.31 0.53

甚至可以搭配其他邏輯條件使用:

1
2
price_change.clear <- price_change[!is.na(price_change)]
print(price_change.clear[price_change.clear < 0]) # 取出小於 0 的非缺失值
[1] -0.42 -0.90

處理缺失值

在現實世界中,我們很少能遇到完美無缺的資料。問卷可能有漏填,感測器可能失靈,匯入資料時也可能因格式問題導致部分欄位缺值。這些缺失值(missing values)在 R 中有一套明確的表示方式與處理邏輯。這個小節將從三種常見「不可用值」開始講解,進而學會怎麼判斷、處理與清理這些值。不過這邊有個小觀念:在資料分析的世界裡,「缺失值」本身也是一種資訊。有時候,缺失的背後有其脈絡(如問卷不願回答、機器讀不到數據),我們不能一律刪掉,而是要根據研究目的決定處理方式。

R 中三種常見「無效值」

名稱 表示 說明
NA Not Available 表示「缺失資料」,通常是資料來源本身沒有提供數值
NaN Not a Number 表示「非數值結果」,通常是數學運算結果無意義(如 0/0)
NULL 表示「完全不存在」,連空格都沒有(長度為 0 的物件)

如果看了不是很懂,我們來簡單說明一下這三個無效值的意義是什麼:

  • NA 是「有這個欄位,但不知道內容是什麼」;
  • NaN 是「運算過程中產生無意義結果」;
  • NULL 是「連欄位都沒有,直接不存在」。

首先我們來說明算數運算與 NA。只要某個元素是 NA,那麼 NA 會傳染整個運算,任何運算只要經過這個位置,結果就會變成 NA。R 無法推論「未知值 + 已知值 = 什麼」,所以保守地回傳 NA

1
2
x <- c(10, NA, 30)
print(x + 5)
[1] 15 NA 35

我們可以使用 is.na() 來檢查哪些位置是缺失值,回傳一個邏輯向量:

1
2
temp <- c(98.6, NA, 101.3)
print(is.na(temp))
[1] FALSE  TRUE FALSE

NaN 則是表示數學錯誤結果,例如:

1
2
3
sqrt(-1)      # 複數,回傳 NaN
log(-10) # 回傳 NaN(因 log 負數無意義)
0 / 0 # 回傳 NaN
Warning message in sqrt(-1):
“NaNs produced”

NaN

Warning message in log(-10):
“NaNs produced”

NaN

NaN

NaN 是特殊的一種 NA,用 is.nan() 可以特別挑出:

1
2
3
score <- c(88, 100, 0/0)
print(is.na(score))
print(is.nan(score))
[1] FALSE FALSE  TRUE
[1] FALSE FALSE  TRUE

這表示 NaN 同時也會被 is.na() 偵測到。最後,NULL 常出現在資料還沒初始化或是函數沒有回傳值時,與 NA 不同,它連容器都不存在,因此無法參與任何向量化操作。

1
2
x <- NULL
print(length(x))
[1] 0

處理缺失值的基本技巧

上面我們已經提過向量可以透過邏輯值進行篩選,而 is.na() 又可以回傳邏輯值,結合這兩個概念,我們就可以用「是 NA 值」的反敘述「不是 NA 值」來進行過濾,也就是 !is.na(),這是最常見的方式,從而保留非 NA 的資料。

1
2
3
weight <- c(65, NA, 72, NA, 80)
print(is.na(weight)) # 回傳邏輯向量
print(weight[!is.na(weight)]) # 用邏輯向量篩選
[1] FALSE  TRUE FALSE  TRUE FALSE
[1] 65 72 80

此外,我們也可以用 complete.cases() 同時清理多個向量,這招適用於需要同時清除「任一欄為 NA 的資料列」的情境,是資料框前處理的核心技能。

1
2
3
4
5
6
height <- c(170, 165, NA, 180)
gender <- c("M", "F", "F", NA)

valid <- complete.cases(height, gender)
print(height[valid])
print(gender[valid])
[1] 170 165
[1] "M" "F"

如果要一勞永逸,則可以直接用 na.omit() 一口氣清掉缺值,na.omit() 可以直接從向量或資料框中移除所有含 NA 的觀測值,其回傳結果是一個「已刪除缺值」的物件,並附註刪除哪些列。

1
2
income <- c(32000, NA, 45000, 39000, NA)
print(na.omit(income))
[1] 32000 45000 39000
attr(,"na.action")
[1] 2 5
attr(,"class")
[1] "omit"

類別資料:進階用法

在上一篇我們已經初步認識何謂類別資料,以及類別資料的型別是因子,也就是 factor,接下來我們可以結合向量與因子型別,深入了解在 R 中如何處理、標示與轉換這類資料。

類別資料複習

在統計學中,類別資料(categorical data)是指將觀察結果歸類為不同的群組或標籤。例如:

  • 性別:男 / 女
  • 教育程度:小學 / 國中 / 高中 / 大學
  • 健康狀態:良好 / 一般 / 不佳

這類資料的重點在於「所屬類別」而非「數值大小」,因此需要不同於數值資料的處理方式。而類別資料可依照「類別之間是否具備順序」分為兩類,一是名目變數(nominal variable),表示類別之間沒有順序關係,例如血型、城市等,我們無法陳述「A 型大於 B 型」。二是有序變數(ordinal variable),類別之間具備明確順序,但數值之間的間距無意義,例如:滿意度、疼痛指數等。即使我們給「輕度」標記為 1、「重度」標記為 3,這個數字只是方便排序,並不表示「重度比輕度多兩倍」。

為什麼 R 需要因子型別?

雖然可以用字串向量或數值向量來表示類別資料,但這些型別無法正確反映「分類資訊」的本質。例如:

1
2
symptom <- c("mild", "severe", "mild", "moderate")
print(class(symptom))
[1] "character"

這雖然可以列出內容,但並不代表它是「一組分類」,而且 R 也不會知道「moderate 比 mild 嚴重」。為此,R 引入因子型別,來專門處理類別資料。這類資料除了儲存每筆觀測值外,還會記錄所有可能的「類別水準」。

建立因子物件

如上一篇所提到的,我們可以使用 factor() 建立無序因子:

1
2
3
4
survey <- c("agree", "disagree", "agree", "neutral")
response <- factor(survey)
print(response)
print(class(response))
[1] agree    disagree agree    neutral 
Levels: agree disagree neutral
[1] "factor"

這就是一個標準的「名目變數」,沒有大小關係,只有歸屬分類。有時候我們希望控制水準的排序方式或改變顯示的標籤名稱,就可以指定 levels 順序與 labels 標籤,這對輸出報表、資料視覺化特別重要,可以確保結果按照邏輯順序排列。

1
2
3
4
status <- c("mid", "low", "high", "low", "mid")
status.f <- factor(status, levels = c("low", "mid", "high"))
levels(status.f) <- c("Low", "Medium", "High")
print(status.f)
[1] Medium Low    High   Low    Medium
Levels: Low Medium High

當類別具有「等級感」,例如疼痛、教育程度、病情輕重等,我們可以設定 ordered = TRUE

1
2
3
4
5
pain <- c("none", "mild", "moderate", "severe", "mild")
pain.levels <- c("none", "mild", "moderate", "severe")

pain.f <- factor(pain, levels = pain.levels, ordered = TRUE)
print(pain.f)
[1] none     mild     moderate severe   mild    
Levels: none < mild < moderate < severe

這樣一來,pain.f 不僅是因子,還是一個有順序的因子物件,可以用來比較,這是無序因子所無法做到的功能。

1
print(pain.f[2] < pain.f[3])
[1] TRUE

不過亂設 levels 可能會出現一些問題。以下是錯誤示範,會導致對應錯位:

1
2
3
score <- factor(c("medium", "high", "low"))
levels(score) <- c("Low", "Medium", "High")
print(score)
[1] High   Low    Medium
Levels: Low Medium High

此例中,原始值為 “medium”、”high”、”low”,但設定 levels 的順序錯誤,導致原值與顯示對應混亂。正確方式應在 factor() 時就指定 levels 順序:

1
2
3
4
score <- factor(c("medium", "high", "low"),
levels = c("low", "medium", "high"))
levels(score) <- c("Low", "Medium", "High")
print(score)
[1] Medium High   Low   
Levels: Low Medium High

處理缺失值與排除特定類別

factor()exclude 參數,可用來排除某些類別或將 NA 當成一種水準:

1
2
3
result <- c("pass", "fail", NA, "pass", "fail")
result.f <- factor(result, exclude = NULL)
print(levels(result.f))
[1] "fail" "pass" NA    

若希望排除某一類,這在需要忽略某些觀測值時相當方便。

1
2
result.f2 <- factor(result, exclude = c("fail", NA))
print(result.f2)
[1] pass <NA> <NA> pass <NA>
Levels: pass

重新設定參考水準

在進行統計建模(如 Logistic 回歸)時,因子的第一個水準會被預設為參考水準(reference level)。使用 relevel() 則可改變參考基準。

1
2
3
edu <- factor(c("junior high", "college", "master", "college", "elementary", "senior high"))
edu <- relevel(edu, ref = "college")
print(levels(edu))
[1] "college"     "elementary"  "junior high" "master"      "senior high"

將因子轉換成整數與文字

使用 as.integer() 可將因子轉換為對應的整數編碼,但這個編碼僅表示位置,數字本身無意義,不能直接進行加減乘除。

1
2
grade <- factor(c("B", "A", "C", "A"))
print(as.integer(grade))
[1] 2 1 3 1

若需轉換回文字,使用 as.character()

1
print(as.character(grade))
[1] "B" "A" "C" "A"