跳到內容

arrow 套件提供功能,讓使用者可以使用熟悉的 dplyr 語法來操作表格化的 Arrow 資料 (TableDataset 物件)。要啟用此功能,請確保 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()

sw %>%
  group_by(species) %>%
  summarize(mean_height = mean(height, na.rm = TRUE)) %>%
  collect()
## # 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
sw %>%
  count(gender) %>%
  collect()
## # 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_typeout_type 參數用於指定輸入和輸出的預期資料類型,而 auto_convert 則指定 arrow 是否應自動將任何 R 輸入轉換為其 Arrow 等效項。

註冊後,以下程式碼可以運作

sw %>%
  mutate(name, snake_name = to_snake_name(name), .keep = "none") %>%
  collect()
## # 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

延伸閱讀