整合 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 函數#
將 Python 函數公開給 R 可以透過 reticulate
程式庫來完成。 例如,如果我們想要從 R 在 R 中建立的陣列上呼叫 pyarrow.compute.add()
,我們可以透過 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
函數包裝在一個函數中,該函數執行從 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
的 Array,將其匯出到 C Data 介面,然後將匯出的參考傳遞給 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
現在將印出 Array,此 Array 是將 3
加到原始 pyarrow.array((1, 2, 3))
陣列的所有元素所產生的結果
$ python addthree.py
R[write to console]: Attaching package: ‘arrow’
RESULT [
4,
5,
6
]