7 操縱資料 - 表格

7.1 簡介

Arrow 專案的目標之一是減少不同資料框實作之間的重複性。資料框的底層實作是一個概念上與你用來處理它的程式碼或應用程式程式介面 (API) 不同的東西。

你可能看過像 dbplyr 的套件,它允許你使用 dplyr API 與 SQL 資料庫互動。

Arrow R 套件編寫的目的在於,讓底層的類 Arrow Table 的物件能夠使用 dplyr API 進行操作,這允許你使用 dplyr 動詞。

例如,以下是一個僅使用 dplyr 的簡短資料處理管道

library(dplyr)
starwars %>%
  filter(species == "Human") %>%
  mutate(height_ft = height/30.48) %>%
  select(name, height_ft)
## # A tibble: 35 × 2
##    name               height_ft
##    <chr>                  <dbl>
##  1 Luke Skywalker          5.64
##  2 Darth Vader             6.63
##  3 Leia Organa             4.92
##  4 Owen Lars               5.84
##  5 Beru Whitesun lars      5.41
##  6 Biggs Darklighter       6.00
##  7 Obi-Wan Kenobi          5.97
##  8 Anakin Skywalker        6.17
##  9 Wilhuff Tarkin          5.91
## 10 Han Solo                5.91
## # ℹ 25 more rows

以及透過 dplyr 語法使用 Arrow 產生的結果相同

arrow_table(starwars) %>%
  filter(species == "Human") %>%
  mutate(height_ft = height/30.48) %>%
  select(name, height_ft) %>%
  collect()
## # A tibble: 35 × 2
##    name               height_ft
##    <chr>                  <dbl>
##  1 Luke Skywalker          5.64
##  2 Darth Vader             6.63
##  3 Leia Organa             4.92
##  4 Owen Lars               5.84
##  5 Beru Whitesun lars      5.41
##  6 Biggs Darklighter       6.00
##  7 Obi-Wan Kenobi          5.97
##  8 Anakin Skywalker        6.17
##  9 Wilhuff Tarkin          5.91
## 10 Han Solo                5.91
## # ℹ 25 more rows

您會注意到我們在上面的 Arrow 管道中使用了 collect()。對於 Arrow 的運作效率之一,是因為它找出需要執行的運算(表達式)的指示,並且只在您實際拉取資料進入 R 階段後才使用 Arrow 執行計算。表示它並非執行許多單獨的運算,而是同時執行所有運算,並使用更最佳化的方式進行。這稱為惰性求值

如果僅在您選擇所需子集,或使用可針對資料區塊進行運算的函數時才將資料拉取至 R,那麼它也表示您能夠處理大於執行程式碼時您機器記憶體的大小資料。

您也可以使用跨越多個檔案分割的資料。例如,您可能有檔案儲存在多個 Parquet 或 Feather 檔案中,並分割在不同的目錄中。您可以透過 open_dataset() 開啟分割或多檔案資料集(如前一章節所述),然後在將任何資料讀入 R 之前使用 Arrow 處理此資料。

7.2 在 Arrow 中使用 dplyr 字句

您想要在 Arrow 中使用 dplyr 字句。

7.2.1 解決方案

library(dplyr)
arrow_table(starwars) %>%
  filter(species == "Human", homeworld == "Tatooine") %>%
  collect()
## # A tibble: 8 × 14
##   name      height  mass hair_color skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Luke Sky…    172    77 blond      fair       blue            19   male  mascu…
## 2 Darth Va…    202   136 none       white      yellow          41.9 male  mascu…
## 3 Owen Lars    178   120 brown, gr… light      blue            52   male  mascu…
## 4 Beru Whi…    165    75 brown      light      blue            47   fema… femin…
## 5 Biggs Da…    183    84 black      light      brown           24   male  mascu…
## 6 Anakin S…    188    84 blond      fair       blue            41.9 male  mascu…
## 7 Shmi Sky…    163    NA black      fair       brown           72   fema… femin…
## 8 Cliegg L…    183    NA brown      fair       blue            82   male  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list<character>>,
## #   vehicles <list<character>>, starships <list<character>>

7.2.2 討論

您可以直接從 Arrow 中使用大部分的 dplyr 字句。

7.2.3 另請參閱

您可以在「dplyr 簡介」中找到各種 dplyr 字句的範例 - 執行 vignette("dplyr", package = "dplyr") 或在pkgdown 網站上檢視。

您可以在建立 Arrow 物件中看到更多關於使用 arrow_table() 建立 Arrow 表格,以及使用 collect() 將它們視為 R 資料框的資訊。

