7 数据操作 - 表格

7.1 简介

Arrow 项目的目标之一是减少不同数据框实现之间的重复。数据框的底层实现与您编写用于与其交互的代码或应用程序编程接口 (API) 是一个概念上不同的东西。

您可能在 dbplyr 等包中见过这种情况,它允许您使用 dplyr API 与 SQL 数据库进行交互。

Arrow R 包的编写是为了使用 dplyr API 操作底层的 Arrow 表格类对象,这使您可以使用 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

以及使用 Arrow 和 dplyr 语法获得相同结果

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 函数。如果您发现任何您希望在 Arrow 中看到映射的基本 R 或 tidyverse 函数,请 在项目 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 的 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 等效项,我们强烈建议您尽可能使用这些映射,因为原始函数有详细的文档记录,并且已测试映射版本以确保返回的结果符合预期。

但是,在某些情况下,您可能希望使用 Arrow C++ 库中没有基本 R 或 tidyverse 等效项的计算函数。

您可以在 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 博客文章 上找到有关此集成的更多信息。