0%

R 語言教學入門|資料框與資料處理藝術

在進入數據分析的世界,我們必須認識不同的資料結構。前面我們已經學習了向量,它像是一條單行的數字或文字;接著認識了矩陣,它如同一張二維的表格,但有個限制:所有元素必須是相同型別。然而,現實世界的資料往往是混合型的——一份調查可能同時包含受訪者的姓名(字串)、年齡(數值)和是否已婚(邏輯值)。這時,我們需要一個更靈活的資料結構,那就是資料框(data frame)。

資料框的本質:表格式資料結構

資料框是 R 語言中最常用、最核心的資料結構,就像是一本電子試算表或資料庫表格。它具有矩陣的表格形式,但允許不同欄位(變數)存放不同型別的資料。這種彈性使得資料框成為處理實際數據分析問題的理想工具。假設你是一位學校老師,手上有一份學生資料表:

姓名 年齡 性別 數學成績 是否及格
小明 16 85 TRUE
小華 17 72 TRUE
小美 16 91 TRUE
小強 17 45 FALSE

這就是一個典型的資料框,它包含了:

  • 字串型資料(姓名、性別)
  • 數值型資料(年齡、成績)
  • 邏輯型資料(是否及格)

而在 R 中,我們可以用以下方式表示這樣的資料框:

1
2
3
4
5
6
7
8
students <- data.frame(
name = c("小明", "小華", "小美", "小強"),
age = c(16, 17, 16, 17),
gender = c("男", "男", "女", "男"),
math_score = c(85, 72, 91, 45),
pass = c(TRUE, TRUE, TRUE, FALSE)
)
print(students)
  name age gender math_score  pass
1 小明  16     男         85  TRUE
2 小華  17     男         72  TRUE
3 小美  16     女         91  TRUE
4 小強  17     男         45 FALSE

這就是一個包含五個變數(欄位)、四個觀測值(列)的資料框。

從向量與矩陣到資料框:為什麼需要資料框

為什麼我們需要從向量和矩陣過渡到資料框?讓我們來比較一下如何用這些不同結構表示同樣的學生資料。如果用多個向量表示:

1
2
3
student_names <- c("小明", "小華", "小美", "小強")
student_ages <- c(16, 17, 16, 17)
student_math_scores <- c(85, 72, 91, 45)

這種方式雖然簡單,但有個致命問題:「向量之間的關聯性完全依賴於索引位置」。如果其中一個向量順序被打亂,我們就無法正確匹配資料。那如果用矩陣表示:

1
2
3
4
5
6
7
8
# 嘗試將所有資料放入一個矩陣
student_matrix <- matrix(
c("小明", "16", "男", "85", "TRUE",
"小華", "17", "男", "72", "TRUE",
"小美", "16", "女", "91", "TRUE",
"小強", "17", "男", "45", "FALSE"),
nrow = 4, byrow = TRUE
)

這裡的問題是:矩陣只能存放一種資料型別,所以所有數值都必須轉換為字串,導致我們無法直接對數值進行運算。以資料框表示的話,資料框就很好地解決了以上所有問題:

  • 保持各觀測值(列)之間的一致性,不會因為單一向量操作而打亂關聯
  • 允許每欄使用不同資料型別,數字就是數字,不需要轉換成字串
  • 有明確的欄位名稱,讓資料的識別和操作更加直觀

資料框與矩陣的區別:異質性 vs 同質性

資料框與矩陣雖然外觀相似,但有幾個關鍵區別:

特性 矩陣 (Matrix) 資料框 (Data Frame)
資料型別的彈性 所有元素必須是相同型別(同質性) 每一欄可以是不同型別(異質性),但每一欄內的元素必須相同型別
內部結構 是一個二維陣列 實際上是一個「列表」(list),每個元素都是一個向量(代表一個變數/欄位)
主要用途 主要用於線性代數和數學運算 主要用於統計分析和資料處理
屬性與方法 較為簡單,主要用於矩陣運算 具有更多特殊方法和功能,更適合實際資料分析

讓我們實際看看兩者的差異:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 建立一個全是數值的矩陣
num_matrix <- matrix(1:12, nrow = 3)
print(num_matrix)
print(class(num_matrix))

# 建立包含不同型別的資料框
mixed_df <- data.frame(
id = 1:3,
name = c("A", "B", "C"),
is_active = c(TRUE, FALSE, TRUE)
)
print(mixed_df)
print(class(mixed_df))

# 查看每個欄位的類型
print(sapply(mixed_df, class))
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
[1] "matrix" "array" 
  id name is_active
1  1    A      TRUE
2  2    B     FALSE
3  3    C      TRUE
[1] "data.frame"
         id        name   is_active 
  "integer" "character"   "logical" 

執行後可以看到,資料框的每一欄可以有不同的資料型別,這是它最關鍵的特性,讓資料處理更加自然和直觀。在後續章節中,我們將深入探討如何建立、操作和轉換資料框,以有效處理各種實際數據分析任務。

建立資料框:打造你的資料之家

想像你正在搬進一個新家,需要一個地方來存放各式各樣的物品—衣服、書籍、廚具等。對於 R 語言的資料分析師來說,資料框就是那個能夠整齊收納各種不同類型資料的「家」。無論你的資料來源為何,學會建立和組織資料框,就能讓你的資料分析工作事半功倍。

使用 data.frame() 函數從零開始建立

最直接的方式是使用 data.frame() 函數,將各個向量組合成一個資料框。每個向量會成為資料框的一個欄位(變數),而向量的長度必須相同,否則 R 會嘗試循環使用較短的向量來匹配最長向量的長度。

1
2
3
4
5
6
7
8
9
10
# 建立一個學生資訊的資料框
students <- data.frame(
name = c("小明", "小華", "小美", "小強"),
age = c(16, 17, 16, 17),
gender = c("男", "男", "女", "男"),
math_score = c(85, 72, 91, 45),
pass = c(TRUE, TRUE, TRUE, FALSE)
)

print(students)
  name age gender math_score  pass
1 小明  16     男         85  TRUE
2 小華  17     男         72  TRUE
3 小美  16     女         91  TRUE
4 小強  17     男         45 FALSE

在建立資料框時,有幾個重要參數可以控制其行為:

1
2
3
4
5
6
7
8
9
10
df <- data.frame(
# 各個欄位向量
col1 = vector1,
col2 = vector2,
...,
# 選擇性參數
stringsAsFactors = FALSE, # 控制是否將字串自動轉為因子
row.names = NULL, # 指定列名
check.names = TRUE # 檢查並修正欄位名稱
)

值得注意的是,在 R 4.0.0 版本之前,data.frame() 預設會將字串自動轉換為因子(stringsAsFactors = TRUE)。如果你使用較舊版本的 R,要特別留意這點:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在 R 4.0.0 之前的版本,字串預設會被轉為因子
old_df <- data.frame(
name = c("小明", "小華"),
age = c(16, 17)
)
print(class(old_df$name)) # "factor"

# 指定不要轉換為因子
new_df <- data.frame(
name = c("小明", "小華"),
age = c(16, 17),
stringsAsFactors = FALSE
)
print(class(new_df$name)) # "character"
[1] "character"
[1] "character"

從其他資料結構轉換為資料框

有時候,我們已經擁有其他資料結構,如矩陣、列表或向量,需要將它們轉換為資料框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1. 從矩陣轉換
# 建立一個數值矩陣
m <- matrix(1:12, nrow = 4, byrow = TRUE)
colnames(m) <- c("X", "Y", "Z")

# 轉換為資料框
df_from_matrix <- as.data.frame(m)
print(df_from_matrix)

# 2. 從從多個向量轉換
# 建立多個單獨的向量
names <- c("小明", "小華", "小美")
ages <- c(16, 17, 16)
scores <- c(85, 72, 91)

# 方法1:使用 data.frame()
df1 <- data.frame(
name = names,
age = ages,
score = scores
)
print(df1)

# 方法2:使用 cbind() 然後轉換
m <- cbind(names, ages, scores)
df2 <- as.data.frame(m)
print(df2)
   X  Y  Z
1  1  2  3
2  4  5  6
3  7  8  9
4 10 11 12
  name age score
1 小明  16    85
2 小華  17    72
3 小美  16    91
  names ages scores
1  小明   16     85
2  小華   17     72
3  小美   16     91

當使用 cbind() 或轉換矩陣時,要注意資料型別可能會統一為字串,這時需要手動轉換欄位型別:

1
2
3
4
5
# 修正資料型別
df2$ages <- as.numeric(df2$ages)
df2$scores <- as.numeric(df2$scores)

print(df2)
  names ages scores
1  小明   16     85
2  小華   17     72
3  小美   16     91

資料框的基本操作:掌握資料的鑰匙

一旦我們建立了資料框,就像擁有了一座資料的寶庫,但若沒有鑰匙,這些寶藏就無法被取用。在 R 語言中,掌握資料框的基本操作技巧,就是獲得打開這座寶庫的鑰匙。無論是資料科學家、統計分析師還是研究人員,都需要熟練運用這些技巧,才能從資料中萃取出有價值的資訊。

檢視資料框結構與內容

當拿到一個新的資料框時,第一步通常是想了解它的基本結構和內容。就像初次走進一座房子,我們會先環顧四周,了解房間的佈局。R 提供了多種函數讓我們能夠「環顧」資料框。以下是檢視資料框最常用的幾個函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 建立一個範例資料框
students <- data.frame(
name = c("小明", "小華", "小美", "小強", "小玲"),
age = c(16, 17, 16, 17, 18),
gender = c("男", "男", "女", "男", "女"),
math_score = c(85, 72, 91, 45, 93),
english_score = c(78, 85, 89, 52, 88),
pass = c(TRUE, TRUE, TRUE, FALSE, TRUE)
)

# 顯示資料框的前幾列
print(head(students)) # 預設顯示前6列,可加參數如 head(df, 10)

# 顯示資料框的後幾列
print(tail(students)) # 預設顯示後6列

# 顯示資料框的結構,包含變數類型和前幾筆資料
print(str(students))

# 取得資料框的敘述統計資訊
print(summary(students))

# 顯示資料框的維度(列數和欄數)
print(dim(students))

# 分別取得列數和欄數
print(nrow(students))
print(ncol(students))

# 取得欄位名稱
print(names(students)) # 或使用 colnames(students)
  name age gender math_score english_score  pass
1 小明  16     男         85            78  TRUE
2 小華  17     男         72            85  TRUE
3 小美  16     女         91            89  TRUE
4 小強  17     男         45            52 FALSE
5 小玲  18     女         93            88  TRUE
  name age gender math_score english_score  pass
1 小明  16     男         85            78  TRUE
2 小華  17     男         72            85  TRUE
3 小美  16     女         91            89  TRUE
4 小強  17     男         45            52 FALSE
5 小玲  18     女         93            88  TRUE
'data.frame':	5 obs. of  6 variables:
 $ name         : chr  "小明" "小華" "小美" "小強" ...
 $ age          : num  16 17 16 17 18
 $ gender       : chr  "男" "男" "女" "男" ...
 $ math_score   : num  85 72 91 45 93
 $ english_score: num  78 85 89 52 88
 $ pass         : logi  TRUE TRUE TRUE FALSE TRUE