7.3 在 Arrow 中的 dplyr 字句中使用 R 函數

您要在 Arrow 中的 dplyr 字句中使用 R 函數。

7.3.1 解決方案

arrow_table(starwars) %>%
  filter(str_detect(name, "Darth")) %>%
  collect()
## # A tibble: 2 × 14
##   name      height  mass hair_color skin_color eye_color birth_year sex   gender
##   <chr>      <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
## 1 Darth Va…    202   136 none       white      yellow          41.9 male  mascu…
## 2 Darth Ma…    175    80 none       red        yellow          54   male  mascu…
## # ℹ 5 more variables: homeworld <chr>, species <chr>, films <list<character>>,
## #   vehicles <list<character>>, starships <list<character>>

7.3.2 討論

Arrow R 套件允許您使用包含基本 R 和許多 tidyverse 函數的表達式的 dplyr 動詞,但在底層呼叫 Arrow 函數。如果您發現任何基本 R 或 tidyverse 函數您希望在 Arrow 中看到其對應函數,請 在專案 JIRA 中開啟問題

以下套件(以及其他一些套件)有許多函數繫結/對應關係寫在 arrow 中。

如果你試著呼叫沒有 arrow 對應關係的函數,資料將會拉回 R,而且您將會看到一個警告訊息。

library(stringr)

arrow_table(starwars) %>%
  mutate(name_split = str_split_fixed(name, " ", 2)) %>%
  collect()
## Warning: Expression str_split_fixed(name, " ", 2) not supported in Arrow;
## pulling data into R
## # A tibble: 87 × 15
##    name     height  mass hair_color skin_color eye_color birth_year sex   gender
##    <chr>     <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr> <chr> 
##  1 Luke Sk…    172    77 blond      fair       blue            19   male  mascu…
##  2 C-3PO       167    75 <NA>       gold       yellow         112   none  mascu…
##  3 R2-D2        96    32 <NA>       white, bl… red             33   none  mascu…
##  4 Darth V…    202   136 none       white      yellow          41.9 male  mascu…
##  5 Leia Or…    150    49 brown      light      brown           19   fema… femin…
##  6 Owen La…    178   120 brown, gr… light      blue            52   male  mascu…
##  7 Beru Wh…    165    75 brown      light      blue            47   fema… femin…
##  8 R5-D4        97    32 <NA>       white, red red             NA   none  mascu…
##  9 Biggs D…    183    84 black      light      brown           24   male  mascu…
## 10 Obi-Wan…    182    77 auburn, w… fair       blue-gray       57   male  mascu…
## # ℹ 77 more rows
## # ℹ 6 more variables: homeworld <chr>, species <chr>, films <list<character>>,
## #   vehicles <list<character>>, starships <list<character>>,
## #   name_split <chr[,2]>

7.4 在 Arrow 的 dplyr 動詞中使用 Arrow 函數

您想要使用在 Arrow 的 C++ 函式庫中執行的函數,但

  • 它沒有對應該基本 R 或 tidyverse 的等效項,或
  • 它有對應關係,但您仍然要直接呼叫 C++ 函數

7.4.1 解決方案

arrow_table(starwars) %>%
  select(name) %>%
  mutate(padded_name = arrow_ascii_lpad(name, options = list(width = 10, padding = "*"))) %>%
  collect()
## # A tibble: 87 × 2
##    name               padded_name       
##    <chr>              <chr>             
##  1 Luke Skywalker     Luke Skywalker    
##  2 C-3PO              *****C-3PO        
##  3 R2-D2              *****R2-D2        
##  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              *****R5-D4        
##  9 Biggs Darklighter  Biggs Darklighter 
## 10 Obi-Wan Kenobi     Obi-Wan Kenobi    
## # ℹ 77 more rows

7.4.2 討論

絕大多數 Arrow C++ 運算函數已對應到它們的基本 R 或 tidyverse 等效項,而且強烈建議您盡可能使用這些對應關係,因為原始函數有良好的說明文件,而且已測試對應版本以確保傳回結果符合預期。

然而,在某些情況下,您可能想要使用沒有基本 R 或 tidyverse 等效項的 Arrow C++ 函式庫中的運算函數。

您可以在 C++ 文件檔 中找到 Arrow C++ 運算函數的說明文件。此說明文件列出所有可用的運算函數、它們需要的任何相關選項類別,以及可與它們一起使用的有效資料型態。

您可以透過呼叫 list_compute_functions() 來列出 R 中所有可用的 Arrow 運算函數。

