Apache Arrow 是一个软件开发平台,用于构建高性能应用程序,这些应用程序处理和传输大型数据集。它旨在提高数据分析方法的性能,并提高将数据从一个系统或编程语言移动到另一个系统的效率。
arrow 包提供了一种使用 Apache Arrow 的标准方法。它提供了一个到 Arrow C++ 库 的低级接口,以及一些高级工具,用于以一种对 R 用户来说感觉自然的方式使用它。本文概述了各个部分如何组合在一起,并描述了类和方法在 R 中遵循的约定。
包约定
arrow R 包构建在 Arrow C++ 库之上,而 C++ 是一种面向对象的语言。因此,Arrow C++ 库的核心逻辑封装在类和方法中。在 arrow R 包中,这些被实现为 R6
类,这些类都采用“标题大小写”命名约定。以下是一些示例:
- 二维表格数据结构,例如
Table
、RecordBatch
和Dataset
- 一维向量式数据结构,例如
Array
和ChunkedArray
- 用于读取、写入和流式传输数据的类,例如
ParquetFileReader
和CsvTableReader
此低级接口允许您以非常灵活的方式与 Arrow C++ 库交互,但在许多常见情况下,您可能根本不需要使用它,因为 arrow 还提供了一个高级接口,使用遵循“蛇形大小写”命名约定的函数。以下是一些示例:
-
arrow_table()
允许您在不直接使用Table
对象的情况下创建 Arrow 表 -
read_parquet()
允许您在不直接使用ParquetFileReader
对象的情况下打开 Parquet 文件
本文中使用的所有示例都依赖于此高级接口。
对于有兴趣了解有关包结构的更多信息的开发人员,请参阅 开发者指南。
Arrow 中的表格数据
Apache Arrow 的一个关键组件是其内存中列式格式,这是一个标准化的、与语言无关的规范,用于表示内存中的结构化表格式数据集。在 arrow R 包中,Table
类用于存储这些对象。表格大致类似于数据帧,并具有类似的行为。arrow_table()
函数允许您以与使用 data.frame()
创建新数据帧的方式非常相似的方式生成新的 Arrow 表
library(arrow, warn.conflicts = FALSE)
dat <- arrow_table(x = 1:3, y = c("a", "b", "c"))
dat
## Table
## 3 rows x 2 columns
## $x <int32>
## $y <string>
您可以使用 [
指定 Arrow 表的子集,就像对数据帧一样
dat[1:2, 1:2]
## Table
## 2 rows x 2 columns
## $x <int32>
## $y <string>
类似地,$
运算符可用于提取命名列
dat$y
## ChunkedArray
## <string>
## [
## [
## "a",
## "b",
## "c"
## ]
## ]
注意输出:Arrow 表中的各个列表示为分块数组,它们是 Arrow 中的一维数据结构,大致类似于 R 中的向量。
表格是使用 Arrow 在内存中表示矩形数据的首要方式,但它们不是 Arrow C++ 库使用的唯一矩形数据结构:还有数据集,用于存储在磁盘上而不是内存中的数据,以及记录批次,它们是基本构建块,但通常不用于数据分析。
要了解有关 arrow 中不同数据对象类的更多信息,请参阅关于 数据对象 的文章。
将表格转换为数据帧
表格是用于表示 Arrow C++ 库分配的内存中矩形数据的 data structure,但可以使用 as.data.frame()
将它们强制转换为本机 R 数据帧(或 tibble)。
as.data.frame(dat)
## x y
## 1 1 a
## 2 2 b
## 3 3 c
当此强制转换发生时,原始 Arrow 表中的每一列都必须转换为本机 R 数据对象。例如,在 dat
表中,dat$x
存储为从 C++ 继承的 Arrow 数据类型 int32,当调用 as.data.frame()
时,它将变为 R 整数类型。
可以对转换过程进行精细控制。要了解有关不同类型以及如何转换它们的更多信息,请参阅 数据类型 文章。
读取和写入数据
使用 arrow 的主要方法之一是在几种常见格式中读取和写入数据文件。arrow 包提供了极快的 CSV 读取和写入功能,但此外还支持 Parquet 和 Arrow(也称为 Feather)等数据格式,这些格式在其他包中不受广泛支持。此外,arrow 包还支持多文件数据集,其中单个矩形数据集存储在多个文件中。
单个文件
当目标是将单个数据文件读入内存时,您可以使用以下几种函数
-
read_parquet()
:读取 Parquet 格式的文件 -
read_feather()
:读取 Arrow/Feather 格式的文件 -
read_delim_arrow()
:读取分隔文本文件 -
read_csv_arrow()
:读取逗号分隔值 (CSV) 文件 -
read_tsv_arrow()
:读取制表符分隔值 (TSV) 文件 -
read_json_arrow()
:读取 JSON 数据文件
在除了 JSON 之外的所有情况下,都有一个相应的 write_*()
函数,允许您以适当的格式写入数据文件。
默认情况下,read_*()
函数将返回数据帧或 tibble,但您也可以使用它们将数据读入 Arrow 表。为此,您需要将 as_data_frame
参数设置为 FALSE
。
在下面的示例中,我们使用 dplyr 包提供的 starwars
数据,并使用 write_parquet()
将其写入 Parquet 文件
library(dplyr, warn.conflicts = FALSE)
file_path <- tempfile(fileext = ".parquet")
write_parquet(starwars, file_path)
然后,我们可以使用 read_parquet()
从此文件加载数据。如下所示,默认行为是返回数据帧 (sw_frame
),但当我们设置 as_data_frame = FALSE
时,数据将作为 Arrow 表 (sw_table
) 读取
sw_frame <- read_parquet(file_path)
sw_table <- read_parquet(file_path, as_data_frame = FALSE)
sw_table
## Table
## 87 rows x 14 columns
## $name <string>
## $height <int32>
## $mass <double>
## $hair_color <string>
## $skin_color <string>
## $eye_color <string>
## $birth_year <double>
## $sex <string>
## $gender <string>
## $homeworld <string>
## $species <string>
## $films: list<element <string>>
## $vehicles: list<element <string>>
## $starships: list<element <string>>
要了解有关读取和写入单个数据文件的更多信息,请参阅 读取/写入文章。
多文件数据集
当表格数据集变得很大时,通常的做法是将数据划分为有意义的子集,并将每个子集存储在单独的文件中。除此之外,这意味着如果只有数据集的一个子集与分析相关,则只需要读取一个(较小的)文件。arrow 包提供了 Dataset 接口,这是一种方便的方式来读取、写入和分析单个大于内存的数据文件和多文件数据集。
为了说明这些概念,我们将创建一个包含 100000 行的无意义数据集,该数据集可以分成 10 个子集
set.seed(1234)
nrows <- 100000
random_data <- data.frame(
x = rnorm(nrows),
y = rnorm(nrows),
subset = sample(10, nrows, replace = TRUE)
)
我们可能想要做的是将此数据分区,然后将其写入 10 个单独的 Parquet 文件,每个文件对应于 subset
列的每个值。为此,我们首先指定一个文件夹的路径,我们将把数据文件写入其中
然后,我们可以使用来自 dplyr 的 group_by()
函数指定将使用 subset
列对数据进行分区,然后将分组后的数据传递给 write_dataset()
random_data %>%
group_by(subset) %>%
write_dataset(dataset_path)
这将创建一个包含 10 个文件的文件集,每个子集一个。这些文件根据以下所示的“Hive 分区”格式命名
list.files(dataset_path, recursive = TRUE)
## [1] "subset=1/part-0.parquet" "subset=10/part-0.parquet"
## [3] "subset=2/part-0.parquet" "subset=3/part-0.parquet"
## [5] "subset=4/part-0.parquet" "subset=5/part-0.parquet"
## [7] "subset=6/part-0.parquet" "subset=7/part-0.parquet"
## [9] "subset=8/part-0.parquet" "subset=9/part-0.parquet"
每个 Parquet 文件都可以使用 read_parquet()
单独打开,但在许多情况下更方便(尤其是对于非常大的数据集)扫描文件夹并“连接”到数据集,而不将其加载到内存中。我们可以使用 open_dataset()
来做到这一点
dset <- open_dataset(dataset_path)
dset
## FileSystemDataset with 10 Parquet files
## 3 columns
## x: double
## y: double
## subset: int32
此 dset
对象不存储内存中的数据,只存储一些元数据。但是,正如下一节所讨论的那样,可以像加载过一样分析 dset
所引用的数据。
要了解有关 Arrow 数据集的更多信息,请参阅 数据集文章。
使用 dplyr 分析 Arrow 数据
Arrow 表和数据集可以使用 dplyr 语法进行分析。这是可能的,因为 arrow R 包提供了一个后端,它将 dplyr 动词转换为 Arrow C++ 库可以理解的命令,并且同样会转换出现在 dplyr 动词调用中的 R 表达式。例如,虽然 dset
数据集不是数据帧(并且不存储内存中的数据值),但您仍然可以将其传递给像下面所示的 dplyr 管道
dset %>%
group_by(subset) %>%
summarize(mean_x = mean(x), min_y = min(y)) %>%
filter(mean_x > 0) %>%
arrange(subset) %>%
collect()
## # A tibble: 6 x 3
## subset mean_x min_y
## <int> <dbl> <dbl>
## 1 2 0.00486 -4.00
## 2 3 0.00440 -3.86
## 3 4 0.0125 -3.65
## 4 6 0.0234 -3.88
## 5 7 0.00477 -4.65
## 6 9 0.00557 -3.50
请注意,我们在管道末尾调用 collect()
。在调用 collect()
(或相关的 compute()
函数)之前,不会执行任何实际计算。这种“惰性求值”使 Arrow C++ 计算引擎能够优化如何执行计算。
要了解有关分析 Arrow 数据的更多信息,请参阅 数据整理文章。 dplyr 查询中可用的函数列表 页面也可能有用。
连接到云存储
arrow R 包的另一个用途是读取、写入和分析存储在云服务上的远程数据集。该包目前同时支持 Amazon Simple Storage Service (S3) 和 Google Cloud Storage (GCS)。以下示例说明了如何使用 s3_bucket()
引用 S3 存储桶,并使用 open_dataset()
连接到存储在其中的数据集
bucket <- s3_bucket("voltrondata-labs-datasets/nyc-taxi")
nyc_taxi <- open_dataset(bucket)
要了解有关 arrow 中对云服务的支持的更多信息,请参阅 云存储 文章。
在 R 和 Python 之间进行高效的数据交换
reticulate 包提供了一个接口,允许您从 R 中调用 Python 代码。arrow 包旨在与 reticulate 互操作。如果 Python 环境安装了 pyarrow 库(arrow 包的 Python 等效项),则可以使用 reticulate 中的 r_to_py()
函数将 Arrow 表从 R 传递到 Python,如下所示
library(reticulate)
sw_table_python <- r_to_py(sw_table)
现在,sw_table_python
对象存储为 pyarrow 表:Table 类的 Python 等效项。当您打印对象时,您可以看到这一点
sw_table_python
## pyarrow.Table
## name: string
## height: int32
## mass: double
## hair_color: string
## skin_color: string
## eye_color: string
## birth_year: double
## sex: string
## gender: string
## homeworld: string
## species: string
## films: list<element: string>
## child 0, element: string
## vehicles: list<element: string>
## child 0, element: string
## starships: list<element: string>
## child 0, element: string
## ----
## name: [["Luke Skywalker","C-3PO","R2-D2","Darth Vader","Leia Organa",...,"Finn","Rey","Poe Dameron","BB8","Captain Phasma"]]
## height: [[172,167,96,202,150,...,null,null,null,null,null]]
## mass: [[77,75,32,136,49,...,null,null,null,null,null]]
## hair_color: [["blond",null,null,"none","brown",...,"black","brown","brown","none","none"]]
## skin_color: [["fair","gold","white, blue","white","light",...,"dark","light","light","none","none"]]
## eye_color: [["blue","yellow","red","yellow","brown",...,"dark","hazel","brown","black","unknown"]]
## birth_year: [[19,112,33,41.9,19,...,null,null,null,null,null]]
## sex: [["male","none","none","male","female",...,"male","female","male","none","female"]]
## gender: [["masculine","masculine","masculine","masculine","feminine",...,"masculine","feminine","masculine","masculine","feminine"]]
## homeworld: [["Tatooine","Tatooine","Naboo","Tatooine","Alderaan",...,null,null,null,null,null]]
## ...
重要的是要认识到,当此传输发生时,只复制了 C++ 指针(即引用 Arrow C++ 库存储的底层数据对象的元数据)。数据值本身在内存中的相同位置。因此,将 Arrow 表从 R 传递到 Python 比将 R 中的数据帧复制到 Python 中的 Pandas DataFrame 快得多。
要了解有关在 R 和 Python 之间传递 Arrow 数据的更多信息,请参阅关于 python 集成 的文章。
访问 Arrow 消息、缓冲区和流
arrow 包还提供许多到 C++ 库的低级绑定,这些绑定使您能够访问和操作 Arrow 对象。您可以使用这些绑定构建到使用 Arrow 的其他应用程序和服务的连接器。一个例子是 Spark: sparklyr
包支持使用 Arrow 将数据移入和移出 Spark,从而产生 显著的性能提升。
贡献到 arrow
Apache Arrow 是一个涵盖多种语言的庞大项目,而 arrow R 包只是这个大型项目的一部分。因此,对于希望为该包做出贡献的开发人员来说,有一些特殊注意事项。为了帮助简化此流程,arrow 文档中包含几篇文章,讨论与 arrow 开发人员相关的主题,但用户极不可能需要这些主题。
有关开发流程概述以及开发人员相关文章列表,请参阅 开发者指南。