數聚點

Share this post

如何有效率地使用 R 語言處理表格式資料

datainpoint.substack.com

如何有效率地使用 R 語言處理表格式資料

2022-11-24 台北大學短講

Yao-Jen Kuo
Dec 5, 2022
Share
Share this post

如何有效率地使用 R 語言處理表格式資料

datainpoint.substack.com
來源:giphy.com

緣起

感謝台北大學企業管理學系的游擱嘉助理教授讓我去他的金融數據分析課程給一個短講,修課學生的主修是企業管理學系,在金融數據的處理和分析上是以 R 作為程式語言,顧及一學期的課程長度,所以實踐了 Tidyverse first 的教學理念。Tidyverse first 的教學理念主要的發起點還是在於表格式資料(e.g. Data.Frame、Tibble),優點當然是實戰程度高、應用性強、學習歷程短;缺點就是在不具備 R 語言基本資料結構宏觀的理解下,無法善加利用函數輸出結果。因此這個短講我設定的主題是「如何有效率地使用 R 語言處理表格式資料 Manipulating Tabular Data with R Efficiently」,旨在透過 90 分鐘的時間,讓聽眾快速建立對於 R 語言表格式資料結構的概覽。

什麼是表格式資料

表格式資料是具備列(Rows)與欄(Columns)的資料組織型態,每一列所具備的欄數都相同、每一欄所具備的列數也都相同;每一列的欄順序都相同、每一欄的列順序也都相同,這樣的一種對稱外型,讓我們能夠以 (m, n) 來描述外型為 m 列 n 欄的表格式資料。表格式資料中的每一欄各自是同質資料,但不同欄彼此允許異質資料;列資料通常具有某個特定的順序,但順序並不是必備的表格式資料特性。

來源:https://commons.wikimedia.org/wiki/File:Relational_database_terms.png

R 語言常見的表格式資料結構

以科學計算、統計分析為主體應用的程式語言,例如 R、SAS、Stata、Matlab 或 Python Pandas,都會具備處理表格式的資料結構,R 內生的 data.frame、Python Pandas 的 DataFrame 或 SAS 的 SAS Data Set。R 語言的表格式資料結構非常多元,常見的有:

  1. matrix

  2. data.frame

  3. tibble

  4. data.table

  5. xts

其中 matrix 與 data.frame 是 R 語言的內建資料結構,tibble、data.table 與 xts 則是第三方套件 tibble、data.table 與 xts 分別提供的資料結構,值得注意的是這些資料結構的類別命名與第三方套件的命名是相同的,要避免混淆。

matrix 是二維的數值向量,可以透過 matrix() 函數建立、也可以更新一維數值向量的維度來建立。

A <- matrix(1:6, nrow = 2)
B <- 1:6
dim(B) <- c(2, 3)
A
B

使用函數檢視 matrix 的類別,matrix 屬於 array 的子類別。

class(A)
is.matrix(A)
is.array(A)

使用函數檢視 matrix 的外型。

length(A)
dim(A)
nrow(A)
ncol(A)

data.frame 是一個由相同長度向量所組合而成的 list 資料結構,是 R 語言最常用來儲存資料的格式。使用函數檢視 data.frame 的類別,data.frame 屬於 list 的子類別。

#install.packages("dplyr")
library("dplyr")

class(dplyr::starwars)
is.data.frame(dplyr::starwars)
is.list(dplyr::starwars)

使用函數檢視 data.frame 的外型。

dim(dplyr::starwars)
nrow(dplyr::starwars)
ncol(dplyr::starwars)

使用函數預覽 data.frame 的內容。

head(dplyr::starwars)
tail(dplyr::starwars)
#View(dplyr::starwars) # Works in RStudio only

使用函數取得 data.frame 的列命名、欄命名。

colnames(dplyr::starwars)
dplyr::starwars |>
  rownames() |>
  as.numeric()

tibble 是「簡約版」的 data.frame,提供了更簡潔明瞭的顯示外觀,並且是 tidyverse 套件組的通用資料結構,例如使用 tidyverse 套件組之中 readr 套件的 read_csv() 函數載入 CSV 檔案、使用 tidyverse 套件組之中 dplyr、tidyr 套件的函數處理資料,都會得到 tibble 的輸出。使用函數檢視 tibble 的類別,tibble 屬於 data.frame 與 list 的子類別。

class(dplyr::starwars)
tibble::is_tibble(dplyr::starwars)
is.data.frame(dplyr::starwars)
is.list(dplyr::starwars)

至於處理 tibble 跟處理 data.frame 完全沒有分別,可以百分百移植所有處理資料框的技巧到 tibble 之上。

data.table 是「大數據版」的 data.frame,提供了更快速的聚合運算、集合運算以及分組運算,例如使用 data.table 套件的 fread() 函數載入 CSV 檔案就會得到 data.table 的輸出。使用函數檢視 data.table 的類別,data.table 屬於 data.frame 與 list 的子類別。

#install.packages("data.table")
library("data.table")

starwars_dt <- data.table::as.data.table(dplyr::starwars)
class(starwars_dt)
data.table::is.data.table(starwars_dt)
is.data.frame(starwars_dt)
is.list(starwars_dt)