list_compute_functions()
##   [1] "abs"                             "abs_checked"                    
##   [3] "acos"                            "acos_checked"                   
##   [5] "add"                             "add_checked"                    
##   [7] "all"                             "and"                            
##   [9] "and_kleene"                      "and_not"                        
##  [11] "and_not_kleene"                  "any"                            
##  [13] "approximate_median"              "array_filter"                   
##  [15] "array_sort_indices"              "array_take"                     
##  [17] "ascii_capitalize"                "ascii_center"                   
##  [19] "ascii_is_alnum"                  "ascii_is_alpha"                 
##  [21] "ascii_is_decimal"                "ascii_is_lower"                 
##  [23] "ascii_is_printable"              "ascii_is_space"                 
##  [25] "ascii_is_title"                  "ascii_is_upper"                 
##  [27] "ascii_lower"                     "ascii_lpad"                     
##  [29] "ascii_ltrim"                     "ascii_ltrim_whitespace"         
##  [31] "ascii_reverse"                   "ascii_rpad"                     
##  [33] "ascii_rtrim"                     "ascii_rtrim_whitespace"         
##  [35] "ascii_split_whitespace"          "ascii_swapcase"                 
##  [37] "ascii_title"                     "ascii_trim"                     
##  [39] "ascii_trim_whitespace"           "ascii_upper"                    
##  [41] "asin"                            "asin_checked"                   
##  [43] "assume_timezone"                 "atan"                           
##  [45] "atan2"                           "binary_join"                    
##  [47] "binary_join_element_wise"        "binary_length"                  
##  [49] "binary_repeat"                   "binary_replace_slice"           
##  [51] "binary_reverse"                  "binary_slice"                   
##  [53] "bit_wise_and"                    "bit_wise_not"                   
##  [55] "bit_wise_or"                     "bit_wise_xor"                   
##  [57] "case_when"                       "cast"                           
##  [59] "ceil"                            "ceil_temporal"                  
##  [61] "choose"                          "coalesce"                       
##  [63] "cos"                             "cos_checked"                    
##  [65] "count"                           "count_all"                      
##  [67] "count_distinct"                  "count_substring"                
##  [69] "count_substring_regex"           "cumulative_max"                 
##  [71] "cumulative_min"                  "cumulative_prod"                
##  [73] "cumulative_prod_checked"         "cumulative_sum"                 
##  [75] "cumulative_sum_checked"          "day"                            
##  [77] "day_of_week"                     "day_of_year"                    
##  [79] "day_time_interval_between"       "days_between"                   
##  [81] "dictionary_encode"               "divide"                         
##  [83] "divide_checked"                  "drop_null"                      
##  [85] "ends_with"                       "equal"                          
##  [87] "exp"                             "extract_regex"                  
##  [89] "fill_null_backward"              "fill_null_forward"              
##  [91] "filter"                          "find_substring"                 
##  [93] "find_substring_regex"            "first"                          
##  [95] "first_last"                      "floor"                          
##  [97] "floor_temporal"                  "greater"                        
##  [99] "greater_equal"                   "hour"                           
## [101] "hours_between"                   "if_else"                        
## [103] "index"                           "index_in"                       
## [105] "index_in_meta_binary"            "indices_nonzero"                
## [107] "invert"                          "is_dst"                         
## [109] "is_finite"                       "is_in"                          
## [111] "is_in_meta_binary"               "is_inf"                         
## [113] "is_leap_year"                    "is_nan"                         
## [115] "is_null"                         "is_valid"                       
## [117] "iso_calendar"                    "iso_week"                       
## [119] "iso_year"                        "last"                           
## [121] "less"                            "less_equal"                     
## [123] "list_element"                    "list_flatten"                   
## [125] "list_parent_indices"             "list_slice"                     
## [127] "list_value_length"               "ln"                             
## [129] "ln_checked"                      "local_timestamp"                
## [131] "log10"                           "log10_checked"                  
## [133] "log1p"                           "log1p_checked"                  
## [135] "log2"                            "log2_checked"                   
## [137] "logb"                            "logb_checked"                   
## [139] "make_struct"                     "map_lookup"                     
## [141] "match_like"                      "match_substring"                
## [143] "match_substring_regex"           "max"                            
## [145] "max_element_wise"                "mean"                           
## [147] "microsecond"                     "microseconds_between"           
## [149] "millisecond"                     "milliseconds_between"           
## [151] "min"                             "min_element_wise"               
## [153] "min_max"                         "minute"                         
## [155] "minutes_between"                 "mode"                           
## [157] "month"                           "month_day_nano_interval_between"
## [159] "month_interval_between"          "multiply"                       
## [161] "multiply_checked"                "nanosecond"                     
## [163] "nanoseconds_between"             "negate"                         
## [165] "negate_checked"                  "not_equal"                      
## [167] "or"                              "or_kleene"                      
## [169] "pairwise_diff"                   "pairwise_diff_checked"          
## [171] "partition_nth_indices"           "power"                          
## [173] "power_checked"                   "product"                        
## [175] "quantile"                        "quarter"                        
## [177] "quarters_between"                "random"                         
## [179] "rank"                            "replace_substring"              
## [181] "replace_substring_regex"         "replace_with_mask"              
## [183] "round"                           "round_binary"                   
## [185] "round_temporal"                  "round_to_multiple"              
## [187] "run_end_decode"                  "run_end_encode"                 
## [189] "second"                          "seconds_between"                
## [191] "select_k_unstable"               "shift_left"                     
## [193] "shift_left_checked"              "shift_right"                    
## [195] "shift_right_checked"             "sign"                           
## [197] "sin"                             "sin_checked"                    
## [199] "sort_indices"                    "split_pattern"                  
## [201] "split_pattern_regex"             "sqrt"                           
## [203] "sqrt_checked"                    "starts_with"                    
## [205] "stddev"                          "strftime"                       
## [207] "string_is_ascii"                 "strptime"                       
## [209] "struct_field"                    "subsecond"                      
## [211] "subtract"                        "subtract_checked"               
## [213] "sum"                             "take"                           
## [215] "tan"                             "tan_checked"                    
## [217] "tdigest"                         "true_unless_null"               
## [219] "trunc"                           "unique"                         
## [221] "us_week"                         "us_year"                        
## [223] "utf8_capitalize"                 "utf8_center"                    
## [225] "utf8_is_alnum"                   "utf8_is_alpha"                  
## [227] "utf8_is_decimal"                 "utf8_is_digit"                  
## [229] "utf8_is_lower"                   "utf8_is_numeric"                
## [231] "utf8_is_printable"               "utf8_is_space"                  
## [233] "utf8_is_title"                   "utf8_is_upper"                  
## [235] "utf8_length"                     "utf8_lower"                     
## [237] "utf8_lpad"                       "utf8_ltrim"                     
## [239] "utf8_ltrim_whitespace"           "utf8_normalize"                 
## [241] "utf8_replace_slice"              "utf8_reverse"                   
## [243] "utf8_rpad"                       "utf8_rtrim"                     
## [245] "utf8_rtrim_whitespace"           "utf8_slice_codeunits"           
## [247] "utf8_split_whitespace"           "utf8_swapcase"                  
## [249] "utf8_title"                      "utf8_trim"                      
## [251] "utf8_trim_whitespace"            "utf8_upper"                     
## [253] "value_counts"                    "variance"                       
## [255] "week"                            "weeks_between"                  
## [257] "xor"                             "year"                           
## [259] "year_month_day"                  "years_between"

