需要在python中调用R,实在是一种无奈的选择。如果能在一门语言中独立完成课题,是一个比较理想的做法。但是,这种想法也不太现实,毕竟每一种语言都有自己的长处。如果能取长补短,综合使用各种语言,也能起到不错的效果。
现在遇到的问题是,如何在python中调用R?这其中包括了如何调用R的对象(函数和包),R和python的对象如何互相转换,以及如何调用R的脚本(外界参数的输入)。python提供了一个模块rpy2 ,可以较好地完成这项工作。rpy2只是提供了一个Python调用R的接口,因此也直接继承了所有R的缺点。一个有意思的项目是renjin,一个基于JVM的R语言解释器。
本文着重记录一些使用过程中的注意事项和小技巧,如有不一致则以官方文档为准。
rpy2作为Python的一个模块,其安装非常方便。需要做的准备工作是在本地安装好R。
$ pip install rpy2
rpy2.robjects
在rpy2中调用R对象,实际上是开启了一个R的交互进程。主要思路是将R的代码写入一个字符串内,之后执行即可。
对于R代码,将一段R代码写成一行,尽管看起来很丑陋而且不推荐,一样可以执行。但是,反过来,对于Python代码则没有这么简单。因为,Python代码是靠缩进来划分代码的区域,假若一段代码中有两个循环嵌套,如果将代码写成一行,执行起来就要麻烦的多(很可能要依赖空格的多少进行解释)。
调用方法:
from rpy2.robjects import r
有三种方式可以选择:
使用r.obj
,比如 r.c(1, 3)
。这种方法虽然方便,但是对于名称中有“点号”的函数会出问题,比如 data.frame
或者 read.csv
等。
使用r['obj']
,比如 r['c'](1, 3)
。这种方法几乎可以调用任何R的函数,而且写法与原始调用很相似,无非是r['func'](value1, para2 = value2)
。如果一个R函数中的变量名是有“点号”的,不能直接赋值,需要构建一个字典形式,比如 r['func'](value1, para2 = value, **{para.3: value3})
。
使用r('obj')
,比如 r('c(1, 3)')
。这种方法从某种程度上讲是万能的,因为总是可以将任意长度的R代码写成一个Python字符串,之后通过r('Rcode')
调用执行。
# import r
>>> from rpy2.robjects import r
# creat an R function
>>> r('''f <- function(r){pi * r}''')
>>> r.f(3)
[9.424778]
# internal function in R
>>> r['ls']()
# two ways of getting 'paste' function in R
# first: direct use R function
>>> print(r['paste'](l, collapse = '-'))
# second: eavl R codes
>>> coder = 'paste(%s, collapse = "-")' % (l.r_repr())
>>> print(r(coder))
# call Python function
>>> l = r['letters']
>>> len(l)
26
>>> dir(l)
创建向量,可以使用rpy2.robjects.vectors
中提供的一系列函数,将一个Python的元组、列表或者字符串转换为R的向量。其中包括 StrVector()
、IntVector()
、FloatVector()
、FactorVector()
和BoolVector()
,分别提供了转换字符、整数、浮点、因子和布尔向量。
创建列表,可以使用ListVector()
将Python的字典转换为R的列表。
创建矩阵和数据框(data.frame)建议直接使用R函数matrix()
和data.frame()
。尽管如此,rpy2提供了 DataFram()
将Python的字典转换成R的数据框(列顺序可能与输入不一致,Python字典特性造成),注意会把字符串自动转换成因子。
以上这些构建的R对象,有一系列的属性和方法,比如names
,可以访问和赋值。
>>> from rpy2.robjects import r
>>> from rpy2.robjects.vectors import StrVector, IntVector, ListVector, DataFrame
# build R vector
>>> testVec = IntVector([1, 2, 3])
>>> testVec = StrVector(('a', 'nice', 'day'))
>>> testVec = StrVector('abc')
>>> testVec.names = StrVector(('name1', 'name2', 'name3'))
>>> dir(testVec)
# build List with names
>>> testList = ListVector({'a': 1, 'nice': 2, 'day': 3})
>>> testList.names
# build matrix
>>> m = r.matrix(IntVector([1, 3, 8, 6]), nrow = 2)
>>> m = r.matrix(range(10), nrow = 5)
# build data.frame
# use R function, string vector are automatically transformed as factors
>>> dataf = r['data.frame'](S = StrVector(['x', 'y', 'z']), F = StrVector('acb'))
>>> dataf
<DataFrame - Python:0x7f072235f7e8 / R:0x19fb250>
[FactorVector, FactorVector]
S: <class 'rpy2.robjects.vectors.FactorVector'>
<FactorVector - Python:0x7f072235f518 / R:0x1978fa8>
[ 1, 2, 3]
F: <class 'rpy2.robjects.vectors.FactorVector'>
<FactorVector - Python:0x7f072235f440 / R:0x18e9c20>
[ 1, 3, 2]
# use string vector "as it is"
>>> datav = DataFrame({'S': r.I(StrVector(['x', 'y', 'z'])), 'F': StrVector('acb')})
>>> datav
<DataFrame - Python:0x7f072235f638 / R:0x1155ff8>
[StrVector, FactorVector]
S: <class 'rpy2.robjects.vectors.StrVector'>
<StrVector - Python:0x7f07222d95f0 / R:0x23b0168>
[str, str, str]
F: <class 'rpy2.robjects.vectors.FactorVector'>
<FactorVector - Python:0x7f07222d9488 / R:0x10e7e70>
[ 1, 3, 2]
在rpy2中构建的对象,可以使用名字、索引或者布尔值进行提取或者修改,注意Python从0开始索引,而R从1开始。R对象的提供了方法rx()
相当于R的 [
操作,而rx2()
相当于[[
操作。
>>> from rpy2.robjects import r
>>> from rpy2.robjects.vectors import StrVector, IntVector, BoolVector, ListVector, DataFrame
# vector
>>> testVec = IntVector([1, 2, 3])
>>> testVec.names[0]
>>> testVec.names = StrVector(('name1', 'name2', 'name3'))
# assign values
>>> testVec[0] = 20
>>> testVec[1: 3] = IntVector([100, 101])
>>> testVec[testVec.names.index('name2')] = 10
# using the rx() method
>>> testVec.rx('name2')
>>> testVec.rx(3)
>>> testVec.rx(-1)
>>> testVec.rx(IntVector([-1, -3]))
>>> testVec.rx(BoolVector([False, True, False]))
# empty vector returns
>>> testVec.rx(0)
# error because R cannot determine the last element
>>> testVec[1: ] = IntVector([100, 101])
# error because rx() is a method
>>> testVec.rx(IntVector([1, 3])) = IntVector([100, 101])
# list and matrix
>>> tmp = r("list(a = matrix(1:10, nrow = 2), b = 'Hello')")
>>> print tmp
$a
[,1] [,2] [,3] [,4] [,5]
[1,] 1 3 5 7 9
[2,] 2 4 6 8 10
$b
[1] "Hello"
>>> tmp.names
>>> tmp.names[1]
>>> tmp.rx('a')
<ListVector - Python:0x8afd86c / R:0x8cf71c0>
[Matrix]
a: <class 'rpy2.robjects.vectors.Matrix'>
<Matrix - Python:0x8b013cc / R:0x97de388>
[ 1, 2, 3, ..., 8, 9, 10]
>>> tmp.rx2('a')
# same as the former one
>>> tmp.rx(1)
>>> tmp.rx2(1)
<Matrix - Python:0x8b01b4c / R:0x97de388>
[ 1, 2, 3, ..., 8, 9, 10]
>>> tmp[tmp.names.index('b')] = 9
# operate matrix
>>> tmpMat = tmp.rx2('a')
# first element of 'a'
>>> tmpMat.rx(1, 1)
# first row of 'a'
>>> tmpMat.rx(1, True)
>>> tmpMat.rx
>>> tmpMat.colnames = r['letters'][1:6]
>>> tmpMat.rx(True, IntVector([1, 3]))
>>> tmpMat.rx(True, BoolVector((True, False, True, False, False)))
>>> tmpMat.rx(True, 'b')
rpy2的R对象拥有ro
属性,可以用于向量式操作。
>>> from rpy2.robjects import r
>>> from rpy2.robjects.vectors import IntVector
>>> tmp1 = IntVector([1, 2, 3])
>>> tmp2 = IntVector([1, 2, 0])
>>> tmp1.rx(tmp2.ro > 0)
R object with classes: ('integer',) mapped to:
<IntVector - Python:0x7f304047b248 / R:0x4cffa08>
[ 1, 2]
R中的NA
通过rpy2.robjects.NA_Integer
或rpy2.robjects.NA_Character
等引用。
全局变量使用rpy2.robjects.globalenv
。特别是遇到Python找不到一个R对象时(此时R对象可能通过r('Rcode')
调用),留意将R对象转变成全局变量。
>>> from rpy2.robjects import r
>>> from rpy2.robjects.vectors import StrVector
>>> from rpy2.robjects import globalenv, NA_Character
# test string NA
>>> testNA = StrVector(['a', 'b', 'c'])
>>> testNA[1] = NA_Character
# test global objects
>>> testGlobal = StrVector(['a', 'b', 'c'])
>>> globalenv['testGlobal'] = testGlobal
>>> connectVec = r("paste(testGlobal, collapse = '-')")
使用rpy2.robjects.packages.importr
调用R包:
>>> from rpy2.robjects.packages import importr
>>> base = importr('base')
>>> stats = importr('stats')
>>> affy = importr('affy')
>>> stats.rnorm(10)
如果想引用一个包中的隐变量,也很简单,只要载入包,然后所有r命令化成成字符串,之后引用即可(这种方法是万能的),比如:
>>> from rpy2.robjects.packages import importr
>>> importr('hwriter')
>>> a = r('hwriter:::hwrite.table(matrix(1:10, 2))')
>>> print(a)
[1] "<table border=\"1\">\n<tr>\n<td>1</td><td>3</td><td>5</td><td>7</td><td>9</td></tr>\n<tr>\n<td>2</td><td>4</td><td>6</td><td>8</td><td>10</td></tr>\n</table>\n"
推荐使用tuple()
或者list()
函数,将R对象转换成Python的元组或者列表对象。
>>> a = r('c(1, 2, 3)')
>>> str(a)
'[1] 1 2 3\n'
>>> tuple(a)
(1.0, 2.0, 3.0)
>>> list(a)
[1.0, 2.0, 3.0]
>>> b = r('matrix(1:6, 2, 3)')
>>> print b
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
>>> tuple(b)
(1, 2, 3, 4, 5, 6)
>>> list(b)
[1, 2, 3, 4, 5, 6]
分清楚需要调用的R还是Python的对象;
善于使用构建好的rpy2对象的属性和方法,比如rpy2Obj.len
;
如果函数有警告(warnings),在ipython等IDE上能够执行,但是如果是脚本或者与网页服务器交互,则会产生错误。解决办法:
鲁莽的解决很简单,强行忽略R的警告,options(warn = -1)
或者R代码放入函数中suppressWarnings()
。
如果是自己代码中使用了R的 warning()
函数,则将warning信息换成字符串,之后单独输出。
2016年6月16日