Apache Arrow 是一个软件开发平台,用于构建处理和传输大型数据集的高性能应用程序。它旨在提高数据分析方法的性能,并提高数据在不同系统或编程语言之间移动的效率。
arrow 包提供了一种在 R 中使用 Apache Arrow 的标准方式。它提供了与 Arrow C++ 库的低级接口,以及一些更高级的工具,旨在让 R 用户以自然的方式使用它。本文概述了各个部分如何协同工作,并描述了 R 中类和方法所遵循的约定。
包约定
arrow R 包构建在 Arrow C++ 库之上,而 C++ 是一种面向对象的语言。因此,Arrow C++ 库的核心逻辑封装在类和方法中。在 arrow R 包中,这些实现为 R6 类,所有这些类都采用“TitleCase”命名约定。其中包括一些示例:
- 二维表格数据结构,例如
Table、RecordBatch和Dataset - 一维向量状数据结构,例如
Array和ChunkedArray - 用于读写和流式传输数据的类,例如
ParquetFileReader和CsvTableReader
这个低级接口允许您以非常灵活的方式与 Arrow C++ 库交互,但在许多常见情况下,您可能根本不需要使用它,因为 arrow 还提供了一个使用遵循“snake_case”命名约定的函数的高级接口。其中包括一些示例:
-
arrow_table()允许您创建 Arrow 表格,而无需直接使用Table对象 -
read_parquet()允许您打开 Parquet 文件,而无需直接使用ParquetFileReader对象
本文中使用的所有示例都依赖于此高级接口。
对包结构感兴趣的开发人员,请参阅开发者指南。
Arrow 中的表格数据
Apache Arrow 的一个关键组件是其内存中的列式格式,这是一个标准化、与语言无关的规范,用于在内存中表示结构化、类似表格的数据集。在 arrow R 包中,Table 类用于存储这些对象。表格大致类似于数据框,并具有相似的行为。arrow_table() 函数允许您生成新的 Arrow 表格,其方式与使用 data.frame() 创建新的数据框非常相似
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 Table 的子集
dat[1:2, 1:2]## Table
## 2 rows x 2 columns
## $x <int32>
## $y <string>
同样,可以使用 $ 运算符提取命名列
dat$y## ChunkedArray
## <string>
## [
## [
## "a",
## "b",
## "c"
## ]
## ]
请注意输出:Arrow Table 中的单个列表示为 Chunked Arrays,它们是 Arrow 中的一维数据结构,大致类似于 R 中的向量。
表格是使用 Arrow 在内存中表示矩形数据的主要方式,但它们不是 Arrow C++ 库使用的唯一矩形数据结构:还有用于存储在磁盘而非内存中的数据集(Datasets),以及作为基本构建块但在数据分析中不常用的记录批次(Record Batches)。
要了解有关 arrow 中不同数据对象类的更多信息,请参阅有关数据对象的文章。
将表格转换为数据框
表格是一种用于表示 Arrow C++ 库分配的内存中的矩形数据的数据结构,但可以使用 as.data.frame() 将其强制转换为原生 R 数据框(或 tibbles)
as.data.frame(dat)## x y
## 1 1 a
## 2 2 b
## 3 3 c
当发生这种强制转换时,原始 Arrow Table 中的每个列都必须转换为原生 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 Table。为此,您需要将 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 Datasets 的更多信息,请参阅数据集文章。
使用 dplyr 分析 Arrow 数据
可以使用 dplyr 语法分析 Arrow 表格和数据集。这是可能的,因为 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 简单存储服务 (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 Table 从 R 传递到 Python,如下所示
library(reticulate)
sw_table_python <- r_to_py(sw_table)sw_table_python 对象现在存储为 pyarrow Table:与 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 Table 从 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 开发人员相关但用户极不可能需要的主题。
有关开发过程的概述和开发人员相关文章列表,请参阅开发者指南。