基本檢視、預覽 data.table 的函數與 data.frame 完全沒有分別,不過處理 data.table 的語法以及技巧卻是其自成一格的 DT[i, j, by],無法完全移植過往處理資料框的技巧。

xts 是 eXtensible Time Series 的縮寫,專門用來儲存與處理時間序列的表格式資料,例如使用 quantmod 套件的 getSymbols() 函數載入個股資訊就會得到 xts 的輸出。使用函數檢視 xts 的類別,xts 屬於 matrix 的子類別。

#install.packages("quantmod")
library("quantmod")

start_date <- "2022-01-01"
AAPL <- getSymbols(Symbols = "AAPL", from = start_date, auto.assign = FALSE)
class(AAPL)
xts::is.xts(AAPL)
is.matrix(AAPL)

解構 xts 可以發現它是由一個日期向量(Date)與一個 matrix 組合而成。

aapl_index <- zoo::index(AAPL)
aapl_coredata <- zoo::coredata(AAPL)
class(aapl_index)
class(aapl_coredata)

有效率處理表格式資料的訣竅

訣竅一:掌握不同資料結構之間的轉換,像是從表格式資料擷取向量,透過下列函數轉換為特定表格式資料結構。

  • as.matrix()

  • as.data.frame()

  • tibble::as.tibble()

  • data.table::as.data.table()

  • xts::as.xts()

訣竅二:同質資料的處理效率高於異質資料,盡可能將所需資料提取為向量或 matrix 再處理,而非以資料框格式處理,例如同樣想要使用 height 與 mass 這兩個欄位,提取為向量或 matrix 的作法比較有效率。

# Good
class(dplyr::starwars[["height"]])
dplyr::starwars[, c("height", "mass")] |>
  as.matrix() |>
  class()
# Not so good
class(dplyr::starwars[, "height"])
class(dplyr::starwars[, c("height", "mass")])

訣竅三:向量化的處理效率高於函數型程式設計,例如同樣想要使用 height 與 mass 這兩個欄位運算 bmi,使用向量運算的作法比較有效率(且易懂。)

# Good
bmi <- dplyr::starwars[["mass"]] / (dplyr::starwars[["height"]]*0.01)^2
# Not so good
bmi <- mapply(function(w, h) {w / (h*0.01)^2}, dplyr::starwars[["mass"]], dplyr::starwars[["height"]])

訣竅四:函數型程式設計的處理效率高於迴圈,例如同樣想要使用 height 與 mass 這兩個欄位運算 bmi,使用函數型程式設計的作法比較有效率。

# Good
bmi <- mapply(function(w, h) {w / (h*0.01)^2}, dplyr::starwars[["mass"]], dplyr::starwars[["height"]])
# Not so good
bmi <- vector("numeric", length = nrow(dplyr::starwars))
for (rowi in 1:nrow(dplyr::starwars)) {
  w <- dplyr::starwars[rowi, "mass"][["mass"]]
  h <- dplyr::starwars[rowi, "height"][["height"]]
  bmi[rowi] <- w/(h*0.01)^2
}

訣竅五:使用迴圈處理時先行定義好輸出的長度與類別效率高於未定義輸出,例如同樣想要使用迴圈輸入 height 與 mass 這兩個欄位運算 bmi,先行定義好輸出的長度與類別的作法比較有效率。

# Good
bmi <- vector("numeric", length = nrow(dplyr::starwars))
for (rowi in 1:nrow(dplyr::starwars)) {
  w <- dplyr::starwars[rowi, "mass"][["mass"]]
  h <- dplyr::starwars[rowi, "height"][["height"]]
  bmi[rowi] <- w/(h*0.01)^2
}
# Not so good
bmi <- vector()
for (rowi in 1:nrow(dplyr::starwars)) {
  w <- dplyr::starwars[rowi, "mass"][["mass"]]
  h <- dplyr::starwars[rowi, "height"][["height"]]
  bmi <- c(bmi, w/(h*0.01)^2)
}

訣竅六:處理異質資料時 data.table 快於 data.frame,例如載入龐大的來源資料應優先考慮 data.table::fread() 函數。

file_url <- "https://raw.githubusercontent.com/Rdatatable/data.table/master/vignettes/flights14.csv"
# Good
flights_dt <- data.table::fread(file_url)
# Not so good
flights_df <- utils::read.csv(file_url)

非常感謝台北大學企業管理學系的游擱嘉助理教授邀約,讓我有機會能夠以另一個角度整理 R 語言的相關知識,也希望來聽短講的同學、我的電子報讀者有所收穫。

對於這篇文章有什麼想法呢?喜歡😻、留言🙋‍♂️或者分享🙌

Leave a comment

約維安計畫學員專區

約維安計畫學員可以點選 nbgitpuller 連結取得本篇文章完整的內容,包含範例資料、程式碼、執行結果以及 Jupyter Notebook。在這個環境中,我們可以新增 R 語言為核心的 Jupyter Notebook,執行本篇文章的程式碼。

nbgitpuller 連結

Share
Share this post

如何有效率地使用 R 語言處理表格式資料

datainpoint.substack.com
Comments
Top
New
Community

No posts

Ready for more?

© 2023 DATAINPOINT, INC.
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing