整合 PyArrow 與 R#

Arrow 支援在同一程序中透過 Arrow C 資料介面 交換資料。

這可以用於在 Python 和 R 函數與方法之間交換資料,讓這兩種語言可以互動,而不會產生封送處理和解除封送處理資料的成本。

注意

本文假設您已正確安裝 pyarrowPython 環境,以及正確安裝 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 檔案,該檔案使用 rpy2addthree.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 腳本,呼叫 add3 加到 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_cdataaddthree 函數

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_sschema_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
]