arrow 套件提供功能,讓使用者可以使用熟悉的 dplyr 語法來操作表格化的 Arrow 資料 (Table
和 Dataset
物件)。要啟用此功能,請確保 arrow 和 dplyr 套件都已載入。在本文中,我們將使用 dplyr 中包含的 starwars
資料集,將其轉換為 Arrow Table,然後分析此資料。請注意,儘管這些範例都使用記憶體內的 Table
物件,但相同的功能也適用於磁碟上的 Dataset
物件,只有行為上的細微差異 (稍後在文章中會說明)。
首先,讓我們載入套件並建立資料
library(dplyr, warn.conflicts = FALSE)
library(arrow, warn.conflicts = FALSE)
sw <- arrow_table(starwars, as_data_frame = FALSE)
單表格 dplyr 動詞
arrow 套件提供對 dplyr 單表格動詞的支援,讓使用者能夠以熟悉的方式建構資料分析管線。以下範例展示了 filter()
、rename()
、mutate()
、arrange()
和 select()
的使用
result <- sw %>%
filter(homeworld == "Tatooine") %>%
rename(height_cm = height, mass_kg = mass) %>%
mutate(height_in = height_cm / 2.54, mass_lbs = mass_kg * 2.2046) %>%
arrange(desc(birth_year)) %>%
select(name, height_in, mass_lbs)
重要的是要注意,arrow 使用延遲求值來延遲計算,直到明確請求結果。這透過使 Arrow C++ 函式庫能夠在一個操作中執行多個計算來加速處理。作為此設計選擇的結果,我們尚未對 sw
資料執行計算。result
變數是一個類別為 arrow_dplyr_query
的物件,代表所有要執行的計算
result
## Table (query)
## name: string
## height_in: double (divide(cast(height, {to_type=double, allow_int_overflow=false, allow_time_truncate=false, allow_time_overflow=false, allow_decimal_truncate=false, allow_float_truncate=false, allow_invalid_utf8=false}), cast(2.54, {to_type=double, allow_int_overflow=false, allow_time_truncate=false, allow_time_overflow=false, allow_decimal_truncate=false, allow_float_truncate=false, allow_invalid_utf8=false})))
## mass_lbs: double (multiply_checked(mass, 2.2046))
##
## * Filter: (homeworld == "Tatooine")
## * Sorted by birth_year [desc]
## See $.data for the source Arrow object
要執行這些計算並實現結果,我們呼叫 compute()
或 collect()
。兩者之間的差異決定了將返回哪種類型的物件。呼叫 compute()
返回一個 Arrow Table,適用於傳遞給其他 arrow 或 dplyr 函數
compute(result)
## Table
## 10 rows x 3 columns
## $name <string>
## $height_in <double>
## $mass_lbs <double>
相反地,collect()
返回一個 R data frame,適用於檢視或傳遞給其他 R 函數進行分析或視覺化
collect(result)
## # A tibble: 10 x 3
## name height_in mass_lbs
## <chr> <dbl> <dbl>
## 1 C-3PO 65.7 165.
## 2 Cliegg Lars 72.0 NA
## 3 Shmi Skywalker 64.2 NA
## 4 Owen Lars 70.1 265.
## 5 Beru Whitesun Lars 65.0 165.
## 6 Darth Vader 79.5 300.
## 7 Anakin Skywalker 74.0 185.
## 8 Biggs Darklighter 72.0 185.
## 9 Luke Skywalker 67.7 170.
## 10 R5-D4 38.2 70.5
arrow 套件廣泛支援單表格 dplyr 動詞,包括那些計算彙總的動詞。例如,它支援 group_by()
和 summarize()
,以及常用的便利函數,例如 count()
## # A tibble: 38 x 2
## species mean_height
## <chr> <dbl>
## 1 Human 178
## 2 Droid 131.
## 3 Wookiee 231
## 4 Rodian 173
## 5 Hutt 175
## 6 NA 175
## 7 Yoda's species 66
## 8 Trandoshan 190
## 9 Mon Calamari 180
## 10 Ewok 88
## # i 28 more rows
## # A tibble: 3 x 2
## gender n
## <chr> <int>
## 1 masculine 66
## 2 feminine 17
## 3 NA 4
但是請注意,目前尚不支援視窗函數,例如 ntile()
。
雙表格 dplyr 動詞
等值聯結 (例如 left_join()
、inner_join()
) 支援用於聯結多個表格。如下所示
jedi <- data.frame(
name = c("C-3PO", "Luke Skywalker", "Obi-Wan Kenobi"),
jedi = c(FALSE, TRUE, TRUE)
)
sw %>%
select(1:3) %>%
right_join(jedi) %>%
collect()
## # A tibble: 3 x 4
## name height mass jedi
## <chr> <int> <dbl> <lgl>
## 1 Luke Skywalker 172 77 TRUE
## 2 C-3PO 167 75 FALSE
## 3 Obi-Wan Kenobi 182 77 TRUE
dplyr 動詞內的表達式
在 dplyr 動詞內部,Arrow 提供了對許多函數和運算符的支援,常見的函數會映射到它們在 base R 和 tidyverse 中的等效項:您可以在函數文件中找到 dplyr 查詢中支援的函數列表。如果您希望看到實作其他函數,請按照「取得協助」指南中的說明提交問題。
註冊自訂綁定
arrow 套件讓使用者可以在某些情況下使用 register_scalar_function()
為自訂函數提供綁定。為了正確運作,待註冊的函數必須將 context
作為其第一個參數,這是查詢引擎的要求。例如,假設我們想要實作一個將字串轉換為 snake case 的函數 (janitor::make_clean_names()
的大幅簡化版本)。該函數可以寫成如下形式
to_snake_name <- function(context, string) {
replace <- c(`'` = "", `"` = "", `-` = "", `\\.` = "_", ` ` = "_")
string %>%
stringr::str_replace_all(replace) %>%
stringr::str_to_lower() %>%
stringi::stri_trans_general(id = "Latin-ASCII")
}
要在 arrow/dplyr 管線中呼叫此函數,需要先註冊它
register_scalar_function(
name = "to_snake_name",
fun = to_snake_name,
in_type = utf8(),
out_type = utf8(),
auto_convert = TRUE
)
在此表達式中,name
參數指定了在 arrow/dplyr 管線的上下文中將識別它的名稱,而 fun
則是函數本身。in_type
和 out_type
參數用於指定輸入和輸出的預期資料類型,而 auto_convert
則指定 arrow 是否應自動將任何 R 輸入轉換為其 Arrow 等效項。
註冊後,以下程式碼可以運作
## # A tibble: 87 x 2
## name snake_name
## <chr> <chr>
## 1 Luke Skywalker luke_skywalker
## 2 C-3PO c3po
## 3 R2-D2 r2d2
## 4 Darth Vader darth_vader
## 5 Leia Organa leia_organa
## 6 Owen Lars owen_lars
## 7 Beru Whitesun Lars beru_whitesun_lars
## 8 R5-D4 r5d4
## 9 Biggs Darklighter biggs_darklighter
## 10 Obi-Wan Kenobi obiwan_kenobi
## # i 77 more rows
若要瞭解更多資訊,請參閱 help("register_scalar_function", package = "arrow")
。
處理不支援的表達式
對於 Table 物件上的 dplyr 查詢 (Table 物件保存在記憶體中,通常可以表示為 data frame),如果 arrow 套件在 dplyr 動詞中偵測到未實作的函數,它會自動呼叫 collect()
以在處理該 dplyr 動詞之前將資料作為 R data frame 返回。例如,lm()
和 residuals()
都未實作,因此如果我們編寫程式碼來計算線性迴歸模型的殘差,則會發生此自動收集。
sw %>%
filter(!is.na(height), !is.na(mass)) %>%
transmute(name, height, mass, res = residuals(lm(mass ~ height)))
## Warning: In residuals(lm(mass ~ height)):
## i Expression not supported in Arrow
## > Pulling data into R
## # A tibble: 59 x 4
## name height mass res
## <chr> <int> <dbl> <dbl>
## 1 Luke Skywalker 172 77 -18.8
## 2 C-3PO 167 75 -17.7
## 3 R2-D2 96 32 -16.4
## 4 Darth Vader 202 136 21.4
## 5 Leia Organa 150 49 -33.1
## 6 Owen Lars 178 120 20.4
## 7 Beru Whitesun Lars 165 75 -16.5
## 8 R5-D4 97 32 -17.0
## 9 Biggs Darklighter 183 84 -18.7
## 10 Obi-Wan Kenobi 182 77 -25.1
## # i 49 more rows
對於 Dataset 物件上的查詢 (Dataset 物件可能大於記憶體),arrow 更加保守,如果偵測到不支援的表達式,則始終會引發錯誤。為了說明此行為,我們可以將 starwars
資料寫入磁碟,然後將其作為 Dataset 開啟。當我們在 Dataset 上使用相同的管線時,會收到錯誤
# write and open starwars dataset
dataset_path <- tempfile()
write_dataset(starwars, dataset_path)
sw2 <- open_dataset(dataset_path)
# dplyr pipeline with unsupported expressions
sw2 %>%
filter(!is.na(height), !is.na(mass)) %>%
transmute(name, height, mass, res = residuals(lm(mass ~ height)))
## Error in `residuals()`:
## ! Expression not supported in Arrow
## > Call collect() first to pull data into R.
在管線中間呼叫 collect()
可以解決此問題
sw2 %>%
filter(!is.na(height), !is.na(mass)) %>%
collect() %>%
transmute(name, height, mass, res = residuals(lm(mass ~ height)))
## # A tibble: 59 x 4
## name height mass res
## <chr> <int> <dbl> <dbl>
## 1 Luke Skywalker 172 77 -18.8
## 2 C-3PO 167 75 -17.7
## 3 R2-D2 96 32 -16.4
## 4 Darth Vader 202 136 21.4
## 5 Leia Organa 150 49 -33.1
## 6 Owen Lars 178 120 20.4
## 7 Beru Whitesun Lars 165 75 -16.5
## 8 R5-D4 97 32 -17.0
## 9 Biggs Darklighter 183 84 -18.7
## 10 Obi-Wan Kenobi 182 77 -25.1
## # i 49 more rows
對於某些操作,您可以使用 DuckDB。它原生支援 Arrow,因此您可以使用輔助函數 to_duckdb()
將 Dataset 或查詢物件傳遞給 DuckDB,而不會產生效能損失,並使用 to_arrow()
將物件傳遞回 Arrow
sw %>%
select(1:4) %>%
filter(!is.na(hair_color)) %>%
to_duckdb() %>%
group_by(hair_color) %>%
filter(height < mean(height, na.rm = TRUE)) %>%
to_arrow() %>%
# perform other arrow operations...
collect()
## # A tibble: 28 x 4
## name height mass hair_color
## <chr> <int> <dbl> <chr>
## 1 Watto 137 NA black
## 2 Shmi Skywalker 163 NA black
## 3 Eeth Koth 171 NA black
## 4 Luminara Unduli 170 56.2 black
## 5 Barriss Offee 166 50 black
## 6 Yoda 66 17 white
## 7 Leia Organa 150 49 brown
## 8 Beru Whitesun Lars 165 75 brown
## 9 Wedge Antilles 170 77 brown
## 10 Wicket Systri Warrick 88 20 brown
## # i 18 more rows
延伸閱讀
- 若要瞭解更多關於多檔案資料集的資訊,請參閱資料集文章。
- 若要瞭解更多關於使用者註冊函數的資訊,請參閱
help("register_scalar_function", package = "arrow")
。 - 若要瞭解更多關於以 arrow 開發人員身分撰寫 dplyr 綁定的資訊,請參閱關於撰寫綁定的文章。