arrow 包提供了一些功能,允许用户使用熟悉的 dplyr 语法操作表格 Arrow 数据(`Table` 和 `Dataset` 对象)。要启用此功能,请确保同时加载了 arrow 和 dplyr 包。在本文中,我们将使用 dplyr 中包含的 `starwars` 数据集,将其转换为 Arrow Table,然后分析此数据。请注意,尽管这些示例都使用内存中的 `Table` 对象,但相同的功能也适用于磁盘上的 `Dataset` 对象,只是行为略有不同(本文稍后将 documented)。
首先,让我们加载这些包并创建数据
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 数据框,适合查看或传递给其他 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 查询,如果 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,因此您可以使用辅助函数 ` 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 绑定的更多信息,请参阅关于编写绑定的文章。