NULL
     name                age          gender            math_score  
 Length:5           Min.   :16.0   Length:5           Min.   :45.0  
 Class :character   1st Qu.:16.0   Class :character   1st Qu.:72.0  
 Mode  :character   Median :17.0   Mode  :character   Median :85.0  
                    Mean   :16.8                      Mean   :77.2  
                    3rd Qu.:17.0                      3rd Qu.:91.0  
                    Max.   :18.0                      Max.   :93.0  
 english_score     pass        
 Min.   :52.0   Mode :logical  
 1st Qu.:78.0   FALSE:1        
 Median :85.0   TRUE :4        
 Mean   :78.4                  
 3rd Qu.:88.0                  
 Max.   :89.0                  
[1] 5 6
[1] 5
[1] 6
[1] "name"          "age"           "gender"        "math_score"   
[5] "english_score" "pass"         

有時候,我們需要更深入地了解資料框的特定部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 檢查每個欄位的資料類型
print(sapply(students, class))

# 查看資料框的前幾列和前幾欄
print(students[1:3, 1:4])

# 查看特定欄位的唯一值
print(unique(students$gender))

# 查看特定欄位的唯一值數量
print(length(unique(students$age)))

# 檢查是否有缺失值
print(any(is.na(students)))

# 若有缺失值,查看各欄位的缺失值數量
print(colSums(is.na(students)))
         name           age        gender    math_score english_score 
  "character"     "numeric"   "character"     "numeric"     "numeric" 
         pass 
    "logical" 


  name age gender math_score
1 小明  16     男         85
2 小華  17     男         72
3 小美  16     女         91
[1] "男" "女"
[1] 3
[1] FALSE
         name           age        gender    math_score english_score 
            0             0             0             0             0 
         pass 
            0 

選取與索引:存取資料框的元素

資料框作為一種二維結構,支持多種索引方式,讓我們能精確地取出所需資料。熟練掌握資料框的索引技巧,就像擁有了打開資料寶庫中每個抽屜的鑰匙。

使用方括號 []

在之前學習向量和矩陣時,我們已經接觸過使用方括號 [] 進行索引的方法。這個基本語法在資料框中同樣適用,只是由於資料框的二維性質,我們需要同時指定行和列的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基本語法:dataframe[row_indices, column_indices]

# 選取特定的列和欄
print(students[3, 4]) # 選取第3列第4欄的元素 (小美的數學成績)

# 選取整列或整欄
print(students[2, ]) # 選取第2列的所有欄 (小華的所有資料)
print(students[, 3]) # 選取第3欄的所有列 (所有人的性別)

# 選取多列或多欄
print(students[c(1, 3, 5), ]) # 選取第1、3、5列 (小明、小美、小玲的資料)
print(students[, c("name", "math_score")]) # 選取特定名稱的欄 (所有人的姓名和數學成績)

# 使用邏輯向量進行索引
print(students[students$age >= 17, ]) # 選取年齡大於等於17的學生
print(students[students$pass == TRUE, ]) # 選取及格的學生
[1] 91
  name age gender math_score english_score pass
2 小華  17     男         72            85 TRUE
[1] "男" "男" "女" "男" "女"
  name age gender math_score english_score pass
1 小明  16     男         85            78 TRUE
3 小美  16     女         91            89 TRUE
5 小玲  18     女         93            88 TRUE
  name math_score
1 小明         85
2 小華         72
3 小美         91
4 小強         45
5 小玲         93
  name age gender math_score english_score  pass
2 小華  17     男         72            85  TRUE
4 小強  17     男         45            52 FALSE
5 小玲  18     女         93            88  TRUE
  name age gender math_score english_score pass
1 小明  16     男         85            78 TRUE
2 小華  17     男         72            85 TRUE
3 小美  16     女         91            89 TRUE
5 小玲  18     女         93            88 TRUE

使用 $ 運算符

除了方括號索引之外,R 提供了一個特別為資料框設計的運算符 $,它可以讓我們直接存取特定欄位。這個運算符是資料框操作中最常用也最直觀的工具之一,它提供了一種簡潔的語法來訪問欄位,而不需要使用方括號和引號。$ 運算符的使用方式是 dataframe$column_name,其中 column_name 是欄位的名稱。這種方式特別適合當你需要頻繁訪問特定欄位,或者在複雜表達式中引用欄位時使用。

1
2
3
4
5
6
7
# 使用 $ 取得特定欄位
print(students$name) # 取得所有學生的姓名
print(students$math_score) # 取得所有學生的數學成績

# 結合 $ 與其他索引
print(students$name[c(1, 3)]) # 取得第1和第3個學生的姓名
print(students$math_score[students$gender == "女"]) # 取得女學生的數學成績
[1] "小明" "小華" "小美" "小強" "小玲"
[1] 85 72 91 45 93
[1] "小明" "小美"
[1] 91 93

使用 $ 運算符有幾個優點:首先,它的語法更加簡潔;其次,在許多整合式開發環境(integrated development environment, IDE) 中,輸入資料框名稱後跟著 $,會自動提示可用的欄位名稱,這大大提高了程式撰寫的效率;最後,它在程式碼閱讀時非常直觀,一眼就能看出正在訪問哪個資料框的哪個欄位。然而,需要注意的是,$ 運算符不能用於動態選擇欄位(例如,當欄位名稱存儲在變數中時)。在這種情況下,我們需要回到方括號索引,使用 dataframe[, column_name_variable] 的形式。

使用 subset() 函數

當我們需要按照特定條件篩選資料時,雖然可以使用方括號搭配邏輯表達式,但程式碼可能會變得冗長複雜。為此,R 提供了更加人性化的 subset() 函數,它專為資料篩選設計,語法更為簡潔直觀。subset 中文就是子集的意思,言下之意就是從一個集合中挑出集合中的集合,聽起來很拗口,但是還是得接受。subset() 函數接受三個主要參數:資料框、篩選條件,以及可選的欄位選擇。它的優點在於可以直接使用欄位名稱,無需每次都加上資料框名稱和 $ 符號,使得複雜的篩選條件表述更加清晰。

1
2
3
4
5
6
7
8
9
10
11
# 基本用法
print(subset(students, age >= 17)) # 選取年齡大於等於17的學生

# 同時選取特定欄位
print(subset(students,
gender == "女",
select = c(name, math_score, english_score))) # 選取女學生的姓名和成績

# 使用多個條件
print(subset(students,
math_score > 80 & english_score > 80)) # 數學和英文都大於80分的學生
  name age gender math_score english_score  pass
2 小華  17     男         72            85  TRUE
4 小強  17     男         45            52 FALSE
5 小玲  18     女         93            88  TRUE
  name math_score english_score
3 小美         91            89
5 小玲         93            88
  name age gender math_score english_score pass
3 小美  16     女         91            89 TRUE
5 小玲  18     女         93            88 TRUE

使用 with() 函數

在複雜的資料操作中,我們經常需要多次引用同一個資料框的不同欄位,如果每次都要寫 dataframe$column,不僅程式碼冗長,還容易出錯。with() 函數提供了一個臨時的環境,讓我們可以在其中直接使用欄位名稱,而不需要重複資料框的名稱。with() 函數特別適合用於複雜的條件表達式或計算,它接受一個資料框和一個表達式作為參數,然後在該資料框的上下文中評估表達式。

1
2
3
4
5
# 不使用 with()
print(students[students$math_score > 80 & students$english_score > 80, ])

# 使用 with()
print(with(students, students[math_score > 80 & english_score > 80, ]))
  name age gender math_score english_score pass
3 小美  16     女         91            89 TRUE
5 小玲  18     女         93            88 TRUE
  name age gender math_score english_score pass
3 小美  16     女         91            89 TRUE
5 小玲  18     女         93            88 TRUE

兩者的結果是一樣的,但使用 with() 的版本減少了重複,使程式碼更加清晰。在更複雜的分析中,這種簡化可以大大提高程式碼的可讀性和維護性。

新增、修改與刪除欄位

在實際的資料分析中,我們經常需要對資料框進行結構上的調整,例如根據現有資料計算新的變數,修正錯誤資料,或是移除不再需要的欄位。這些操作就像是重新安排你的辦公室佈局,使工作流程更加順暢和高效。

新增欄位

新增欄位的方法有多種,最直接的是使用 $ 運算符。這種方法允許我們創建全新的欄位,並立即為其賦值。在進行資料分析時,我們經常需要根據現有資料計算新變數,例如我們想要計算學生的總成績與平均分數,並加入到資料框中:

1
2
3
4
5
6
# 使用 $ 運算符新增單一欄位
students$total_score <- students$math_score + students$english_score
students$average_score <- students$total_score / 2

# 查看新增欄位後的資料框
print(head(students))
  name age gender math_score english_score  pass total_score average_score
1 小明  16     男         85            78  TRUE         163          81.5
2 小華  17     男         72            85  TRUE         157          78.5
3 小美  16     女         91            89  TRUE         180          90.0
4 小強  17     男         45            52 FALSE          97          48.5
5 小玲  18     女         93            88  TRUE         181          90.5

執行後,資料框會增加兩個新欄位,分別是總分和平均分。這種方法非常直觀,適合逐個添加欄位。如果需要一次性新增多個欄位,可以使用 transform() 函數一次新增多個欄位,它允許我們在一個函數調用中定義多個新欄位:

1
2
3
4
5
6
7
8
9
10
# 使用 transform() 一次新增多個欄位
students <- transform(students,
is_adult = age >= 18,
grade = ifelse(average_score >= 90, "優",
ifelse(average_score >= 80, "良",
ifelse(average_score >= 60, "及格", "不及格")))
)

# 查看新增欄位後的資料框
print(head(students))
  name age gender math_score english_score  pass total_score average_score
1 小明  16     男         85            78  TRUE         163          81.5
2 小華  17     男         72            85  TRUE         157          78.5
3 小美  16     女         91            89  TRUE         180          90.0
4 小強  17     男         45            52 FALSE          97          48.5
5 小玲  18     女         93            88  TRUE         181          90.5
  is_adult  grade
1    FALSE     良
2    FALSE   及格
3    FALSE     優
4    FALSE 不及格
5     TRUE     優

在這個例子中,我們使用 transform() 函數同時新增了兩個欄位:is_adult(根據年齡判斷是否成年)和 grade(根據平均分給出等級評價)。特別注意的是,我們使用了 ifelse() 函數進行條件判斷,這是在 R 中進行條件轉換的常用方法,如果不太了解 if-else 判斷語句的讀者,可以在之後提到流程控制時再回來看。

修改欄位

除了新增欄位,修改現有欄位也是資料處理的重要操作。在資料清理、轉換或重新編碼的過程中,我們常常需要修改欄位的值或資料類型。R 提供了多種修改欄位的方法,從簡單的全欄位替換到條件式修改特定值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修改整個欄位
students$gender <- factor(students$gender) # 將性別轉換為因子型別

# 條件式修改特定值
students$math_score[students$name == "小強"] <- 55 # 修改小強的數學成績

# 使用 transform() 修改多個欄位
students <- transform(students,
total_score = math_score + english_score, # 更新總分
average_score = (math_score + english_score) / 2 # 更新平均分
)

# 查看修改後的資料框
print(head(students))
  name age gender math_score english_score  pass total_score average_score
1 小明  16     男         85            78  TRUE         163          81.5
2 小華  17     男         72            85  TRUE         157          78.5
3 小美  16     女         91            89  TRUE         180          90.0
4 小強  17     男         55            52 FALSE         107          53.5
5 小玲  18     女         93            88  TRUE         181          90.5
  is_adult  grade
