跳到內容

Apache Arrow 是一個軟體開發平台,用於建置處理和傳輸大型資料集的高效能應用程式。它旨在提升資料分析方法的效能,並提高在不同系統或程式語言之間移動資料的效率。

arrow 套件提供在 R 中使用 Apache Arrow 的標準方法。它為 Arrow C++ 函式庫 提供了底層介面,以及一些高階工具,讓 R 使用者能以更自然的方式使用它。本文概述了各部分如何組合在一起,並描述了類別和方法在 R 中遵循的慣例。

套件慣例

arrow R 套件建立在 Arrow C++ 函式庫之上,而 C++ 是一種物件導向語言。因此,Arrow C++ 函式庫的核心邏輯封裝在類別和方法中。在 arrow R 套件中,這些實作為 R6 類別,它們都採用 "TitleCase" 命名慣例。以下是一些範例:

  • 二維表格資料結構,例如 TableRecordBatchDataset
  • 一維向量式資料結構,例如 ArrayChunkedArray
  • 用於讀取、寫入和串流資料的類別,例如 ParquetFileReaderCsvTableReader

這個底層介面讓您能以非常彈性的方式與 Arrow C++ 函式庫互動,但在許多常見情況下,您可能根本不需要使用它,因為 arrow 也提供了一個高階介面,使用遵循 "snake_case" 命名慣例的函數。以下是一些範例:

  • arrow_table() 讓您無需直接使用 Table 物件即可建立 Arrow 表格
  • read_parquet() 讓您無需直接使用 ParquetFileReader 物件即可開啟 Parquet 檔案

本文中使用的所有範例都依賴於這個高階介面。

對於有興趣了解套件結構的開發人員,請參閱開發人員指南

Arrow 中的表格資料

Apache Arrow 的一個關鍵組件是其記憶體內欄式格式,這是一種標準化的、與語言無關的規範,用於在記憶體中表示結構化的表格狀資料集。在 arrow R 套件中,Table 類別用於儲存這些物件。表格大致上類似於資料框,並且具有相似的行為。arrow_table() 函數讓您能以與使用 data.frame() 建立新資料框非常相似的方式產生新的 Arrow 表格

library(arrow, warn.conflicts = FALSE)

dat <- arrow_table(x = 1:3, y = c("a", "b", "c"))
dat
## Table
## 3 rows x 2 columns
## $x <int32>
## $y <string>