這裡的大部分函數已對應到它們的基本 R 或 tidyverse 等效項,而且可以像平常一樣在 dplyr 查詢中呼叫。對於沒有基本 R 或 tidyverse 等效項的函數,或您想要提供自訂選項,您可以在它們的名稱之前加上前綴「arrow_」來呼叫。

例如,基本 R 的 is.na() 函數等同於 Arrow C++ 運算函數 is_null(),其中選項 nan_is_null 設為 TRUE
在 arrow 中已建立這些函數之間的對應關係(其中 nan_is_null 設為 TRUE)。

demo_df <- data.frame(x = c(1, 2, 3, NA, NaN))

arrow_table(demo_df) %>%
  mutate(y = is.na(x)) %>% 
  collect()
## # A tibble: 5 × 2
##       x y    
##   <dbl> <lgl>
## 1     1 FALSE
## 2     2 FALSE
## 3     3 FALSE
## 4    NA TRUE 
## 5   NaN TRUE

如果你想呼叫 Arrow 的 `is_null()` 函數,但將 `nan_is_null` 設定為 `FALSE`(因此會在檢查的值為 `NA` 時傳回 `TRUE`,但在檢查的值為 `NaN` 時傳回 `FALSE`),你必須直接呼叫 `is_null()`,並指定 `nan_is_null = FALSE` 選項。

arrow_table(demo_df) %>%
  mutate(y = arrow_is_null(x, options  = list(nan_is_null = FALSE))) %>% 
  collect()
## # A tibble: 5 × 2
##       x y    
##   <dbl> <lgl>
## 1     1 FALSE
## 2     2 FALSE
## 3     3 FALSE
## 4    NA TRUE 
## 5   NaN FALSE

7.4.2.1 具有選項的運算函數

雖然並非所有 Arrow C++ 運算函數都需要指定選項,但大部分都需要。若要讓這些函數在 R 中運作,它們必須透過 R 套件的 C++ 程式碼連結至適當的 libarrow 選項 C++ 類別。在撰寫本文時,Arrow R 套件開發版本中提供的所有運算函數都已與其選項類別關聯在一起。然而,隨著 Arrow C++ 函式庫功能的擴充,可能會新增尚未具備 R 繫結的運算函數。如果你在 R 套件中找到你想要使用的 C++ 運算函數,請 在 Github 專案中開啟一個議題

7.5 運算視窗聚集

你想要對分組表格或在像 `filter()` 之類的行向操作中應用聚集(例如 `mean()`)。

7.5.1 解決方案

arrow_table(starwars) %>%
  select(1:4) %>%
  filter(!is.na(hair_color)) %>%
  left_join(
    arrow_table(starwars) %>%
      group_by(hair_color) %>%
      summarize(mean_height = mean(height, na.rm = TRUE))
  ) %>%
  filter(height < mean_height) %>%
  select(!mean_height) %>%
  collect()
## # A tibble: 29 × 4
##    name                  height  mass hair_color
##    <chr>                  <int> <dbl> <chr>     
##  1 Luke Skywalker           172    77 blond     
##  2 Leia Organa              150    49 brown     
##  3 Beru Whitesun lars       165    75 brown     
##  4 Wedge Antilles           170    77 brown     
##  5 Yoda                      66    17 white     
##  6 Lobot                    175    79 none      
##  7 Ackbar                   180    83 none      
##  8 Wicket Systri Warrick     88    20 brown     
##  9 Nien Nunb                160    68 none      
## 10 Finis Valorum            170    NA blond     
## # ℹ 19 more rows

或者使用 `to_duckdb()`

arrow_table(starwars) %>%
  select(1:4) %>%
  filter(!is.na(hair_color)) %>%
  to_duckdb() %>%
  group_by(hair_color) %>%
  filter(height < mean(height, na.rm = TRUE)) %>%
  to_arrow() %>%
  collect()
## # A tibble: 29 × 4
##    name                  height  mass hair_color
##    <chr>                  <int> <dbl> <chr>     
##  1 Luke Skywalker           172    77 blond     
##  2 Finis Valorum            170    NA blond     
##  3 Yoda                      66    17 white     
##  4 Leia Organa              150    49 brown     
##  5 Beru Whitesun lars       165    75 brown     
##  6 Wedge Antilles           170    77 brown     
##  7 Wicket Systri Warrick     88    20 brown     
##  8 Cordé                    157    NA brown     
##  9 Dormé                    165    NA brown     
## 10 Padmé Amidala            165    45 brown     
## # ℹ 19 more rows

7.5.2 討論

Arrow 不支援視窗函數,並將資料提取到 R。對於大型表格,這會犧牲效能。

arrow_table(starwars) %>%
  select(1:4) %>%
  filter(!is.na(hair_color)) %>%
  group_by(hair_color) %>%
  filter(height < mean(height, na.rm = TRUE))
## Warning: Expression height < mean(height, na.rm = TRUE) not supported in Arrow;
## pulling data into R
## # A tibble: 29 × 4
## # Groups:   hair_color [5]
##    name                  height  mass hair_color
##    <chr>                  <int> <dbl> <chr>     
##  1 Luke Skywalker           172    77 blond     
##  2 Leia Organa              150    49 brown     
##  3 Beru Whitesun lars       165    75 brown     
##  4 Wedge Antilles           170    77 brown     
##  5 Yoda                      66    17 white     
##  6 Lobot                    175    79 none      
##  7 Ackbar                   180    83 none      
##  8 Wicket Systri Warrick     88    20 brown     
##  9 Nien Nunb                160    68 none      
## 10 Finis Valorum            170    NA blond     
## # ℹ 19 more rows

你可以透過以下方式對 arrow 表格執行這些視窗聚集運算:

  • 個別運算聚集,並加入結果
  • 將資料傳遞到 DuckDB,並使用 DuckDB 查詢引擎執行運算

Arrow 支援與 DuckDB 的零複製整合,DuckDB 可以直接查詢 Arrow 資料集,並將查詢結果串流回 Arrow。此整合使用 DuckDB 與 Arrow 之間的零複製資料串流,讓你可以在两者之間組合查詢,而且在前後傳遞資料時不需要負擔(重新)序列化資料的成本。這在 Arrow 或 DuckDB 查詢引擎其中一種支援某項功能,但另一種不支援時特別有用。你可以在 Arrow 部落格文章 中找到更多關於此整合的資訊。