1    FALSE     良
2    FALSE   及格
3    FALSE     優
4    FALSE 不及格
5     TRUE     優

修改欄位時,可以根據具體需求選擇不同的方法:

  • 全欄位修改:如上面將 gender 欄位轉換為因子型別,這種操作會影響該欄位的所有值。
  • 條件式修改:如上面修改小強的數學成績,這種方法允許我們只修改符合特定條件的資料。這在數據清理中特別有用,例如修正特定觀測值中的異常或錯誤數據。
  • 批量修改:使用 transform() 函數可以一次修改或更新多個欄位。這在需要同時處理多個相關欄位時非常方便,例如根據更新的原始數據重新計算衍生變數。

修改欄位時需要注意資料類型的一致性,例如將數值型欄位的部分值修改為字串可能會導致整個欄位轉換為字串型別,這可能影響後續的數值計算。

刪除欄位

隨著資料分析的進行,我們可能會發現某些欄位已不再需要,或者為了簡化模型而需要移除某些變數。刪除欄位的方法有多種,每種適用於不同的情境:

  • 使用 NULL 賦值:這是移除單一欄位最簡單的方法,直接將該欄位設為 NULL 即可。
  • 負索引:使用負索引(如 -which(names(students) == "grade"))可以排除特定欄位,這在欄位名稱存儲在變數中時特別有用。
  • 選擇性保留:有時候,指定要保留的欄位可能比指定要刪除的欄位更簡單,尤其是當需要保留的欄位較少時。
  • 使用 subset() 函數:subset() 函數的 select 參數允許我們選擇要保留或刪除的欄位,語法簡潔且直觀。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用 NULL 移除單一欄位
students$is_adult <- NULL

# 使用索引移除多個欄位
students <- students[, -which(names(students) == "grade")] # 移除 grade 欄位

# 或者,選取要保留的欄位
students <- students[, c("name", "age", "gender", "math_score", "english_score", "pass", "total_score", "average_score")]

# 使用 subset() 選取要保留的欄位
students <- subset(students, select = -c(total_score)) # 移除 total_score 欄位

# 查看修改後的資料框
head(students)
  name age gender math_score english_score  pass average_score
1 小明  16     男         85            78  TRUE          81.5
2 小華  17     男         72            85  TRUE          78.5
3 小美  16     女         91            89  TRUE          90.0
4 小強  17     男         55            52 FALSE          53.5
5 小玲  18     女         93            88  TRUE          90.5

請注意,刪除欄位是不可逆的操作,因此在執行前最好確保這些欄位確實不再需要,或者已經有了備份。在一些情況下,尤其是處理大型資料集時,將不需要的欄位設為 NULL 比創建新的資料框更節省記憶體。

批次操作多個欄位

在資料分析中,我們經常需要對多個欄位執行相同的操作,例如標準化所有數值欄位、四捨五入到特定小數點位數,或對所有文字欄位進行格式轉換。如果一個一個欄位處理,不僅耗時且容易出錯,更重要的是,程式碼會變得冗長難以維護。R 提供了一系列強大的功能來批次處理多個欄位,其中 lapply() 函數是最常用的工具之一,並時常配合 [] 操作:

1
2
3
4
5
6
# 將所有數值型欄位四捨五入到1位小數
numeric_cols <- sapply(students, is.numeric)
students[, numeric_cols] <- lapply(students[, numeric_cols], function(x) round(x, 1))

# 查看結果
print(head(students))
  name age gender math_score english_score  pass average_score
1 小明  16     男         85            78  TRUE          81.5
2 小華  17     男         72            85  TRUE          78.5
3 小美  16     女         91            89  TRUE          90.0
4 小強  17     男         55            52 FALSE          53.5
5 小玲  18     女         93            88  TRUE          90.5

這段程式碼首先使用 sapply() 函數識別出資料框中的所有數值型欄位,返回一個邏輯向量 numeric_cols,其中 TRUE 表示對應的欄位是數值型。然後,使用 lapply() 函數對所有數值型欄位應用自定義的四捨五入函數,最後將處理結果賦值回原資料框的相應位置。

資料框的合併與連接

在實際的資料分析中,我們經常需要整合來自不同來源的資料。就像拼圖一樣,將各個片段組合起來,才能看到完整的圖像。R 提供了多種方法來合併和連接資料框,讓我們能夠整合不同的資料集。這些方法與我們在學習矩陣時介紹的 rbind()cbind() 函數有相似之處,但在處理資料框時有一些特別的考量和應用場景。不過在實際應用中,純粹的 cbind()rbind() 合併較少使用,因為它們要求資料框的結構嚴格匹配。更常見的是,我們需要根據某些共同鍵值(如ID、姓名等)來合併資料,這時就需要用到更靈活的方法,例如 merge() 函數,它能夠根據共同的欄位將不同結構的資料框智能地整合在一起。

使用 rbind() 垂直合併資料框

就像在矩陣章節中看到的,rbind() 用於將多個資料框「堆疊」起來,即垂直合併。不過,與矩陣不同的是,合併資料框時,這些資料框必須有相同的欄位名稱結構,而不僅僅是列數相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 建立兩個具有相同欄位的資料框
students1 <- data.frame(
name = c("小明", "小華"),
age = c(16, 17),
score = c(85, 72)
)

students2 <- data.frame(
name = c("小美", "小強"),
age = c(16, 17),
score = c(91, 45)
)

# 使用 rbind() 垂直合併
all_students <- rbind(students1, students2)

# 查看結果
print(all_students)
  name age score
1 小明  16    85
2 小華  17    72
3 小美  16    91
4 小強  17    45

需要注意的是,如果兩個資料框的欄位名稱或數量不同,rbind() 將會報錯。如果資料型別不一致,R 會嘗試進行自動轉換,這可能導致意想不到的結果。例如,如果一個資料框的某欄是數值,而另一個資料框的對應欄是字符,則合併後整個欄位都會變成字符型。

使用 cbind() 水平合併資料框

同樣,cbind() 也是從矩陣操作中熟悉的函數,用於將多個資料框「並排」,即水平合併。與矩陣合併不同的是,合併資料框時,我們需要關注列的一致性 - 不僅僅是列數相同,更重要的是確保每一列代表相同的觀測單位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 建立兩個具有相同列數的資料框
info <- data.frame(
name = c("小明", "小華", "小美", "小強"),
age = c(16, 17, 16, 17)
)

scores <- data.frame(
math = c(85, 72, 91, 45),
english = c(78, 85, 89, 52)
)

# 使用 cbind() 水平合併
students_info <- cbind(info, scores)

# 查看結果
print(students_info)
  name age math english
1 小明  16   85      78
2 小華  17   72      85
3 小美  16   91      89
4 小強  17   45      52

cbind() 在合併時不會檢查資料的相關性,僅依照列的順序進行合併,因此使用時需要確保資料排列的順序一致。如果兩個資料框的列數不同,R 會嘗試循環使用較短的資料框,這可能導致錯誤的資料對應。

使用 merge() 依照共同值合併資料框

在實際的資料分析中,我們通常需要整合來自不同來源或表格的資料。而且,與簡單的 rbind()cbind() 不同,這些資料往往需要根據某些共同的識別符(如ID、姓名、日期等)進行智能匹配,而不僅僅是按照位置堆疊。merge() 函數提供了這種強大的資料整合能力,它的工作原理類似於資料庫中的 JOIN 操作。merge() 函數提供了四種主要的合併方式,每種適用於不同的場景:

  • 內部合併(inner join):只保留在兩個資料框中都有匹配的觀測值。例如,只保留同時出現在學生基本資料和考試成績中的學生。
  • 左外部合併(left outer join):保留左側資料框的所有觀測值,不論是否在右側資料框中有匹配。缺失的資訊以 NA 填充。例如,保留所有註冊學生的資料,即使有些學生可能缺考。
  • 右外部合併(right outer join):保留右側資料框的所有觀測值,不論是否在左側資料框中有匹配。例如,保留所有參加考試的學生資料,即使有些可能是外校借讀生而沒有基本資料。
  • 完全外部合併(full outer join):保留兩個資料框中的所有觀測值,不論是否有匹配。這是最包容的合併方式,確保不遺漏任何資料。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 建立兩個有共同欄位 'name' 的資料框
students_info <- data.frame(
name = c("小明", "小華", "小美", "小強"),
age = c(16, 17, 16, 17),
gender = c("男", "男", "女", "男")
)

test_scores <- data.frame(
name = c("小明", "小華", "小美", "小玲"), # 注意:小強缺席,多了小玲
math = c(85, 72, 91, 93),
english = c(78, 85, 89, 88)
)

# 內部合併(僅保留兩個資料框都有的觀測值)
inner_join <- merge(students_info, test_scores, by = "name")

# 左外部合併(保留左邊資料框的所有觀測值)
left_join <- merge(students_info, test_scores, by = "name", all.x = TRUE)

# 右外部合併(保留右邊資料框的所有觀測值)
right_join <- merge(students_info, test_scores, by = "name", all.y = TRUE)

# 完全外部合併(保留所有觀測值)
full_join <- merge(students_info, test_scores, by = "name", all = TRUE)

# 查看各種合併結果
print("內部合併結果:")
print(inner_join)

print("左外部合併結果:")
print(left_join)

print("右外部合併結果:")
print(right_join)

print("完全外部合併結果:")
print(full_join)
[1] "內部合併結果:"
  name age gender math english
1 小明  16     男   85      78
2 小美  16     女   91      89
3 小華  17     男   72      85
[1] "左外部合併結果:"
  name age gender math english
1 小明  16     男   85      78
2 小美  16     女   91      89
3 小強  17     男   NA      NA
4 小華  17     男   72      85
[1] "右外部合併結果:"
  name age gender math english
1 小明  16     男   85      78
2 小玲  NA   <NA>   93      88
3 小美  16     女   91      89
4 小華  17     男   72      85
[1] "完全外部合併結果:"
  name age gender math english
1 小明  16     男   85      78
2 小玲  NA   <NA>   93      88
3 小美  16     女   91      89
4 小強  17     男   NA      NA
5 小華  17     男   72      85

merge() 函數的主要參數說明:

  • by:指定用於合併的共同欄位名稱
  • by.xby.y:當兩個資料框中的欄位名稱不同時,分別指定
  • all.x = TRUE:保留左側資料框的所有列(類似 SQL 中的 LEFT JOIN)
  • all.y = TRUE:保留右側資料框的所有列(類似 SQL 中的 RIGHT JOIN)
  • all = TRUE:保留兩側資料框的所有列(類似 SQL 中的 FULL OUTER JOIN)

在選擇合併方式時,應根據具體的分析需求和資料特性。例如,若要確保分析結果的完整性,可能會選擇左合併或完全合併;若只關注有完整資料的觀測值,則可能選擇內部合併。

操作資料框的小建議

  1. 檢查資料型別:合併前檢查共同欄位的資料型別是否一致,不一致可能導致合併失敗或不正確的結果。
1
2
3
4
5
6
7
# 檢查資料型別
sapply(students_info["name"], class)
sapply(test_scores["name"], class)

# 確保型別一致
students_info$name <- as.character(students_info$name)
test_scores$name <- as.character(test_scores$name)

name: ‘character’

name: ‘character’

  1. 處理重複值:如果合併欄位中有重複值,合併後的結果可能會出現多列。使用 unique() 或其他方法確保唯一性。
1
2
3
4
5
6
# 檢查是否有重複值
any(duplicated(students_info$name))
any(duplicated(test_scores$name))

# 若有重複值,可以考慮取第一筆或進行聚合
students_info <- students_info[!duplicated(students_info$name), ]

FALSE

FALSE

  1. 排序合併結果:合併後的資料順序可能會變化,通常建議重新排序。
1
2
3
# 依照 name 排序
merged_data <- merge(students_info, test_scores, by = "name")
merged_data <- merged_data[order(merged_data$name), ]
  1. 處理NA值:合併後可能出現NA值,根據分析需求決定如何處理。
1
2
3
4
5
# 移除含有NA的列
complete_data <- na.omit(merged_data)

# 或填充NA值
merged_data$math[is.na(merged_data$math)] <- 0

資料框的進階操作:資料魔術師的技巧

當你已經掌握了資料框的基本操作後,是時候提升技能層次,成為真正的「資料魔術師」了。進階的資料操作技巧不僅能幫助你更高效地清理和轉換資料,還能讓你的分析更加深入和精確。就像魔術師能夠讓觀眾看到不可思議的景象,資料魔術師也能從雜亂無章的原始資料中,變出令人驚嘆的洞見發現

依照條件篩選資料

資料分析中最常見的操作之一就是根據特定條件篩選資料。在龐大的資料集中找出符合特定條件的觀測值,就像在海洋中找到特定類型的魚一樣。R 提供了多種方法來進行這種篩選。

使用邏輯運算子篩選

最基本的篩選方法是使用邏輯運算子(如 >, <, ==, &, | 等),這些運算子可以創建邏輯條件,用來判斷每一列的資料是否符合我們設定的標準。在資料分析中,篩選資料是一個常見且關鍵的步驟,它能幫助我們專注於特定的觀測值子集,如果忘記的讀者可以回去複習。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 建立一個範例資料框
employees <- data.frame(
name = c("張小明", "李小華", "王小美", "趙小強", "陳小玲"),
age = c(25, 32, 28, 41, 35),
department = c("行銷", "技術", "行銷", "人資", "技術"),
salary = c(32000, 45000, 33000, 38000, 47000),
years_employed = c(1, 5, 2, 12, 7),
performance = c("良好", "優秀", "良好", "普通", "優秀")
)

# 篩選出年齡大於30的員工
senior_staff <- employees[employees$age > 30, ]
print(senior_staff)

# 篩選出行銷部門的員工
marketing_staff <- employees[employees$department == "行銷", ]
print(marketing_staff)

# 複合條件:技術部門且薪資大於40000的員工
tech_high_salary <- employees[employees$department == "技術" & employees$salary > 40000, ]
print(tech_high_salary)

# 使用 OR 條件:年資超過10年或薪資超過45000的員工
experienced_or_high_paid <- employees[employees$years_employed > 10 | employees$salary > 45000, ]
print(experienced_or_high_paid)
    name age department salary years_employed performance
2 李小華  32       技術  45000              5        優秀
4 趙小強  41       人資  38000             12        普通
5 陳小玲  35       技術  47000              7        優秀
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
3 王小美  28       行銷  33000              2        良好
    name age department salary years_employed performance
2 李小華  32       技術  45000              5        優秀
5 陳小玲  35       技術  47000              7        優秀
    name age department salary years_employed performance
4 趙小強  41       人資  38000             12        普通
5 陳小玲  35       技術  47000              7        優秀

這種篩選方法的關鍵在於方括號內的條件表達式。當我們寫 employees[employees$age > 30, ] 時,R 會先評估 employees$age > 30 這個條件,生成一個邏輯向量,其中對應位置為 TRUE 的行會被保留。方括號中的逗號後面沒有指定值,表示保留所有的列。
複合條件可以使用 &|運算子組合。例如,employees$department == "技術" & employees$salary > 40000 會找出既屬於技術部門又薪資高於 40,000 的員工。需要注意的是,當條件復雜時,適當使用括號來確定運算優先順序是很重要的。

使用 subset() 函數簡化篩選

前面已經提過 subset() 函數的好用之處,因此就不贅述其使用方式。

1
2
3
4
5
6
7
8
9
10
11
# 使用 subset() 篩選年齡大於30的員工
senior_staff <- subset(employees, age > 30)
print(senior_staff)

# 篩選行銷部門的員工,只選擇 name 和 salary 欄位
marketing_staff <- subset(employees, department == "行銷", select = c(name, salary))
print(marketing_staff)

# 複合條件:績效為優秀且年資超過5年的員工
good_experienced_staff <- subset(employees, performance == "優秀" & years_employed > 5)
print(good_experienced_staff)
    name age department salary years_employed performance
2 李小華  32       技術  45000              5        優秀
4 趙小強  41       人資  38000             12        普通
5 陳小玲  35       技術  47000              7        優秀
    name salary
1 張小明  32000
3 王小美  33000
    name age department salary years_employed performance
5 陳小玲  35       技術  47000              7        優秀

使用 which() 函數精確定位

which() 函數是R語言中進行資料篩選和定位的另一個強大工具。不同於直接使用邏輯條件進行篩選,which() 函數返回的是符合條件的元素在向量或矩陣中的位置索引,而不是元素本身。這種方法在某些情境下特別有用,尤其是當你需要知道特定元素的確切位置,或者需要執行更複雜的索引操作時。which() 函數的基本用法是將一個邏輯向量作為參數,返回其中為 TRUE 的元素的位置。此外,還有一些特殊的變體如 which.max()which.min(),它們分別返回向量中最大值和最小值的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 找出薪資最高的員工
max_salary_index <- which.max(employees$salary)
highest_paid <- employees[max_salary_index, ]
print(highest_paid)

# 找出薪資最低的員工
min_salary_index <- which.min(employees$salary)
lowest_paid <- employees[min_salary_index, ]
print(lowest_paid)

# 找出所有行銷部門員工的索引
marketing_indices <- which(employees$department == "行銷")
marketing_staff <- employees[marketing_indices, ]
print(marketing_staff)
    name age department salary years_employed performance
5 陳小玲  35       技術  47000              7        優秀
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
3 王小美  28       行銷  33000              2        良好

在第一個例子中,which.max(employees$salary) 直接返回薪資最高的員工在資料框中的行索引。這比先找出最大薪資,再用條件 employees$salary == max(employees$salary) 篩選更加高效,尤其是當資料中可能有多個最大值時(which.max() 只返回第一個最大值的位置)。類似地,which.min() 用於找出最小值的位置,在這個例子中是薪資最低的員工。

在第三個例子中,which(employees$department == "行銷") 返回所有行銷部門員工的行索引,然後我們使用這些索引從原始資料框中選取相應的行。這種方法在進行複雜索引操作時特別有用,例如當你需要基於一個條件找到位置,然後基於這些位置進行其他操作時。

which() 函數在處理缺失值時有一個重要特性:它會忽略 NA 值,只返回確定為 TRUE 的位置。這與直接使用邏輯向量進行索引不同,後者會將 NA 視為 FALSE。例如:

1
2
3
4
5
6
7
8
# 假設有NA值的向量
x <- c(1, NA, 3, 4, NA, 6)

# 直接用邏輯條件(NA會被視為FALSE)
print(x[x > 2]) # 結果只有 3, 4, 6

# 使用which()(完全忽略NA)
print(x[which(x > 2)]) # 結果相同,但處理方式不同
[1] NA  3  4 NA  6
[1] 3 4 6

which() 函數還有一些進階用法,例如在矩陣或多維陣列中,可以使用 which(x, arr.ind = TRUE) 返回元素的行列索引,而不僅僅是線性索引。

使用 %in% 運算子篩選多個值

當我們需要篩選符合多個可能值的資料時,%in% 運算子提供了一種簡潔有效的解決方案。這個運算子測試左側的值是否出現在右側的向量中,如果是,則返回 TRUE,否則返回 FALSE。在資料篩選中,%in% 運算子特別適合用於類別型變數,例如篩選特定的部門、產品類型、國家等。相比於使用多個 OR 條件(如 department == "行銷" | department == "技術" | ...),%in% 提供了更簡潔且可擴展的語法。

1
2
3
4
5
6
7
8
9
# 篩選行銷部門或技術部門的員工
target_departments <- c("行銷", "技術")
marketing_tech_staff <- employees[employees$department %in% target_departments, ]
print(marketing_tech_staff)

# 篩選特定名單中的員工
target_names <- c("張小明", "陳小玲")
specific_employees <- employees[employees$name %in% target_names, ]
print(specific_employees)
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
2 李小華  32       技術  45000              5        優秀
3 王小美  28       行銷  33000              2        良好
5 陳小玲  35       技術  47000              7        優秀
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
5 陳小玲  35       技術  47000              7        優秀

在第一個例子中,我們首先創建了一個包含目標部門的向量 target_departments,然後使用 %in% 運算子檢查每個員工的部門是否在這個向量中。這樣,我們就能輕鬆地篩選出所有屬於行銷或技術部門的員工,無論目標部門的數量有多少。

第二個例子展示了如何使用 %in% 運算子篩選特定名單中的員工。我們只需創建一個包含目標名字的向量,然後檢查每個員工的名字是否在這個向量中。這種方法在處理大量篩選值時特別有用。

%in% 運算子的一個重要特性是它會忽略順序和重複值。這意味著左側的值只要在右側向量中出現一次,就會返回 TRUE,不論它在右側向量中的位置或出現次數。例如:

1
2
3
# 順序和重複不影響結果
print("張小明" %in% c("陳小玲", "張小明", "張小明")) # 返回 TRUE
print("張小明" %in% c("陳小玲", "李小華")) # 返回 FALSE
[1] TRUE
[1] FALSE

%in% 運算子在處理缺失值時也有特定的行為。NA 值不被視為等於任何值,包括它自己,所以:

1
2
print(NA %in% c(1, 2, 3))       # 返回 FALSE
print(NA %in% c(1, 2, 3, NA)) # 返回 TRUE,因為NA在向量中
[1] FALSE
[1] TRUE

排序與排列資料

資料排序是另一項重要的資料操作技巧。透過適當的排序,我們可以讓資料的模式更容易被觀察到,比較變得更直觀,也便於報告和呈現。

使用 order() 函數排序

order() 是 R 中最常用的排序函數,它返回排序後的索引。因為有序的資料通常更容易理解和分析,例如按照年齡排序的人口資料可以幫助我們發現年齡分布模式;按照銷售額排序的產品資料可以直觀地顯示最暢銷的商品。這個函數不直接對資料進行排序,而是返回一個整數向量,指示元素在排序後的位置順序。這種設計非常靈活,允許我們根據排序結果對多個相關向量或資料框進行重新排列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 依照年齡從小到大排序
age_order <- order(employees$age)
employees_by_age <- employees[age_order, ]
print(employees_by_age)

# 依照薪資從高到低排序(遞減)
salary_desc_order <- order(employees$salary, decreasing = TRUE)
employees_by_salary_desc <- employees[salary_desc_order, ]
print(employees_by_salary_desc)

# 依照多個欄位排序:先依部門,再依薪資(從高到低)
dept_salary_order <- order(employees$department, -employees$salary)
employees_by_dept_salary <- employees[dept_salary_order, ]
print(employees_by_dept_salary)
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
3 王小美  28       行銷  33000              2        良好
2 李小華  32       技術  45000              5        優秀
5 陳小玲  35       技術  47000              7        優秀
4 趙小強  41       人資  38000             12        普通
    name age department salary years_employed performance
