前言
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對象來支持使用“.”來訪問帶名字空間的對象和函數。更多的知識,請移步官方文檔。