December 4, 2024

Python中调用R函数

版权声明:本文为博主原创文章,转载请注明原文出处!

作者:阿振

写作时间:2024-12-04 15:38:19


基本思路

在某些时候有些算法只有R实现,但是我们先统一用Python脚本进行处理的时候,可以选择在Python中调用R。通过rpy2可以很容易实现在Python中对R的调用,我们可以通过 pipconda进行 rpy2的安装。

1
pip install rpy2

在Python脚本中导入必要的 rpy2函数和需要的R库,

1
2
3
4
import rpy2.robjects as ro
from rpy2.robjects.packages import importr

stats = importr("stats")

由于做科学计算的时候,经常用的的是多维Array和DataFrame对象,我们可以激活 numpypandas和R中对应对象的

Python对象转为R对象

rpy2提供了一系列的 Vector对象用于将Python中的 list转为R中的 vector,例如
ro.FloatVector(data[column])
pd.DataFrame对象 data中的 column列转为一个 FloatVector对象,这个对象包含了R能够理解的信息,可以直接传递给R当作 vecor用。
当然,我们还有 IntVectorStrVector等多个 Vector,甚至还可以将Python中的 dict转为 ListVector作为R中的 list使用。

如果是Python中的 np.ndarray对象,我们则可以通过 localconverter转为R兼容的 Array对象。

1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np
from rpy2.robjects.conversion import localconverter
from rpy2.robjects import numpy2ri, r

# 创建Python多维数组
np_array = np.array([1.5, 2.5, 3.5])
# 使用localconverter自动转为R兼容的array
with localconverter(numpy2ri.converter):
r_array = numpy2ri.py2rpy(np_array)

# 调用R的is.array()函数检查一下是否是R中的array
print(r("is.array")(r_array))

如果是Python中的 pd.DataFrame对象,我们可以通过 localconverter将其转为R兼容的 data.frame对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
from rpy2.robjects import pandas2ri, r
from rpy2.robjects.conversion import localconverter

# 这里是Python中的DataFrame
df = pd.DataFrame({
"col1": [1, 2, 3],
"col2": ["A", "B", "C"]
})

# 通过localconverter将其转为R中的data.frame
with localconverter(pandas2ri.converter):
r_df = pandas2ri.py2rpy(df)

# 调用R的class()函数检查一下是否是R中的data.frame
print(r("class")(r_df))
# 返回['data.frame']则说明是R中的data.frame

调用R函数

其实大家已经看到了,在Python中调用R函数可以把R对象实例当作Python字典,方法当作键值,后面跟括号,括号中是函数参数,进行调用。上面判断Python对象是否是R兼容的 arraydata.frame对象就是使用的这种方式。但是这种方式看起来不是很像正常的函数调用,当然,在 rpy2中提供了多种调用或者执行R函数的方法,这里只介绍最Pythonic的方法。

函数调用在导入了包之后,可以直接跟普通Python函数一样使用,但是注意函数传递的参数需要是对应R对象的参数。下面是调用 stats包中的 ts()函数生成一个 ts时序对象:

1
2
3
4
5
series = stats.ts(
data=ro.FloatVector(data[column]),
frequency=73,
start=ro.FloatVector([2017, 1])
)

ts()函数的三个参数,在R版本中第一个是 vector,第二个是一个数字字面量(当然,R中没有纯粹的scalar类型),第三个是 vector
这样的R函数调用是不是更加看起来像Python的正常函数调用?除了你需要把函数的参数转为R兼容的类型之外,没有什么区别。

函数结果转为Python对象

我们调用了R的算法函数进行统计计算,若要进行后续的处理或者结果的可视化,想将结果转为Python的数据对象。很多时候结果模型结果对象一半以R中的 list表示,要拿到 list中的数据,rpy2提供了 rx()rx2()方法对其进行操作。rx()和相当于R中的”[“操作,而 rx2()相当于”[[“操作。此外,我们还可以通过name属性获得列表中个个元素名称。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import rpy2.robjects as ro

result = ro.r('list(a = "Hello", b = c(1, 2, 3, 4), c = matrix(1: 15, nrow=5))')

# 结果为[1] "a" "b" "c"
print(result.name)

# 结果为:
# $a
# [1] "Hello"
print(result.rx('a'))
# 结果为:
# [1] "Hello"
print(result.rx2('a'))

# 结果为:
# [,1] [,2] [,3]
# [1,] 1 6 11
# [2,] 2 7 12
# [3,] 3 8 13
# [4,] 4 9 14
# [5,] 5 10 15
print(result.rx2('c'))

踩坑记录

我在写代码实现过程中,参考网上教程,进行了 numpypandas对象的自动转换的激活,如下所示。这样子R对象在可以转化的情况下,会自动 numpypandas对象,方便Python程序进一步处理。

1
2
3
4
5
from rpy2.robjects import numpy2ri
from rpy2.robjects import pandas2ri

numpy2ri.activate()
pandas2ri.activate()

这种转化是全局进行的,如果局部不需要自动转换,则需要进行特殊处理。如下所示,设置使用 default_converter来避免自动转换。

1
2
3
4
5
6
with localconverter(default_converter):
series = stats.ts(
data=ro.FloatVector(data[column]),
frequency=73, # Five-day frequency
start=ro.FloatVector([2017, 1]) # Start year and period
)

在我使用的3.5.16版本中,已经不推荐这种全局自动转换了,所以我们今后在需要的转换的时候可以使用 localconverter进行手动转换,就像上面的案例中进行的操作。