5 陳小玲  35       技術  47000              7        優秀
2 李小華  32       技術  45000              5        優秀
4 趙小強  41       人資  38000             12        普通
3 王小美  28       行銷  33000              2        良好
1 張小明  25       行銷  32000              1        良好
    name age department salary years_employed performance
4 趙小強  41       人資  38000             12        普通
3 王小美  28       行銷  33000              2        良好
1 張小明  25       行銷  32000              1        良好
5 陳小玲  35       技術  47000              7        優秀
2 李小華  32       技術  45000              5        優秀

在第一個例子中,order(employees$age) 返回一個整數向量,表示員工按年齡從小到大排序後的索引。然後,我們使用這個索引向量對 employees 資料框進行重新排列,得到按年齡排序的員工資料。

第二個例子展示了如何使用 decreasing = TRUE 參數實現遞減排序。這裡,我們按照薪資從高到低排序員工資料,這種排序在分析高薪員工特徵或識別薪資差距時可能特別有用。

第三個例子演示了多欄位排序的方法。首先按部門字母順序排序,然後在每個部門內按薪資從高到低排序。注意,我們使用了 -employees$salary 而不是 decreasing = TRUE,這是因為 order() 函數允許為每個欄位單獨指定排序方向。使用負號表示該欄位按遞減順序排序。

order() 函數與其他R函數結合使用時特別強大。例如,可以與 head() 結合找出前 $N$ 名:

1
2
3
4
# 找出薪資最高的三名員工
top3_salary_indices <- order(employees$salary, decreasing = TRUE)[1:3]
top3_employees <- employees[top3_salary_indices, ]
print(top3_employees)
    name         age department     salary years_employed performance
5 陳小玲  0.45009330       技術  1.1731769     0.36420116        優秀
2 李小華 -0.03214952       技術  0.8798827    -0.09105029        優秀
4 趙小強  1.41457895       人資 -0.1466471     1.50232980        普通
       bonus annual_increase   salary_z total_score performance_score is_tech
5  1.1375544      -0.5906096  1.1731769   3.7075931                 3    TRUE
2  1.0249252      -0.3909669  0.8798827   2.2705239                 3    TRUE
4 -0.9742421      -0.9004716 -0.1466471   0.7489008                 1   FALSE
  is_marketing
5        FALSE
2        FALSE
4        FALSE

使用 sort() 排序單個向量

當我們只需要排序單個向量而不是整個資料框時,sort() 函數提供了一種更直接的方法。與 order() 不同,sort() 直接返回排序後的向量值,而不是排序後的索引。

1
2
3
4
5
6
7
# 排序薪資(不影響原資料框)
sorted_salaries <- sort(employees$salary)
print(sorted_salaries)

# 排序薪資(遞減)
sorted_salaries_desc <- sort(employees$salary, decreasing = TRUE)
print(sorted_salaries_desc)
[1] 32000 33000 38000 45000 47000
[1] 47000 45000 38000 33000 32000

sort() 函數在資料探索階段特別有用,例如:

  • 快速查看資料的分布範圍:range(sort(x))
  • 確定分位數:sort(x)[ceiling(length(x) * 0.25)] 找出第一四分位數
  • 去除重複值並排序:sort(unique(x))
  • 找出極端值:sort(x)[c(1, length(x))] 找出最小值和最大值

雖然 sort() 函數在功能上比 order() 簡單,但這種簡單性正是它的優勢。當我們只需要關注單個變數的排序結果,而不需要維護多個變數之間的對應關係時,sort() 提供了更直接的解決方法。

使用 arrange() 函數(來自 dplyr 套件)

如果你安裝了 dplyr 套件,arrange() 函數提供了更直觀的排序方式。arrange() 函數的基本語法是 arrange(data, ...) ,其中 data 是要排序的資料框,... 是用於排序的一個或多個變數。相比於基礎 R 的 order() 函數,arrange() 有幾個顯著的優點:

  • 直觀的語法:不需要使用方括號索引,直接指定排序變數
  • 內建的遞減排序:使用 desc() 函數包裝變數即可實現遞減排序
  • 自動處理缺失值:預設將 NA 放在最後
  • 與其他 dplyr 函數無縫整合:可以輕鬆組合多個數據操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 如果尚未安裝 dplyr,請先安裝
# install.packages("dplyr")
library(dplyr)

# 依照年齡排序
employees_by_age <- arrange(employees, age)
print(employees_by_age)

# 依照薪資遞減排序
employees_by_salary_desc <- arrange(employees, desc(salary))
print(employees_by_salary_desc)

# 依照部門和年資排序
employees_by_dept_years <- arrange(employees, department, years_employed)
print(employees_by_dept_years)
    name age department salary years_employed performance
1 張小明  25       行銷  32000              1        良好
2 王小美  28       行銷  33000              2        良好
3 李小華  32       技術  45000              5        優秀
4 陳小玲  35       技術  47000              7        優秀
5 趙小強  41       人資  38000             12        普通
    name age department salary years_employed performance
1 陳小玲  35       技術  47000              7        優秀
2 李小華  32       技術  45000              5        優秀
3 趙小強  41       人資  38000             12        普通
4 王小美  28       行銷  33000              2        良好
5 張小明  25       行銷  32000              1        良好
    name age department salary years_employed performance
1 趙小強  41       人資  38000             12        普通
2 李小華  32       技術  45000              5        優秀
3 陳小玲  35       技術  47000              7        優秀
4 張小明  25       行銷  32000              1        良好
5 王小美  28       行銷  33000              2        良好

排序與排列的技巧

  1. 保存排序結果:排序後的結果通常需要賦值給新變數,以免覆蓋原始資料。

  2. 複合排序:可以同時根據多個條件排序,越前面的條件優先級越高。

  3. 處理缺失值:預設情況下,NA 值會被排在最後,可以透過 na.last 參數控制:

1
2
3
4
5
# 將 NA 值放在最前面
employees$bonus <- c(5000, NA, 3000, 2000, 8000)
bonus_order <- order(employees$bonus, na.last = FALSE)
employees_by_bonus <- employees[bonus_order, ]
print(employees_by_bonus)
    name age department salary years_employed performance bonus
2 李小華  32       技術  45000              5        優秀    NA
4 趙小強  41       人資  38000             12        普通  2000
3 王小美  28       行銷  33000              2        良好  3000
1 張小明  25       行銷  32000              1        良好  5000
5 陳小玲  35       技術  47000              7        優秀  8000

適當的排序可以讓資料更有條理,幫助你在視覺上更快發現資料中的規律和異常。

資料的轉換與運算

資料分析中經常需要對原始資料進行各種轉換和運算,以便獲得更有意義的指標和變數。這些操作可以從簡單的算術運算到複雜的統計函數,是資料前處理的重要環節。

對欄位進行算術運算

最基本的資料轉換是算術運算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 計算員工的每年加薪幅度(假設薪資增長率與年資相關)
employees$annual_increase <- employees$salary / employees$years_employed
print(employees)

# 計算績效獎金(假設與薪資成比例)
employees$bonus <- ifelse(employees$performance == "優秀", employees$salary * 0.2,
ifelse(employees$performance == "良好", employees$salary * 0.1, employees$salary * 0.05))
print(employees)

# 薪資標準化(減去平均值再除以標準差)
mean_salary <- mean(employees$salary)
sd_salary <- sd(employees$salary)
employees$salary_z <- (employees$salary - mean_salary) / sd_salary
print(employees)
    name age department salary years_employed performance bonus annual_increase
1 張小明  25       行銷  32000              1        良好  5000       32000.000
2 李小華  32       技術  45000              5        優秀    NA        9000.000
3 王小美  28       行銷  33000              2        良好  3000       16500.000
4 趙小強  41       人資  38000             12        普通  2000        3166.667
5 陳小玲  35       技術  47000              7        優秀  8000        6714.286
    name age department salary years_employed performance bonus annual_increase
1 張小明  25       行銷  32000              1        良好  3200       32000.000
2 李小華  32       技術  45000              5        優秀  9000        9000.000
3 王小美  28       行銷  33000              2        良好  3300       16500.000
4 趙小強  41       人資  38000             12        普通  1900        3166.667
5 陳小玲  35       技術  47000              7        優秀  9400        6714.286
    name age department salary years_employed performance bonus annual_increase
1 張小明  25       行銷  32000              1        良好  3200       32000.000
2 李小華  32       技術  45000              5        優秀  9000        9000.000
3 王小美  28       行銷  33000              2        良好  3300       16500.000
4 趙小強  41       人資  38000             12        普通  1900        3166.667
5 陳小玲  35       技術  47000              7        優秀  9400        6714.286
    salary_z
1 -1.0265298
2  0.8798827
3 -0.8798827
4 -0.1466471
5  1.1731769

使用 apply() 家族函數批次處理

apply() 家族函數是 R 中進行批次資料處理的強大工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 對所有數值欄位計算平均值
numeric_cols <- sapply(employees, is.numeric)
col_means <- sapply(employees[, numeric_cols], mean, na.rm = TRUE)
print(col_means)

# 對每個數值欄位進行標準化
employees[, numeric_cols] <- lapply(employees[, numeric_cols], function(x) {
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
})
print(employees)

# 使用 apply() 對每一列(每個員工)計算數值欄位的總和
row_sums <- apply(employees[, numeric_cols], 1, sum, na.rm = TRUE)
employees$total_score <- row_sums
print(employees)
            age          salary  years_employed           bonus annual_increase 
          32.20        39000.00            5.40         5360.00        13476.19 
       salary_z 
           0.00 
    name         age department     salary years_employed performance
1 張小明 -1.15738277       行銷 -1.0265298    -1.00155320        良好
2 李小華 -0.03214952       技術  0.8798827    -0.09105029        優秀
3 王小美 -0.67513995       行銷 -0.8798827    -0.77392747        良好
4 趙小強  1.41457895       人資 -0.1466471     1.50232980        普通
5 陳小玲  0.45009330       技術  1.1731769     0.36420116        優秀
       bonus annual_increase   salary_z
1 -0.6081974       1.6179375 -1.0265298
2  1.0249252      -0.3909669  0.8798827
3 -0.5800401       0.2641106 -0.8798827
4 -0.9742421      -0.9004716 -0.1466471
5  1.1375544      -0.5906096  1.1731769
    name         age department     salary years_employed performance
1 張小明 -1.15738277       行銷 -1.0265298    -1.00155320        良好
2 李小華 -0.03214952       技術  0.8798827    -0.09105029        優秀
3 王小美 -0.67513995       行銷 -0.8798827    -0.77392747        良好
4 趙小強  1.41457895       人資 -0.1466471     1.50232980        普通
5 陳小玲  0.45009330       技術  1.1731769     0.36420116        優秀
       bonus annual_increase   salary_z total_score
1 -0.6081974       1.6179375 -1.0265298  -3.2022555
2  1.0249252      -0.3909669  0.8798827   2.2705239
3 -0.5800401       0.2641106 -0.8798827  -3.5247623
4 -0.9742421      -0.9004716 -0.1466471   0.7489008
5  1.1375544      -0.5906096  1.1731769   3.7075931

類別型資料的轉換

處理類別型資料通常涉及重新編碼或轉換為因子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 將部門轉換為因子
employees$department <- factor(employees$department)
print(levels(employees$department))

# 將績效轉換為有序因子
employees$performance <- factor(employees$performance,
levels = c("普通", "良好", "優秀"),
ordered = TRUE)
print(employees$performance)

# 將績效轉換為數值評分
performance_scores <- c("普通" = 1, "良好" = 2, "優秀" = 3)
employees$performance_score <- as.numeric(performance_scores[as.character(employees$performance)])
print(employees)

# 部門分類重編碼(前端方便製作虛擬變數)
employees$is_tech <- employees$department == "技術"
employees$is_marketing <- employees$department == "行銷"
print(employees)
[1] "人資" "行銷" "技術"
[1] 良好 優秀 良好 普通 優秀
Levels: 普通 < 良好 < 優秀
    name         age department     salary years_employed performance
1 張小明 -1.15738277       行銷 -1.0265298    -1.00155320        良好
2 李小華 -0.03214952       技術  0.8798827    -0.09105029        優秀
3 王小美 -0.67513995       行銷 -0.8798827    -0.77392747        良好
4 趙小強  1.41457895       人資 -0.1466471     1.50232980        普通
5 陳小玲  0.45009330       技術  1.1731769     0.36420116        優秀
       bonus annual_increase   salary_z total_score performance_score
1 -0.6081974       1.6179375 -1.0265298  -3.2022555                 2
2  1.0249252      -0.3909669  0.8798827   2.2705239                 3
3 -0.5800401       0.2641106 -0.8798827  -3.5247623                 2
4 -0.9742421      -0.9004716 -0.1466471   0.7489008                 1
5  1.1375544      -0.5906096  1.1731769   3.7075931                 3
    name         age department     salary years_employed performance
1 張小明 -1.15738277       行銷 -1.0265298    -1.00155320        良好
2 李小華 -0.03214952       技術  0.8798827    -0.09105029        優秀
3 王小美 -0.67513995       行銷 -0.8798827    -0.77392747        良好
4 趙小強  1.41457895       人資 -0.1466471     1.50232980        普通
5 陳小玲  0.45009330       技術  1.1731769     0.36420116        優秀
       bonus annual_increase   salary_z total_score performance_score is_tech
1 -0.6081974       1.6179375 -1.0265298  -3.2022555                 2   FALSE
2  1.0249252      -0.3909669  0.8798827   2.2705239                 3    TRUE
3 -0.5800401       0.2641106 -0.8798827  -3.5247623                 2   FALSE
4 -0.9742421      -0.9004716 -0.1466471   0.7489008                 1   FALSE
5  1.1375544      -0.5906096  1.1731769   3.7075931                 3    TRUE
  is_marketing
1         TRUE
2        FALSE
3         TRUE
4        FALSE
5        FALSE

日期與時間的處理

在實際資料中,日期和時間的處理非常常見:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 建立一個含日期的資料框
employee_dates <- data.frame(
name = c("張小明", "李小華", "王小美"),
hire_date = c("2022-05-15", "2018-11-03", "2021-08-22")
)

# 將字串轉換為日期型別
employee_dates$hire_date <- as.Date(employee_dates$hire_date)
print(employee_dates)

# 計算工作天數
today <- Sys.Date()
employee_dates$days_employed <- as.numeric(today - employee_dates$hire_date)
print(employee_dates)

# 提取年、月、日
employee_dates$hire_year <- format(employee_dates$hire_date, "%Y")
employee_dates$hire_month <- format(employee_dates$hire_date, "%m")
employee_dates$hire_day <- format(employee_dates$hire_date, "%d")
print(employee_dates)
    name  hire_date
1 張小明 2022-05-15
2 李小華 2018-11-03
3 王小美 2021-08-22
    name  hire_date days_employed
1 張小明 2022-05-15          1061
2 李小華 2018-11-03          2350
3 王小美 2021-08-22          1327
    name  hire_date days_employed hire_year hire_month hire_day
1 張小明 2022-05-15          1061      2022         05       15
2 李小華 2018-11-03          2350      2018         11       03
3 王小美 2021-08-22          1327      2021         08       22

處理缺失值

在真實世界的資料集中,缺失值是不可避免的問題。有效地處理這些缺失值是資料清理的關鍵步驟,直接影響後續分析的質量和可靠性。

識別缺失值

首先,需要識別資料中的缺失值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 建立含有缺失值的資料框
employee_data <- data.frame(
name = c("張小明", "李小華", "王小美", "趙小強", "陳小玲"),
age = c(25, 32, NA, 41, 35),
department = c("行銷", "技術", "行銷", NA, "技術"),
salary = c(32000, 45000, 33000, 38000, NA),
years_employed = c(1, 5, 2, NA, 7)
)

# 檢查每個欄位的缺失值數量
na_counts <- colSums(is.na(employee_data))
print(na_counts)

# 檢查哪些列(觀測值)含有缺失值
rows_with_na <- apply(employee_data, 1, function(x) any(is.na(x)))
print(employee_data[rows_with_na, ])

# 檢查完整的觀測值(沒有缺失值的列)
complete_rows <- complete.cases(employee_data)
print(employee_data[complete_rows, ])
          name            age     department         salary years_employed 
             0              1              1              1              1 
    name age department salary years_employed
3 王小美  NA       行銷  33000              2
4 趙小強  41       <NA>  38000             NA
5 陳小玲  35       技術     NA              7
    name age department salary years_employed
1 張小明  25       行銷  32000              1
2 李小華  32       技術  45000              5

移除缺失值

最簡單的處理方法是移除含有缺失值的觀測值,但這可能會導致資料損失。在決定是否移除缺失值時,我們需要權衡資料完整性與分析可靠性之間的關係。如果缺失值比例較低,且呈隨機分布,那麼移除它們通常不會對分析結果產生重大影響;但如果缺失值比例較高,或者與特定變數或條件相關,那麼簡單地移除它們可能會引入偏差,導致結果失真。

1
2
3
4
5
6
7
8
9
# 移除含有任何缺失值的列
employee_data_clean <- na.omit(employee_data)
print(employee_data_clean)

# 移除缺失值過多的欄位(例如超過50%)
na_prop <- colMeans(is.na(employee_data))
keep_cols <- na_prop < 0.5 # 保留缺失值比例小於50%的欄位
employee_data_reduced <- employee_data[, keep_cols]
print(employee_data_reduced)
    name age department salary years_employed
1 張小明  25       行銷  32000              1
2 李小華  32       技術  45000              5
    name age department salary years_employed
1 張小明  25       行銷  32000              1
2 李小華  32       技術  45000              5
3 王小美  NA       行銷  33000              2
4 趙小強  41       <NA>  38000             NA
5 陳小玲  35       技術     NA              7

填補缺失值

填補缺失值是保留資料量的一種方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 用平均值填補數值型欄位的缺失值
employee_data$age[is.na(employee_data$age)] <- mean(employee_data$age, na.rm = TRUE)
employee_data$salary[is.na(employee_data$salary)] <- mean(employee_data$salary, na.rm = TRUE)
print(employee_data)

# 用中位數填補(對極端值更穩健)
employee_data$years_employed[is.na(employee_data$years_employed)] <- median(employee_data$years_employed, na.rm = TRUE)
print(employee_data)

# 用眾數填補類別型欄位
get_mode <- function(x) {
ux <- unique(x)
ux[which.max(tabulate(match(x, ux)))]
}

# 移除 NA 後計算眾數
dept_mode <- get_mode(employee_data$department[!is.na(employee_data$department)])
# 用眾數填補缺失值
employee_data$department[is.na(employee_data$department)] <- dept_mode
print(employee_data)
    name   age department salary years_employed
1 張小明 25.00       行銷  32000              1
2 李小華 32.00       技術  45000              5
3 王小美 33.25       行銷  33000              2
4 趙小強 41.00       <NA>  38000             NA
5 陳小玲 35.00       技術  37000              7
    name   age department salary years_employed
1 張小明 25.00       行銷  32000            1.0
2 李小華 32.00       技術  45000            5.0
3 王小美 33.25       行銷  33000            2.0
4 趙小強 41.00       <NA>  38000            3.5
5 陳小玲 35.00       技術  37000            7.0
    name   age department salary years_employed
1 張小明 25.00       行銷  32000            1.0
2 李小華 32.00       技術  45000            5.0
3 王小美 33.25       行銷  33000            2.0
4 趙小強 41.00       行銷  38000            3.5
5 陳小玲 35.00       技術  37000            7.0

標記缺失值

有時候,保留缺失值的資訊也很重要:

1
2
3
4
5
6
7
# 建立指示變數標記原始資料中的缺失值
employee_data$age_missing <- is.na(employee_data$age)
employee_data$salary_missing <- is.na(employee_data$salary)
employee_data$years_missing <- is.na(employee_data$years_employed)
employee_data$dept_missing <- is.na(employee_data$department)

print(employee_data)
    name   age department salary years_employed age_missing salary_missing
1 張小明 25.00       行銷  32000            1.0       FALSE          FALSE
2 李小華 32.00       技術  45000            5.0       FALSE          FALSE
3 王小美 33.25       行銷  33000            2.0       FALSE          FALSE
4 趙小強 41.00       行銷  38000            3.5       FALSE          FALSE
5 陳小玲 35.00       技術  37000            7.0       FALSE          FALSE
  years_missing dept_missing
1         FALSE        FALSE
2         FALSE        FALSE
3         FALSE        FALSE
4         FALSE        FALSE
5         FALSE        FALSE

有效處理缺失值是資料清理過程中的關鍵步驟,需要根據資料特性和分析目的選擇適當的策略。無論是移除、填補還是標記,都應該謹慎考慮對結果的潛在影響。

資料框的敘述統計:讓數據說話

資料收集只是第一步,而資料分析的精髓在於從大量數字中提煉出有意義的洞見。敘述統計就像是一個翻譯家,幫助我們將枯燥的數字轉化為清晰的故事。無論你是分析銷售趨勢、研究人口特徵,還是探索學生的學習模式,敘述統計都能幫助你快速理解資料的核心特性,為後續的深入分析鋪平道路。

基礎統計量計算

敘述統計的第一步是了解資料的基本特性,包括集中趨勢、離散程度和分布形狀等。R 提供了豐富的函數來計算這些統計量。這些基礎統計量是資料分析的起點,它們能幫助我們快速掌握資料的整體輪廓,發現潛在的模式和異常,並為後續的深入分析提供方向。無論是探索性分析還是報告生成,基礎統計量都扮演著不可替代的角色。它們像是資料的「生命體徵」,能夠迅速告訴我們資料的健康狀況和基本特徵。在進行任何複雜的統計分析或模型建立前,了解這些基本統計量是必不可少的步驟,它可以幫助我們避免誤解資料,並為接下來的分析奠定堅實基礎。

使用 summary() 函數

最快速獲取資料概覽的方法是使用 summary() 函數。這個多功能函數能夠智能識別不同型別的資料,並提供相應的摘要統計。對於數值變數,它會計算最小值、最大值、四分位數和平均值;對於因子變數,它會顯示各水準的頻數;對於邏輯變數,則會計算 TRUEFALSE 的數量。summary() 函數,基本語法如下:

1
summary(object, ..., digits = NULL, quantile.type = 7)

其中:

  • object:要產生摘要的對象,可以是向量、矩陣、資料框、列表或其他 R 對象
  • ...:傳遞給特定方法的其他參數
  • digits:用於數字顯示的有效位數,預設為 NULL
  • quantile.type:計算分位數時使用的算法類型,預設為 7

summary 函數是 R 語言中的通用函數(generic function),它會根據你丟進去的資料類型不同,去呼叫對應的方法,然後幫你產出合適的摘要統計結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 建立一個範例資料框
sales_data <- data.frame(
product = c("A", "B", "C", "A", "B", "C", "A", "B", "C", "A"),
region = c("北", "北", "北", "南", "南", "南", "東", "東", "東", "西"),
price = c(150, 200, 120, 155, 210, 125, 145, 195, 115, 160),
quantity = c(50, 30, 60, 45, 25, 55, 55, 35, 65, 40),
discount = c(0.1, 0, 0.15, 0.1, 0.05, 0.1, 0, 0.05, 0.2, 0.05)
)

# 取得整個資料框的敘述統計
summary_all <- summary(sales_data)
print(summary_all)

# 特定欄位的敘述統計
summary_price <- summary(sales_data$price)
print(summary_price)

# 分類資料的摘要
summary_product <- summary(factor(sales_data$product))
print(summary_product)
   product             region              price          quantity    
 Length:10          Length:10          Min.   :115.0   Min.   :25.00  
 Class :character   Class :character   1st Qu.:130.0   1st Qu.:36.25  
 Mode  :character   Mode  :character   Median :152.5   Median :47.50  
                                       Mean   :157.5   Mean   :46.00  
                                       3rd Qu.:186.2   3rd Qu.:55.00  
                                       Max.   :210.0   Max.   :65.00  
    discount    
 Min.   :0.000  
 1st Qu.:0.050  
 Median :0.075  
 Mean   :0.080  
 3rd Qu.:0.100  
 Max.   :0.200  
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  115.0   130.0   152.5   157.5   186.2   210.0 
A B C 
4 3 3 

計算常見的統計量

除了 summary(),R 還提供了許多專門計算特定統計量的函數,這些函數讓我們能夠針對特定需求,精確計算所需的統計指標。在資料分析過程中,不同的分析目的可能需要關注不同的統計量,R 提供的這些專門函數使得我們能夠靈活地選擇和計算適合當前分析目的的指標。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 平均值 (Mean)
mean_price <- mean(sales_data$price)
print(paste("平均價格:", mean_price))

# 中位數 (Median)
median_price <- median(sales_data$price)
print(paste("價格中位數:", median_price))

# 標準差 (Standard Deviation)
sd_price <- sd(sales_data$price)
print(paste("價格標準差:", sd_price))

# 變異數 (Variance)
var_price <- var(sales_data$price)
print(paste("價格變異數:", var_price))

# 極值 (Min and Max)
min_price <- min(sales_data$price)
max_price <- max(sales_data$price)
print(paste("最低價格:", min_price, "最高價格:", max_price))

# 四分位數 (Quartiles)
quantile_price <- quantile(sales_data$price)
print(paste("價格四分位數:"))
print(quantile_price)

# 特定百分位數
percentile_75 <- quantile(sales_data$price, 0.75)
print(paste("價格75百分位數:", percentile_75))

# 範圍 (Range)
range_price <- range(sales_data$price)
print(paste("價格範圍:", range_price[1], "到", range_price[2]))
[1] "平均價格: 157.5"
[1] "價格中位數: 152.5"
[1] "價格標準差: 34.0954542424646"
[1] "價格變異數: 1162.5"
[1] "最低價格: 115 最高價格: 210"
[1] "價格四分位數:"
    0%    25%    50%    75%   100% 
115.00 130.00 152.50 186.25 210.00 
[1] "價格75百分位數: 186.25"
[1] "價格範圍: 115 到 210"

使用 sapply() 同時計算多個統計量

當我們需要同時計算多個數值欄位的統計量時,使用 sapply() 函數非常方便。這個函數可以應用於一個資料框的多個欄位,並返回每個欄位的統計量,例如均值、中位數、標準差等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 找出所有數值欄位
numeric_cols <- sapply(sales_data, is.numeric)
numeric_data <- sales_data[, numeric_cols]

# 計算所有數值欄位的平均值
means <- sapply(numeric_data, mean)
print("平均值:")
print(means)

# 計算所有數值欄位的標準差
sds <- sapply(numeric_data, sd)
print("標準差:")
print(sds)

# 同時計算多個統計量
stats_summary <- sapply(numeric_data, function(x) {
c(
Mean = mean(x),
Median = median(x),
SD = sd(x),
Min = min(x),
Max = max(x)
)
})
print("綜合統計摘要:")
print(stats_summary)
[1] "平均值:"
   price quantity discount 
  157.50    46.00     0.08 
[1] "標準差:"
      price    quantity    discount 
34.09545424 13.29160136  0.06324555 
[1] "綜合統計摘要:"
           price quantity   discount
Mean   157.50000  46.0000 0.08000000
Median 152.50000  47.5000 0.07500000
SD      34.09545  13.2916 0.06324555
Min    115.00000  25.0000 0.00000000
Max    210.00000  65.0000 0.20000000

合併計算:營業額和利潤

在實際的業務分析中,通常需要將多個欄位結合起來計算更具意義的指標。例如,透過計算每筆交易的營業額、利潤和利潤率,能幫助我們更好地理解公司運營的狀況。我們在底下建立一個假設性的資料 sales_data,並計算了營業額、實際收入、成本、利潤和利潤率。最後,使用 summary() 函數來查看這些指標的敘述統計量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 計算每筆交易的營業額
sales_data$revenue <- sales_data$price * sales_data$quantity

# 計算折扣後的實際收入
sales_data$actual_revenue <- sales_data$revenue * (1 - sales_data$discount)

# 假設成本是售價的70%
sales_data$cost <- sales_data$price * 0.7 * sales_data$quantity

# 計算利潤
sales_data$profit <- sales_data$actual_revenue - sales_data$cost

# 計算利潤率
sales_data$profit_margin <- sales_data$profit / sales_data$actual_revenue

# 查看敘述統計
profit_summary <- summary(sales_data[, c("revenue", "actual_revenue", "profit", "profit_margin")])
print(profit_summary)
    revenue     actual_revenue     profit       profit_margin   
 Min.   :5250   Min.   :4988   Min.   : 747.5   Min.   :0.1250  
 1st Qu.:6506   1st Qu.:6020   1st Qu.:1328.1   1st Qu.:0.2222  
 Median :6925   Median :6154   Median :1447.5   Median :0.2427  
 Mean   :6848   Mean   :6284   Mean   :1490.9   Mean   :0.2358  
 3rd Qu.:7406   3rd Qu.:6432   3rd Qu.:1679.7   3rd Qu.:0.2632  
 Max.   :7975   Max.   :7975   Max.   :2392.5   Max.   :0.3000  

依照群組進行統計

在現實世界的資料分析中,經常需要對不同群組進行比較,這能幫助我們了解各群體之間的差異。例如,我們可以比較不同區域的銷售表現,了解不同產品的毛利率差異,或者根據不同的時間段來分析銷售趨勢。在 R 中,透過不同的群組統計方法,我們能夠高效地達成這些目的。

使用 aggregate() 函數

aggregate() 是 R 中一個非常有用的基礎函數,它允許我們根據一個或多個變數對資料進行分組,並對每個分組計算相應的統計量。這使得我們能夠從不同群組中提取有意義的數據,進行後續的分析與比較。aggregate() 函數的基本語法如下:

1
aggregate(x, by, FUN, ..., simplify = FALSE, drop = TRUE)

其中:

  • x:要匯總的資料,可以是向量、矩陣、資料框或時間序列
  • by:指定分組的列表,通常是一個或多個分類變數
  • FUN:應用於每組的函數
  • ...:傳遞給 FUN 的其他參數
  • simplify:是否簡化結果,默認為 FALSE
  • drop:是否移除只有一個元素的因子水平,默認為 TRUE

aggregate() 函數還有另一種使用公式語法的形式:

1
aggregate(formula, data, FUN, ...)

其中:

  • formula:指定響應變數和分組變數的公式,格式為 response ~ group1 + group2 + ...
  • data:包含變數的資料框
  • FUN:應用於每組的函數
  • ...:傳遞給 FUN 的其他參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 計算每個產品的平均價格
avg_price_by_product <- aggregate(price ~ product, data = sales_data, FUN = mean)
print(avg_price_by_product)

# 計算每個地區的總銷售量
total_quantity_by_region <- aggregate(quantity ~ region, data = sales_data, FUN = sum)
print(total_quantity_by_region)

# 同時計算多個統計量
sales_by_product <- aggregate(cbind(price, quantity, profit) ~ product,
data = sales_data,
FUN = function(x) c(mean = mean(x), sum = sum(x)))
print(sales_by_product)

# 依照多個變數分組
sales_by_product_region <- aggregate(cbind(revenue, profit) ~ product + region,
data = sales_data,
FUN = sum)
print(sales_by_product_region)
  product    price
1       A 152.5000
2       B 201.6667
3       C 120.0000
  region quantity
1     北      140
2     西       40
3     東      155
4     南      125
  product price.mean price.sum quantity.mean quantity.sum profit.mean
1       A   152.5000  610.0000          47.5        190.0    1721.875
2       B   201.6667  605.0000          30.0         90.0    1606.250
3       C   120.0000  360.0000          60.0        180.0    1067.500
  profit.sum
1   6887.500
2   4818.750
3   3202.500
   product region revenue  profit
1        A     北    7500 1500.00
2        B     北    6000 1800.00
3        C     北    7200 1080.00
4        A     西    6400 1600.00
5        A     東    7975 2392.50
6        B     東    6825 1706.25
7        C     東    7475  747.50
8        A     南    6975 1395.00
9        B     南    5250 1312.50
10       C     南    6875 1375.00

使用 tapply() 函數

在 R 中,除了 aggregate() 函數,tapply() 函數也是一個強大的工具,專門用來對資料進行分組統計。與 aggregate() 不同,tapply() 函數將變數分組並將指定的函數應用於每個分組,而不需要像 aggregate() 那樣明確指定變數名稱和結果類型。tapply() 函數的基本語法如下:

1
tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)

其中

  • X:需要進行計算的向量或數據列。
  • INDEX:用來分組的因素(通常是因子或分組向量)。
  • FUN:要應用於每組的函數,如 meansumsd 等。
  • simplify:是否簡化結果,默認值為 TRUE,表示將結果轉換為矩陣或向量。
1
2
3
4
5
6
7
8
9
10
11
12
13
# 計算每個產品的平均價格
avg_price_by_product <- tapply(sales_data$price, sales_data$product, mean)
print(avg_price_by_product)

# 計算每個地區的總營業額
total_revenue_by_region <- tapply(sales_data$revenue, sales_data$region, sum)
print(total_revenue_by_region)

# 計算每個產品和地區組合的平均利潤
avg_profit_by_prod_reg <- tapply(sales_data$profit,
list(Product = sales_data$product, Region = sales_data$region),
mean)
print(avg_profit_by_prod_reg)
       A        B        C 
152.5000 201.6667 120.0000 
   北    西    東    南 
20700  6400 22275 19100 
       Region
Product   北   西      東     南
      A 1500 1600 2392.50 1395.0
      B 1800   NA 1706.25 1312.5
      C 1080   NA  747.50 1375.0

雖然 aggregate()tapply() 都能進行分組統計,但它們在使用上有一些區別:

  • aggregate() 更加靈活,支持多個變數的分組並能返回資料框格式,適合用來處理複雜的數據結構。
  • tapply() 更加簡潔,尤其適用於單一向量的分組統計,並且它的輸出格式通常是向量或矩陣。

