集成 PyArrow 与 R#
Arrow 支持通过 Arrow C 数据接口 在同一进程内交换数据。
这可用于在 Python 和 R 的函数及方法之间交换数据,从而使两种语言能够交互,而无需支付任何数据编组(marshaling)和解组的开销。
注意
本文假设您拥有一个已正确安装 pyarrow 的 Python 环境,以及一个已正确安装 arrow 库的 R 环境。有关更多详细信息,请参阅 Python 安装说明 和 R 安装说明。
从 Python 调用 R 函数#
假设我们有一个简单的 R 函数,它接收一个 Arrow 数组并为其所有元素加上 3
library(arrow)
addthree <- function(arr) {
return(arr + 3L)
}
我们可以将此函数保存在 addthree.R 文件中,以便重复使用。
一旦创建了 addthree.R 文件,我们就可以使用 rpy2 库从 Python 调用其任何函数,该库可在 Python 解释器中启用 R 运行时。
与大多数 Python 库一样,可以使用 pip 安装 rpy2
$ pip install rpy2
我们可以对 addthree 函数执行的最基本操作是从 Python 中传入一个数字并观察其如何返回结果。
为此,我们可以创建一个 addthree.py 文件,利用 rpy2 从 addthree.R 文件中导入 addthree 函数并进行调用
import rpy2.robjects as robjects
# Load the addthree.R file
r_source = robjects.r["source"]
r_source("addthree.R")
# Get a reference to the addthree function
addthree = robjects.r["addthree"]
# Invoke the function
r = addthree(3)
# Access the returned value
value = r[0]
print(value)
运行 addthree.py 文件将展示我们的 Python 代码如何访问 R 函数并打印出预期的结果
$ python addthree.py
6
如果我们不想传递基本数据类型,而是想传递 Arrow 数组,则可以依赖 rpy2-arrow 模块,该模块实现了 rpy2 对 Arrow 类型的支持。
rpy2-arrow 可以通过 pip 安装
$ pip install rpy2-arrow
rpy2-arrow 实现了从 PyArrow 对象到 R Arrow 对象的转换器,由于它依赖于 C 数据接口,因此这一过程不会产生任何数据拷贝开销。
为了向 addthree 函数传递 PyArrow 数组,我们的 addthree.py 文件需要进行修改,以启用 rpy2-arrow 转换器,然后传入 PyArrow 数组
import rpy2.robjects as robjects
from rpy2_arrow.pyarrow_rarrow import (rarrow_to_py_array,
converter as arrowconverter)
from rpy2.robjects.conversion import localconverter
r_source = robjects.r["source"]
r_source("addthree.R")
addthree = robjects.r["addthree"]
import pyarrow
array = pyarrow.array((1, 2, 3))
# Enable rpy2-arrow converter so that R can receive the array.
with localconverter(arrowconverter):
r_result = addthree(array)
# The result of the R function will be an R Environment
# we can convert the Environment back to a pyarrow Array
# using the rarrow_to_py_array function
py_result = rarrow_to_py_array(r_result)
print("RESULT", type(py_result), py_result)
运行修改后的 addthree.py 现在应该能正确执行 R 函数并打印出结果 PyArrow 数组
$ python addthree.py
RESULT <class 'pyarrow.lib.Int64Array'> [
4,
5,
6
]
如需更多信息,您可以参考 rpy2 文档 和 rpy2-arrow 文档
从 R 调用 Python 函数#
可以通过 reticulate 库将 Python 函数暴露给 R。例如,如果我们想在 R 中对 R 创建的数组调用 pyarrow.compute.add(),可以通过 reticulate 在 R 中导入 pyarrow 来实现。
一个调用 add 函数为 R 数组元素加 3 的基础 addthree.R 脚本如下所示
# Load arrow and reticulate libraries
library(arrow)
library(reticulate)
# Create a new array in R
a <- Array$create(c(1, 2, 3))
# Make pyarrow.compute available to R
pc <- import("pyarrow.compute")
# Invoke pyarrow.compute.add with the array and 3
# This will add 3 to all elements of the array and return a new Array
result <- pc$add(a, 3)
# Print the result to confirm it's what we expect
print(result)
运行 addthree.R 脚本将打印出将 3 加到原始 Array$create(c(1, 2, 3)) 数组所有元素后的结果
$ R --silent -f addthree.R
Array
<double>
[
4,
5,
6
]
如需更多信息,您可以参考 Reticulate 文档 以及 R Arrow 文档
使用 C 数据接口进行 R 到 Python 的通信#
上述两种解决方案在底层都使用了 Arrow C 数据接口。
如果我们想扩展之前的 addthree 示例,从使用 rpy2-arrow 切换到使用原始的 C 数据接口,可以通过修改代码库来实现。
为了支持通过 C 数据接口导入 Arrow 数组,我们需要将 addthree 函数包装在一个函数中,该函数执行从 C 数据接口导入 R Arrow 数组所需的额外工作。
这些工作将由 addthree_cdata 函数完成,它会在数组导入后调用 addthree 函数。
因此,我们的 addthree.R 将同时包含 addthree_cdata 和 addthree 函数
library(arrow)
addthree_cdata <- function(array_ptr_s, schema_ptr_s) {
a <- Array$import_from_c(array_ptr, schema_ptr)
return(addthree(a))
}
addthree <- function(arr) {
return(arr + 3L)
}
现在我们可以通过 array_ptr_s 和 schema_ptr_s 参数从 Python 向 R 提供数组及其模式,以便 R 可以从中重建 Array,然后使用该数组调用 addthree。
从 Python 调用 addthree_cdata 包括构建我们想要传递给 R 的数组、将其导出到 C 数据接口,然后将导出的引用传递给 R 函数。
我们的 addthree.py 将变为
# Get a reference to the addthree_cdata R function
import rpy2.robjects as robjects
r_source = robjects.r["source"]
r_source("addthree.R")
addthree_cdata = robjects.r["addthree_cdata"]
# Create the pyarrow array we want to pass to R
import pyarrow
array = pyarrow.array((1, 2, 3))
# Import the pyarrow module that provides access to the C Data interface
from pyarrow.cffi import ffi as arrow_c
# Allocate structures where we will export the Array data
# and the Array schema. They will be released when we exit the with block.
with arrow_c.new("struct ArrowArray*") as c_array, \
arrow_c.new("struct ArrowSchema*") as c_schema:
# Get the references to the C Data structures.
c_array_ptr = int(arrow_c.cast("uintptr_t", c_array))
c_schema_ptr = int(arrow_c.cast("uintptr_t", c_schema))
# Export the Array and its schema to the C Data structures.
array._export_to_c(c_array_ptr)
array.type._export_to_c(c_schema_ptr)
# Invoke the R addthree_cdata function passing the references
# to the array and schema C Data structures.
# Those references are passed as strings as R doesn't have
# native support for 64bit integers, so the integers are
# converted to their string representation for R to convert it back.
r_result_array = addthree_cdata(str(c_array_ptr), str(c_schema_ptr))
# r_result will be an Environment variable that contains the
# arrow Array built from R as the return value of addthree.
# To make it available as a Python pyarrow array we need to export
# it as a C Data structure invoking the Array$export_to_c R method
r_result_array["export_to_c"](str(c_array_ptr), str(c_schema_ptr))
# Once the returned array is exported to a C Data infrastructure
# we can import it back into pyarrow using Array._import_from_c
py_array = pyarrow.Array._import_from_c(c_array_ptr, c_schema_ptr)
print("RESULT", py_array)
运行更改后的 addthree.py 现在将打印出将 3 加到 pyarrow.array((1, 2, 3)) 数组所有元素后生成的数组
$ python addthree.py
R[write to console]: Attaching package: ‘arrow’
RESULT [
4,
5,
6
]