您可以使用 [ 來指定 Arrow 表格的子集,就像您對資料框所做的那樣

dat[1:2, 1:2]
## Table
## 2 rows x 2 columns
## $x <int32>
## $y <string>

同樣地,可以使用 $ 運算子來提取具名欄位

dat$y
## ChunkedArray
## <string>
## [
##   [
##     "a",
##     "b",
##     "c"
##   ]
## ]

請注意輸出:Arrow 表格中的個別欄位表示為 Chunked Arrays,這是在 Arrow 中的一維資料結構,大致上類似於 R 中的向量。

表格是使用 Arrow 在記憶體中表示矩形資料的主要方式,但它們並非 Arrow C++ 函式庫使用的唯一矩形資料結構:還有用於儲存在磁碟而非記憶體中的資料的 Datasets,以及作為基本構建模組但通常不用于資料分析的 Record Batches。

若要深入了解 arrow 中不同的資料物件類別,請參閱關於資料物件的文章。

將表格轉換為資料框

表格是一種資料結構,用於表示 Arrow C++ 函式庫配置的記憶體內的矩形資料,但可以使用 as.data.frame() 強制轉換為原生 R 資料框(或 tibbles)

##   x y
## 1 1 a
## 2 2 b
## 3 3 c

當發生這種強制轉換時,原始 Arrow 表格中的每個欄位都必須轉換為原生 R 資料物件。例如,在 dat 表格中,dat$x 儲存為從 C++ 繼承的 Arrow 資料類型 int32,當呼叫 as.data.frame() 時,它會變成 R 整數類型。

可以對此轉換過程進行細緻的控制。若要深入了解不同的類型以及它們的轉換方式,請參閱資料類型文章。

讀取和寫入資料

使用 arrow 的主要方式之一是以幾種常見格式讀取和寫入資料檔案。arrow 套件提供了極快的 CSV 讀取和寫入功能,此外還支援其他套件不廣泛支援的資料格式,例如 Parquet 和 Arrow(也稱為 Feather)。此外,arrow 套件支援多檔案資料集,其中單個矩形資料集儲存在多個檔案中。

個別檔案

當目標是將單個資料檔案讀取到記憶體中時,您可以使用以下幾個函數

在 JSON 以外的每種情況下,都有一個對應的 write_*() 函數,可讓您以適當的格式寫入資料檔案。

預設情況下,read_*() 函數將傳回資料框或 tibble,但您也可以使用它們將資料讀取到 Arrow 表格中。若要執行此操作,您需要將 as_data_frame 引數設定為 FALSE

在以下範例中,我們採用 dplyr 套件提供的 starwars 資料,並使用 write_parquet() 將其寫入 Parquet 檔案

library(dplyr, warn.conflicts = FALSE)

file_path <- tempfile(fileext = ".parquet")
write_parquet(starwars, file_path)

然後,我們可以使用 read_parquet() 從此檔案載入資料。如下所示,預設行為是傳回資料框 (sw_frame),但當我們設定 as_data_frame = FALSE 時,資料會讀取為 Arrow 表格 (sw_table)

sw_frame <- read_parquet(file_path)
sw_table <- read_parquet(file_path, as_data_frame = FALSE)
sw_table
## Table
## 87 rows x 14 columns
## $name <string>
## $height <int32>
## $mass <double>
## $hair_color <string>
## $skin_color <string>
## $eye_color <string>
## $birth_year <double>
## $sex <string>
## $gender <string>
## $homeworld <string>
## $species <string>
## $films: list<element <string>>
## $vehicles: list<element <string>>
## $starships: list<element <string>>

若要深入了解讀取和寫入個別資料檔案,請參閱讀取/寫入文章

多檔案資料集

當表格資料集變得很大時,通常好的做法是將資料分割成有意義的子集,並將每個子集儲存在單獨的檔案中。其中,這表示如果只有資料的一個子集與分析相關,則只需要讀取一個(較小的)檔案。arrow 套件提供了 Dataset 介面,這是一種方便的方式來讀取、寫入和分析大於記憶體的單個資料檔案和多檔案資料集。

為了說明這些概念,我們將建立一個包含 100000 列的無意義資料集,該資料集可以分割成 10 個子集

set.seed(1234)
nrows <- 100000
random_data <- data.frame(
  x = rnorm(nrows),
  y = rnorm(nrows),
  subset = sample(10, nrows, replace = TRUE)
)

我們可能想要做的是分割此資料,然後將其寫入 10 個單獨的 Parquet 檔案,每個檔案對應於 subset 欄位的每個值。為此,我們先指定要將資料檔案寫入的資料夾路徑

dataset_path <- file.path(tempdir(), "random_data")

然後,我們可以使用 dplyr 中的 group_by() 函數來指定將使用 subset 欄位分割資料,然後將分組的資料傳遞給 write_dataset()

random_data %>%
  group_by(subset) %>%
  write_dataset(dataset_path)

這會建立一組 10 個檔案,每個子集一個檔案。這些檔案根據如下所示的 "hive partitioning" 格式命名

list.files(dataset_path, recursive = TRUE)
##  [1] "subset=1/part-0.parquet"  "subset=10/part-0.parquet"
##  [3] "subset=2/part-0.parquet"  "subset=3/part-0.parquet" 
##  [5] "subset=4/part-0.parquet"  "subset=5/part-0.parquet" 
##  [7] "subset=6/part-0.parquet"  "subset=7/part-0.parquet" 
##  [9] "subset=8/part-0.parquet"  "subset=9/part-0.parquet"

可以使用 read_parquet() 單獨開啟這些 Parquet 檔案中的每一個,但通常更方便(尤其是對於非常大的資料集)的是掃描資料夾並「連接」到資料集,而無需將其載入記憶體。我們可以使用 open_dataset() 來執行此操作

dset <- open_dataset(dataset_path)
dset
## FileSystemDataset with 10 Parquet files
## 3 columns
## x: double
## y: double
## subset: int32

這個 dset 物件不會將資料儲存在記憶體中,僅儲存一些 metadata。但是,如下一節所述,可以分析 dset 所引用的資料,就好像它已被載入一樣。

若要深入了解 Arrow Datasets,請參閱dataset 文章

使用 dplyr 分析 Arrow 資料

Arrow 表格和 Datasets 可以使用 dplyr 語法進行分析。這是可能的,因為 arrow R 套件提供了一個後端,可將 dplyr 動詞轉換為 Arrow C++ 函式庫可以理解的命令,並且類似地轉換 dplyr 動詞呼叫中出現的 R 表達式。例如,雖然 dset Dataset 不是資料框(並且不會將資料值儲存在記憶體中),但您仍然可以將其傳遞給 dplyr 管道,如下所示

dset %>%
  group_by(subset) %>%
  summarize(mean_x = mean(x), min_y = min(y)) %>%
  filter(mean_x > 0) %>%
  arrange(subset) %>%
  collect()
## # A tibble: 6 x 3
##   subset  mean_x min_y
##    <int>   <dbl> <dbl>
## 1      2 0.00486 -4.00
## 2      3 0.00440 -3.86
## 3      4 0.0125  -3.65
## 4      6 0.0234  -3.88
## 5      7 0.00477 -4.65
## 6      9 0.00557 -3.50

請注意,我們在管道的末端呼叫 collect()。在呼叫 collect()(或相關的 compute() 函數)之前,不會執行實際的計算。"惰性評估" 使 Arrow C++ 計算引擎能夠最佳化計算的執行方式。

若要深入了解分析 Arrow 資料,請參閱資料整理文章dplyr 查詢中可用的函數列表頁面也可能很有用。

連線到雲端儲存空間

arrow R 套件的另一個用途是讀取、寫入和分析遠端儲存在雲端服務上的資料集。該套件目前同時支援 Amazon Simple Storage Service (S3) 和 Google Cloud Storage (GCS)。以下範例說明如何使用 s3_bucket() 來引用 S3 bucket,以及使用 open_dataset() 連線到儲存在那裡的資料集

bucket <- s3_bucket("voltrondata-labs-datasets/nyc-taxi")
nyc_taxi <- open_dataset(bucket)

若要深入了解 arrow 中對雲端服務的支援,請參閱雲端儲存空間文章。

R 和 Python 之間的高效資料交換

reticulate 套件提供了一個介面,可讓您從 R 呼叫 Python 程式碼。arrow 套件旨在與 reticulate 互通。如果 Python 環境安裝了 pyarrow 函式庫(Python 中與 arrow 套件等效的函式庫),您可以使用 reticulate 中的 r_to_py() 函數將 Arrow 表格從 R 傳遞到 Python,如下所示

library(reticulate)

sw_table_python <- r_to_py(sw_table)

sw_table_python 物件現在儲存為 pyarrow 表格:Python 中與 Table 類別等效的類別。您可以在列印物件時看到這一點

sw_table_python
## pyarrow.Table
## name: string
## height: int32
## mass: double
## hair_color: string
## skin_color: string
## eye_color: string
## birth_year: double
## sex: string
## gender: string
## homeworld: string
## species: string
## films: list<element: string>
##   child 0, element: string
## vehicles: list<element: string>
##   child 0, element: string
## starships: list<element: string>
##   child 0, element: string
## ----
## name: [["Luke Skywalker","C-3PO","R2-D2","Darth Vader","Leia Organa",...,"Finn","Rey","Poe Dameron","BB8","Captain Phasma"]]
## height: [[172,167,96,202,150,...,null,null,null,null,null]]
## mass: [[77,75,32,136,49,...,null,null,null,null,null]]
## hair_color: [["blond",null,null,"none","brown",...,"black","brown","brown","none","none"]]
## skin_color: [["fair","gold","white, blue","white","light",...,"dark","light","light","none","none"]]
## eye_color: [["blue","yellow","red","yellow","brown",...,"dark","hazel","brown","black","unknown"]]
## birth_year: [[19,112,33,41.9,19,...,null,null,null,null,null]]
## sex: [["male","none","none","male","female",...,"male","female","male","none","female"]]
## gender: [["masculine","masculine","masculine","masculine","feminine",...,"masculine","feminine","masculine","masculine","feminine"]]
## homeworld: [["Tatooine","Tatooine","Naboo","Tatooine","Alderaan",...,null,null,null,null,null]]
## ...

重要的是要認識到,當發生這種傳輸時,只會複製 C++ 指標(即,指向 Arrow C++ 函式庫儲存的底層資料物件的 metadata)。資料值本身在記憶體中的相同位置。這樣做的結果是,將 Arrow 表格從 R 傳遞到 Python 比將 R 中的資料框複製到 Python 中的 Pandas DataFrame 快得多。

若要深入了解在 R 和 Python 之間傳遞 Arrow 資料,請參閱關於python 整合的文章。

存取 Arrow 訊息、緩衝區和串流

arrow 套件還提供了許多與 C++ 函式庫的底層綁定,使您能夠存取和操作 Arrow 物件。您可以使用這些來建置與其他使用 Arrow 的應用程式和服務的連接器。一個範例是 Spark:sparklyr 套件支援使用 Arrow 在 Spark 之間移動資料,從而產生顯著的效能提升

貢獻 arrow

Apache Arrow 是一個廣泛的專案,跨越多種語言,而 arrow R 套件只是這個大型專案的一部分。因此,對於想要貢獻套件的開發人員來說,有一些特殊的考量。為了讓這個過程更輕鬆,arrow 文件中有幾篇文章討論了與 arrow 開發人員相關的主題,但使用者不太可能需要這些主題。

如需開發過程的概述以及開發人員的相關文章列表,請參閱開發人員指南