跳至内容

arrow 包提供了一些功能,使用户能够使用熟悉的 dplyr 语法操作表格 Arrow 数据(TableDataset 对象)。要启用此功能,请确保已加载 arrow 和 dplyr 包。在本文中,我们将使用 dplyr 中包含的 starwars 数据集,将其转换为 Arrow 表,然后分析此数据。请注意,尽管这些示例都使用了内存中的 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 表,适用于传递给其他 arrow 或 dplyr 函数

compute(result)
## Table
## 10 rows x 3 columns
## $name <string>
## $height_in <double>
## $mass_lbs <double>

相反,collect() 返回一个 R 数据框,适用于查看或传递给其他 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 支持许多函数和运算符,常见的函数映射到它们的基本 R 和 tidyverse 等效项:您可以在函数文档中找到 dplyr 查询中支持的函数列表。如果您想看到实现的其他函数,请按照 获取帮助 指南提交问题。

注册自定义绑定

arrow 包使用户能够在某些情况下使用 register_scalar_function() 为自定义函数提供绑定。为了正常运行,要注册的函数必须以 context 作为其第一个参数,这是查询引擎的要求。例如,假设我们想要实现一个函数,将字符串转换为蛇形大小写(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 查询,这些对象保存在内存中,通常应该可以表示为数据框。如果 arrow 包在 dplyr 动词中检测到未实现的函数,它会自动调用 collect() 将数据作为 R 数据框返回,然后再处理该 dplyr 动词。例如,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 对象(可能大于内存)上的查询,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,因此您可以将 Dataset 或查询对象传递给 DuckDB,而不会使用辅助函数 to_duckdb() 产生性能损失,并将对象传递回 Arrow,使用 to_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 "Leia Organa"              150  49   brown     
##  7 "Beru Whitesun Lars"       165  75   brown     
##  8 "Wedge Antilles"           170  77   brown     
##  9 "Wicket Systri Warrick"     88  20   brown     
## 10 "Cord\u00e9"               157  NA   brown     
## # i 18 more rows

进一步阅读