前言
Python编程灵活方便,R的模型方法众多,如何将两者结合起来,发挥更大的作用,值得探索。Python可以直接调用R的函数,R是开源项目,肯定会有一些第三方库实现Python与R互通。需要在python中调用R,实在是一种无奈的选择。如果能在一门语言中独立完成一个项目(或数据挖掘任务),是一个比较理想的做法。但是,这种想法也不太现实,毕竟每一种语言都有自己的长处。如果能取长补短,综合使用各种语言,也能起到不错的效果。
现在遇到的问题是,如何在python中调用R?这其中包括了如何调用R的对象(函数和包),R和python的对象如何互相转换,以及如何调用R的脚本(外界参数的传入)。python提供了一个模块rpy2,可以较好地完成这项工作。使用第三方库rpy2包可以实现使用python读取R的对象、调用R的方法以及Python与R数据结构转换等。实际上除了Python,其他语言与R互通的第三方包也大大的有。
下面就详细介绍一下,rpy2包的使用方法,实现R与Python共舞。
目录
模块 rpy2.robjects 是rpy2对R的一个高级封装,该模块里包含了一个r对象和一系列的R数据结构。使用rpy2的大多数情况,只需要跟这个模块打交道即可。
r实例是指rpy2.robjects.r,它是在Python中的嵌入式R进程,把r当作从python走向R的通道来看就可以了。通过r实例,我们可以读取R的内置变量、调用R的函数、甚至直接把它当作R的解析器来用。下面就直接体验一下R如何与Python无缝整合吧。
-
在R的命令行中,我们直接输入对象名来访问R的内置对象(在R控制台中访问R对象),如pi、letters、cars:
> pi [1] 3.141593 > letters [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" > head(cars) speed dist 1 4 2 2 4 10 3 7 4 4 7 22 5 8 16 6 9 10 >
-
在python中访问R对象,使用r实例,python访问R对象也很简单,而且方法很多,如下:
##### r实例 ##### from rpy2 import robjects r = robjects.r ##### r实例 ##### print(r[\'pi\']) print(r(\'pi\')) print(r.pi)
### 代码运行 ### >>> >>> from rpy2 import robjects >>> r = robjects.r >>> print(r[\'pi\']) [1] 3.141593 >>> print(r(\'pi\')) [1] 3.141593 >>> print(r.pi) [1] 3.141593 >>>
在这段代码中,我们用了三种方式来访问R对象,把r实例当作字典,把r实例当作方法,把r实例当作一个类对象。在实际中,使用哪一种方式要因习惯而异,我喜欢的方法是使用第三种,把r实例当作自己人,直接使用“.”来访问R对象。但这种方法有一个缺陷,就是不能访问带名字空间的R对象或函数,而其他两种方式是可以的,这点将在随后说明。
详细示例展示:
==========================
# creat an R function
>>> r(
\'\'\'
f <- function(r){pi * r}
\'\'\'
)
>>> r[\'f\'](3)
[9.424778]
# internal function in R
>>> r[\'ls\']()
# another internal function
>>> l = r[\'letters\']
>>> len(l)
>>> r[\'paste\'](l, collapse = \'-\')
# an alternative way of getting \'paste\' function in R
# eval the R code
>>> coder = \'paste(%s, collapse = "-")\' % (l.r_repr())
>>> r(coder)
==========================
对于一些特殊的R对象比如list和matrix,如果python要调去其中的部分数据,可以通过其rx()和rx2()方法操作。对于list,可以查看其name属性,以获得列表个个元素名称。rx()和相当于"["操作(注意取出的是R的list对象),而rx2()相当于"[["操作。一个例子:
==========================
>>> 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
<StrVector - Python:0x8afdc8c / R:0x8ce0a70>
[\'a\', \'b\']
>>> 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.rx(1)
<ListVector - Python:0x8b010cc / R:0x8cf7100>
[Matrix]
a: <class \'rpy2.robjects.vectors.Matrix\'>
<Matrix - Python:0x8b017cc / R:0x97de388>
[ 1, 2, 3, ..., 8, 9, 10]
>>> tmp.rx2(1)
<Matrix - Python:0x8b01b4c / R:0x97de388>
[ 1, 2, 3, ..., 8, 9, 10]
>>> tmp.rx2(\'a\').rx(1, 1) # first element of \'a\'
<IntVector - Python:0x8b01acc / R:0x8cf6fa0>
[ 1]
>>> tmp.rx2(\'a\').rx(1, True) # first row of \'a\'
<IntVector - Python:0x8b01f2c / R:0x965ffd8>
[ 1, 3, 5, 7, 9]
==========================
注意事项:
-
1.如果函数有警告(warnings),在ipython等IDE上能够执行,但是如果是脚本或者与网页服务器交互,则会产生错误,解决办法如下:
- 1.鲁莽的解决很简单,强行忽略R的警告,options(warn = -1)或者R代码放入函数中suppressWarnings()。
- 2.第二种办法,如果是自己代码中使用了warning()函数,则将warning信息换成字符串,之后单独输出。
-
2.如果R的函数参数用到向量,有两种解决办法:
-
1.使用robject.**Vector()函数(见下)先将python对象转换成R对象,然后带入函数;
-
2.直接使用python对象,一个例子:
========================== >>> a = r[\'matrix\'](range(10), nrow = 2) >>> print(a) [,1] [,2] [,3] [,4] [,5] [1,] 0 2 4 6 8 [2,] 1 3 5 7 9 ==========================
-
通过r实例,我们可以轻易地实现用Python调用R的函数。下面我们分别在R控制台和python命令行下读一个数据文件并画一张点图。
-
R控制台读取文件画点图
setwd("C:/Users/abdata/Desktop/") oPath <- "C:/Users/abdata/Desktop/" oName <- "result_test_win" # result_test_linux oType <- "csv" oPathFile <- paste(oPath,oName,\'.\',oType,sep="",collapse="") test_data <- read.csv(oPathFile,header=T) # 把文件读进一个数据框变量data中。 # head(test_data, 10) # names(test_data) plot_data <- test_data mtx <- data.matrix(plot_data) # 把data转变成矩阵 dotchart(mtx) # 用矩阵的数据画点图
-
python控制台读取文件画点图
接下来用python来做一遍同样的事情,我们之前了解到,使用r实例可以直接访问R对象,还可 以直接调用R的函数,其实在Python看来,对象和函数是相同的东西,函数也是一种对象罢了。现在来试一下调用”read.table()”函数读入一 个数据文件data.csv:
# 在 python控制台 运行代码: data = r.read.csv(\'C:/Users/abdata/Desktop/result_test_win.csv\')
>>> data = r.read.csv(\'C:/Users/abdata/Desktop/result_test_win.csv\') Traceback (most recent call last): File "C:\Program Files\Anaconda3\lib\site-packages\rpy2\robjects\__init__.py", line 336, in __getattribute__ return self.__getitem__(attr) File "C:\Program Files\Anaconda3\lib\site-packages\rpy2\robjects\__init__.py", line 341, in __getitem__ res = _globalenv.get(item) LookupError: \'read\' not found During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Program Files\Anaconda3\lib\site-packages\rpy2\robjects\__init__.py", line 338, in __getattribute__ raise AttributeError(orig_ae) AttributeError: \'R\' object has no attribute \'read\' >>>
出错了!在上面我提到过了,使用“.”引用的方式不能访问带有名字空间的R对象和函数,read.table 是表示在read包下面的table函数,通过“.”的形式调用失败,必须要用字典的方式或参数的方式来获得:
plot_data = r[\'read.csv\'](\'C:/Users/abdata/Desktop/result_test_win.csv\') print(r.head(plot_data)) mtx = r[\'data.matrix\'](plot_data) r.setwd("C:/Users/abdata/Desktop/") # r.jpeg(file="myplot.jpeg") r.png(file="myplot.png", bg="transparent") # r.pdf(file="myplot.pdf") r.dotchart(mtx) r[\'dev.off\']()
这段代码得到的结果与在R控制台下画点图的效果是一样的。代码 r.dotchart(mtx) 是直接通过“.”来调用R的函数dotchart的,在没有名实空间的情况下,是正常的。如果你为了避免太多不可控制的出错机会,你可以统一地使用字典的方式来访问R对象和方法,这是最保险的方法,但看起来有点别扭。
-
如何载入和使用R包
使用rpy2.robjects.packages.importr对象,调用方法是
========================== >>> 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" ==========================
-
r实例就是一R控制台
其实r实例就是一个可交互的R控制台,只不过交互对象是Python与R罢了,为了证明r实例具有R控制台的特性,来做个实验,写一串R脚本,作为Python一个字符串变量的内容,把该字符串传给r实例,然后把r实例当作方法来调用:
rscript = \'\'\' setwd("C:/Users/abdata/Desktop/") oPath <- "C:/Users/abdata/Desktop/" oName <- "result_test_win" # result_test_linux oType <- "csv" oPathFile <- paste(oPath,oName,\'.\',oType,sep="",collapse="") plot_data <- read.csv(oPathFile,header=T) mtx <- data.matrix(plot_data) # jpeg(file="myplot.jpeg") # png(file="myplot.png", bg="transparent") pdf(file="myplot.pdf") dotchart(mtx) dev.off() \'\'\' r(rscript)
rscript1 = \'\'\' setwd("C:/Users/abdata/Desktop/") jpeg(file="myplot.jpeg") par(mar=c(1,2,2,1), mfrow = c(2,2)) x <- rnorm(1000,0,1) plot(x, main="Random data scatter plot", ylab="X", xlab="N") hist(x,prob=T,col=gray(.9), main="Random data bar graph", ylab="X", xlab="N") # hist(x1,breaks=20,main="Random data bar graph", ylab="X", xlab="N") lines(density(x, bw=.8), col="black", lwd=1.2) curve(dnorm(x,0,1), from=-4, to=4, main="Density Function Curve", ylab="f(X)", xlab="X") curve(pnorm(x,0,1), from=-4, to=4, main="Distribution Function Curve", ylab="f(X)", xlab="X") par(mfrow=c(1,1)) dev.off() \'\'\' print(r(rscript1))
rscript2 = \'\'\' # 随机数生成 sample_size=10 mean_value=0 sd_value=1 decimal_places=4 result <- round(rnorm(sample_size, mean_value, sd_value),decimal_places) result <- paste0(result,sep="",collapse="|") return(result) \'\'\' data=r(rscript2) print(data[0])
注意:把r实例当作控制台,只能够通过r(r代码)的方式来使用r实例,字典的方式行不通。
在实际应用中,使用R语言来编写自己的函数同样是不可避免的,在R控制台中,可以使用 source(‘script_path’) 的方法来加载自定义R脚本。而在Python环境中使用自己义R脚本中的函数也同样方便:使用 r.source(‘script_path’) 即可把自定义函数加载到全局环境中,再使用r.自定义方法名就可以实现调用。
注意事项:转换R对象为全局变量。【因为使用函数robjects.globalenv()将对象转换成全局变量,特别是遇到python找不到一个R对象时(此时R对象可能通过r(\'Rcode\')调用),留意将R对象转变成全局变量。】
下面就详细介绍我在项目实践过程中,利用python加载r自定义函数的实例:
############## python_R_rpy2.py ##############
# -*- coding:utf-8 -*-
# objection: try to use R-script in python
# @author: baorui
# time: 2017.02.03
# python version: 3.5
# help(model)
# r实例
from rpy2 import robjects
r = robjects.r
# 调用R-script --- r.source(\'path/R-script.R\')
r.source(\'/opt/test/R-script_1.R\')
r.source(\'/opt/test/R-script_2.R\')
# 使用R-script
### 调用自定义Test函数
r.Test()
### 调用自定义Test1函数
r.Test1()
result_test1 = r.Test1(4)
print(result_test1)
print(result_test1[0])
### 调用自定义fun_normal函数
sample_size=10
mean_value=0
sd_value=1
decimal_places=4
result_normal = r.fun_normal(sample_size, mean_value, sd_value, decimal_places)
### 调用多元无序Logit模型
print(r(\'mlog_F\'))
print(r(\'result\'))
############## python_R_rpy2.py ##############
############## R-script_1.py ##############
# try to use R-script in python---here is the R-script-defined function
# @author: baorui
# time: 2017.02.04
# R version: 3.2.0
# help(function_name)---function help document
# help(package="package_name")---package help document
# function_name--function code
# Test
Test <- function(){
print(\'-------hello world--------\')
print(pi)
}
# Test1
Test1 <- function(a=4){
# print(\'this is a self-defined function with arg\')
# print(\'result is arg*2\')
result = a*2
## here should be NOTICE: must be return \'result\'. must not return (a*2).
## if do, it will error: arg would not be used
return(result)
}
# normal
fun_normal <- function(sample_size, mean_value, sd_value, decimal_places){
sample_size=as.numeric(sample_size)
mean_value=as.numeric(mean_value)
sd_value=as.numeric(sd_value)
decimal_places=as.numeric(decimal_places)
result <- round(rnorm(sample_size, mean_value, sd_value),decimal_places)
result <- paste0(result,sep="",collapse="|")
return(result)
}
############## R-script_1.py ##############
############## R-script_2.py ##############
# try to use R-script in python---here is the R-script-defined function
# @author: baorui
# time: 2017.02.04
# R version: 3.2.0
# 数据
# 多元无序Logit模型的估计一般也是采用最大似然法,R中估计多元无序Logit模型使用mlogit包中的mlogit()函数。
# install.packages("mlogit")
options(warn = -1)
library(mlogit)
data("Fishing", package="mlogit")
dim(Fishing)
head(Fishing)
class(Fishing)
# 多元无序Logit模型
# 在使用mlogit之前,需要把数据用mlogit.data()转换成合适mlogit()函数分析的格式。
Fish <- mlogit.data(Fishing, choice=\'mode\', shape = \'wide\', varying = c(2:9))
dim(Fish)
head(Fish)
class(Fish)
typeof(Fish)
mode(Fish)
# mlogit()函数应用及展示拟合模型的详细结果
mlog_F <- mlogit(mode ~ price + catch, data=Fish)
print(mlog_F)
print(summary(mlog_F))
result <- summary(mlog_F)
############## R-script_2.py ##############
向量(Vector)是R的一个最重要的也是最常用的数据类型,可以理解为一个二维数据,对应Python的list。在R控制台中,声明一个变 量:“x <- 1”,X会被声明成一个向量,而其第一个值是1。R常常用c()函数来声创建一个由多个值组成的向量,例如c(1,2,3,4)。Python要与R打交道,除了访问R对象和调用R函数,还有就是要学会如何转换常见的数据类型。
通常,可以将python的list对象,转换成为R的vector对象【robjects.ListVector()将python的字典转换成R的列表】,之后直接使用R函数调用。rpy2提供了几个类,供我们把Python的list转换成R的Vector。
相应的函数,如下:
robjects.StrVector()
robjects.IntVector()
robjects.FloatVector()
robjects.complexVector()
robjects.FactorVector()
robjects.BoolVector()
robjects.ListVector()
这些函数将python列表转化成R的数据类型分别为:
字符
整数
浮点
复数
因子
布尔向量
列表
更多具体转换可见,官网文档-类型转换。
-
python对象转换成R对象
========================== >>> >>> testmatrix = robjects.IntVector([1, 2, 3, 4]) >>> robjects.r[\'matrix\'](testmatrix, nrow = 2) # another dynamic arguments example >>> x = robjects.IntVector(range(10)) >>> y = robjects.r.rnorm(10) >>> kwargs = {\'ylab\': \'foo/bar\', \'type\': \'b\', \'col\': \'blue\', \'log\': \'x\'} >>> robjects.r.plot(*args, **kwargs) >>> ========================== ========================== # 以IntVector为例,将Python的list转换成R的Vector:robjects.IntVector([1,2,3,4,5])。 # 下面来使用刚学到的类型转换知识画上一个例子的散点图来结束此次体验: x = [1,2,3,4,5,6] r.plot(robjects.IntVector(x)) ==========================
注意事项:使用vector系列函数时,输入的只能是python的列表,而不能是数字或者字符串。
-
R对象转换成python对象
推荐使用tuple()或者list()函数,将R对象转换成tuple或者list。
==========================
>>> a = r(\'c(1, 2, 3)\')
>>> print(a)
[1] 1 2 3
>>> str(a)
[1] 1 2 3
>>> tuple(a)
(1.0, 2.0, 3.0)
>>> list(a)
[1.0, 2.0, 3.0]
>>> b = r(\'matrix(1:6, 2, 3)\')
>>> b
[1,2,3,4,5,6]
>>> 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]
==========================
rpy2提供的不仅仅是上面这些,上面的知识只是rpy2所提供的20%,但是已经足以解决80%的问题。rpy2还提供了更低级的API,你可以做更多的事情,例如你可以实现另一个robjects对象来支持使用“.”来访问带名字空间的对象和函数。更多的知识,请移步官方文档。