選擇哪個函數取決於具體的需求。如果需要處理更為複雜的數據並進行多變數的分組統計,aggregate() 可能更適合;而對於簡單的分組計算,tapply() 是一個非常高效且直觀的選擇。

使用 by() 函數

by() 函數是 R 中一個非常有用的工具,它允許我們對資料的子集應用更複雜的函數。與 tapply() 類似,by() 函數也支持資料分組的操作,但它的優勢在於可以對資料的每個子集應用自定義的函數,並返回更靈活的結果。by() 函數的基本語法如下:

1
by(data, INDICES, FUN, ...)
  • data:需要進行處理的資料(通常是資料框、矩陣或數組)。
  • INDICES:分組的依據,可以是因子向量或多個因子向量。
  • FUN:需要應用到每個子集的函數。
  • ...:額外的參數,傳遞給函數 FUN

by() 函數的優勢在於它能夠處理更複雜的操作,並且可以直接對資料框進行操作。

1
2
3
4
5
6
7
8
# 對每個產品計算價格的敘述統計
price_summary_by_product <- by(sales_data$price, sales_data$product, summary)
print(price_summary_by_product)

# 對每個地區計算價格和數量的相關係數
correlation_by_region <- by(sales_data[, c("price", "quantity")], sales_data$region,
function(x) cor(x$price, x$quantity))
print(correlation_by_region)
sales_data$product: A
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  145.0   148.8   152.5   152.5   156.2   160.0 
------------------------------------------------------------ 
sales_data$product: B
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  195.0   197.5   200.0   201.7   205.0   210.0 
------------------------------------------------------------ 
sales_data$product: C
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  115.0   117.5   120.0   120.0   122.5   125.0 
sales_data$region: 北
[1] -0.9989061
------------------------------------------------------------ 
sales_data$region: 西
[1] NA
------------------------------------------------------------ 
sales_data$region: 東
[1] -0.9989061
------------------------------------------------------------ 
sales_data$region: 南
[1] -0.9997597

頻率分析與列聯表

頻率分析是研究類別型資料分布的重要工具,而列聯表則用於探索兩個或多個類別變數之間的關係。這在市場分析、社會調查和科學研究中都有廣泛應用。無論是市場研究者想了解不同消費群體的購買偏好,還是醫療研究者關注不同治療方法對各症狀群體的效果,又或者是社會學家研究教育程度與職業選擇之間的關聯,頻率分析和列聯表都是不可或缺的分析工具。

單變數頻率分析

最簡單的頻率分析就是計算某個類別變數中各類別的出現次數。這種分析通常是資料探索的第一步,它可以幫助我們了解類別資料的分布情況,識別主要類別和稀有類別,並為後續的深入分析奠定基礎。

例如,在商品銷售分析中,我們可能想知道哪種產品銷售最多;在調查研究中,我們可能關心受訪者的教育程度分布;在醫療研究中,我們可能需要了解不同症狀的發生頻率。這些問題都可以通過單變數頻率分析來解答。

R 語言提供了多種工具來進行單變數頻率分析,其中最基本的是 table() 函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用 table() 計算產品的頻率分布
product_freq <- table(sales_data$product)
print(product_freq)

# 計算百分比分布
product_prop <- prop.table(product_freq) * 100
print(product_prop)

# 同時顯示次數和百分比
product_freq_table <- cbind(
Frequency = as.vector(product_freq),
Percentage = as.vector(product_prop)
)
print(product_freq_table)
A B C 
4 3 3 

 A  B  C 
40 30 30 
     Frequency Percentage
[1,]         4         40
[2,]         3         30
[3,]         3         30

透過這些分析,我們可以清楚地看到各類別的分布情況,了解哪些類別占主導地位,哪些類別較為罕見。這種了解對於資源分配、風險評估和策略規劃都有重要意義。例如,如果某產品的銷售占比特別高,則可能需要確保其庫存充足;如果某症狀的發生頻率特別低,那麼在臨床實踐中可能需要特別警惕。

列聯表分析

當我們想要探索兩個類別變數之間的關係時,列聯表(contingency table)是一個強大的工具。它將兩個變數的所有可能組合排列成一個矩陣,每個元素顯示該組合的頻數或百分比。這使我們能夠直觀地看到變數之間的關聯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 建立產品和地區的列聯表
prod_region_table <- table(sales_data$product, sales_data$region)
print(prod_region_table)

# 加上邊際總和
prod_region_table_with_margin <- addmargins(prod_region_table)
print(prod_region_table_with_margin)

# 計算行百分比(每個地區內各產品的比例)
prod_region_prop_col <- prop.table(prod_region_table, margin = 2) * 100
print(prod_region_prop_col)

# 計算列百分比(每個產品在各地區的分布)
prod_region_prop_row <- prop.table(prod_region_table, margin = 1) * 100
print(prod_region_prop_row)
    北 西 東 南
  A  1  1  1  1
  B  1  0  1  1
  C  1  0  1  1
     
      北 西 東 南 Sum
  A    1  1  1  1   4
  B    1  0  1  1   3
  C    1  0  1  1   3
  Sum  3  1  3  3  10
   
           北        西        東        南
  A  33.33333 100.00000  33.33333  33.33333
  B  33.33333   0.00000  33.33333  33.33333
  C  33.33333   0.00000  33.33333  33.33333
   
          北       西       東       南
  A 25.00000 25.00000 25.00000 25.00000
  B 33.33333  0.00000 33.33333 33.33333
  C 33.33333  0.00000 33.33333 33.33333

列聯表不僅能顯示原始的計數數據,還可以從不同角度計算不同的百分比:

  • 行百分比:在每個列(例如地區)內,各行(例如產品)的分布情況。這有助於比較不同地區的產品偏好。
  • 列百分比:在每個行(例如產品)內,各列(例如地區)的分布情況。這有助於了解不同產品的地區分布。
  • 總百分比:每個單元格在整體中的占比,有助於了解整體資料狀況。

如果需要從公式建立列聯表時,我們可以使用 xtabs() 函數。與基本的 table() 函數相比,xtabs() 提供了更大的靈活性和功能性,特別是當我們需要處理包含多個變數的複雜關係,或者希望在表格中納入數值統計時。

xtabs() 函數允許我們使用公式,通常以 ~ 符號表示,左側可以是要計算的值(可選),右側是分組變數,變數之間用 + 連接。

1
2
3
4
5
6
7
8
9
10
11
12
# 基本列聯表
sales_xtab <- xtabs(~ product + region, data = sales_data)
print(sales_xtab)

# 包含數值的列聯表(如總價量)
quantity_xtab <- xtabs(quantity ~ product + region, data = sales_data)
print(quantity_xtab)

# 計算每個產品/地區組合的平均價格
price_xtab <- xtabs(price ~ product + region, data = sales_data) /
xtabs(~ product + region, data = sales_data)
print(price_xtab)
       region
product 北 西 東 南
      A  1  1  1  1
      B  1  0  1  1
      C  1  0  1  1
       region
product 北 西 東 南
      A 50 40 55 45
      B 30  0 35 25
      C 60  0 65 55
       region
product  北  西  東  南
      A 150 160 145 155
      B 200     195 210
      C 120     115 125

在這些例子中,我們可以看到 xtabs() 的多種用法:

  • 基本頻數列聯表:第一個例子 xtabs(~ product + region, data = sales_data)table(sales_data$product, sales_data$region) 的結果相同,計算每個產品-地區組合的頻數。這是最基本的用法,適合快速了解兩個變數之間的分布關係。

  • 數值加總列聯表:第二個例子 xtabs(quantity ~ product + region, data = sales_data) 不僅統計頻數,還計算每個組合的數量總和。這種用法特別適合於銷售分析,例如計算每個產品在各地區的總銷售量。

  • 計算組合平均值:第三個例子展示了一個巧妙的技巧,通過兩個 xtabs() 函數的比值,計算每個產品-地區組合的平均價格。

多變數列聯表

隨著分析的深入,我們常常需要探索更複雜的關係,即三個或更多變數之間的相互作用。多變數列聯表提供了一種強大的方法,讓我們能夠同時觀察多個類別變數之間的關係,揭示可能被簡單雙變數分析忽略的複雜模式和交互效應。

在實際應用中,多變數列聯表特別有用於發現條件性關係。例如,某些產品與地區的關係可能會因季節而異;或者藥物效果與性別的關係可能會因年齡組別而不同。這種層次化的分析能夠提供更細緻、更貼近現實的洞察。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 建立一個包含產品、地區和折扣類別的列聯表
# 先將折扣轉換為類別
sales_data$discount_level <- cut(sales_data$discount,
breaks = c(-Inf, 0, 0.1, Inf),
labels = c("無折扣", "小折扣", "大折扣"))

# 三向列聯表
three_way_table <- xtabs(~ product + region + discount_level, data = sales_data)
print(three_way_table)

# 檢視特定層面
print(three_way_table[, , "大折扣"])

# 合併某一維度
collapsed_table <- apply(three_way_table, c(1, 2), sum)
print(collapsed_table)
, , discount_level = 無折扣

       region
product 北 西 東 南
      A  0  0  1  0
      B  1  0  0  0
      C  0  0  0  0

, , discount_level = 小折扣

       region
product 北 西 東 南
      A  1  1  0  1
      B  0  0  1  1
      C  0  0  0  1

, , discount_level = 大折扣

       region
product 北 西 東 南
      A  0  0  0  0
      B  0  0  0  0
      C  1  0  1  0

       region
product 北 西 東 南
      A  0  0  0  0
      B  0  0  0  0
      C  1  0  1  0
       region
product 北 西 東 南
      A  1  1  1  1
      B  1  0  1  1
      C  1  0  1  1

在這個例子中,我們首先將連續的折扣變數轉換為類別變數,分為「無折扣」、「小折扣」和「大折扣」三個水平。然後,使用 xtabs() 函數創建一個三向列聯表,同時考慮產品、地區和折扣水平三個變數。

三向列聯表的輸出結果以多個二維表格的形式呈現,每個表格對應一個第三變數(本例中為折扣水平)的層次。這種表示方法讓我們能夠清楚地看到在每個折扣水平下,產品和地區之間的關係。

從輸出中,我們可以看到「無折扣」層面下的產品-地區分布、「小折扣」層面下的分布,以及「大折扣」層面下的分布。例如,我們可以發現C產品在北區和東區有大折扣的銷售紀錄,而其他產品在大折扣層面下沒有紀錄。

此外,我們還可以使用索引操作來檢視特定層面的資料,例如 three_way_table[, , "大折扣"] 讓我們專注於大折扣情境下的產品-地區關係。

最後,如果我們想要忽略某個維度進行摺疊分析,可以使用 apply() 函數。在例子中,apply(three_way_table, c(1, 2), sum) 對第三維度(折扣水平)進行摺疊,返回產品與地區的總體關係。這相當於無論折扣水平如何,只考慮產品和地區的總體分布。

小結

敘述統計是將原始資料轉換為有用洞見的關鍵步驟。掌握這些工具後,你將能夠在資料的海洋中找到隱藏的模式和關係,讓數據真正開口「說話」。通過基礎統計量計算、群組統計分析和頻率列聯表,你可以回答業務問題,支持決策制定,並為後續的進階分析鋪平道路。