将 PyArrow 与 R 集成#
Arrow 支持通过 Arrow C 数据接口 在同一进程内交换数据。
这可用于在 Python 和 R 函数与方法之间交换数据,从而使两种语言能够交互,而无需任何数据编组和解组的开销。
注意
本文假定您已正确安装了 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 运行时。
rpy2 可以像大多数 Python 库一样使用 pip 安装
$ 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 模块来实现,该模块为 Arrow 类型实现了 rpy2 支持。
rpy2-arrow 可以通过 pip 安装
$ pip install rpy2-arrow
rpy2-arrow 实现了从 PyArrow 对象到 R Arrow 对象的转换器,这不会产生任何数据复制开销,因为它依赖于 C 数据接口。
要将 PyArrow 数组传递给 addthree 函数,我们的 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 调用 pyarrow.compute.add() 在 R 中创建的数组上,我们可以通过 reticulate 在 R 中导入 pyarrow 来实现。
一个基本的 addthree.R 脚本,调用 add 将 3 添加到 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 函数包装在一个函数中,该函数执行将 Arrow 数组从 C 数据接口导入 R 所需的额外工作。
这项工作将由 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
]