Java-呼叫R語言和呼叫Python(前後端展示)

Tttori 發表於 2022-06-18
Python Java

1. 背景

  1. R語言和Python用於資料分析和資料處理,並生成相應的直方圖和散點圖
  2. 需要實現一個展示平臺,後端使用Java,分別呼叫R語言和呼叫Python,並返回資料和圖給前端顯示
  3. 這個平臺主要實現多維度資料的特徵選擇,以及資料集協變數偏移(Covariate shift)的糾正的功能
  4. 本質就是一個Java呼叫R語言以及Java呼叫Python的Demo,做得很簡單,大神勿噴

2. 技術棧

  • Java 用的是 Springboot
  • R語言
  • Python
  • 前端用的是 Vue + ElementUI (前端只會點皮毛)
  • MySQL

3. Java呼叫R語言

3.1 R語言安裝Rserve伺服器

在這之前需要分別對Java和R做些準備,首先是R語言安裝Rserve伺服器
Java呼叫R語言時,Rserve需要啟動,可以通過CMD命令列 / RStudio 執行

# 安裝Rserve
install.packages("Rserve")
# 載入Rserve
library(Rserve())
# 啟動Rserve
Rserve()

這裡使用CMD命令列展示啟動Rserve,這樣完成了Java呼叫R語言的第一步

image


3.2 Springboot新增Rsession依賴

新增Rsession依賴之後就可以直接調包了

<dependency>
   <groupId>com.github.yannrichet</groupId>
   <artifactId>Rsession</artifactId>
   <version>1.8.3</version>
</dependency>

3.3 Java呼叫R常用命令

這裡演示一些我需求中Java呼叫R的一些方式,其中包括一些比較常用的方法
Java呼叫R的基本指令、R的圖片如何儲存並返回、R的結果如何獲取和過濾等

/**
* 這裡是Java呼叫R語言,R語言對多維度的資料進行特徵選擇,並將特徵選擇的結果返回,寫入MySQL
**/
public List<Map<String,String>> featureSelection(){
    RConnection c = null;// RConnection用於和Rserve建立連線
	try{
		c = new RConncetion();// 建立連線

		String RPath = "../featureSelection.R";// R檔案的地址

		c.assign("path",Rpath);// assign命令是將Rpath新增到R中,命名為path

		c.eval("source(path)");// eval命令是執行R命令,這裡則是執行source方法根據路徑載入R檔案

		String Dpath = fileMapper.selectFilePath("train",1);// 通過MySQL獲取資料集路徑

		String str = "rfProfile <- fsFunction('"+Dpath+"')";// R命令,執行我的R檔案中的方法

		c.eval(str);//執行

		// 出圖,因為是個Demo,圖片我就直接儲存在了本地,圖片以資料集名稱命名
		String fileName = fileMapper.selectFileName("train", 1);//檔名

		String imgPath = "D:/fileAndData/imgs/" + fileName + ".png";// 圖片儲存路徑

		c.assign("imgPath",imgPath);
		c.eval("png(imgPath)");// 使用R語言的png()方法儲存圖片
		c.assign("mainName",fileName);
		c.eval("print(plot(rfProfile,type='b',main=mainName))");// 想要出圖一定要套一個print(),不然會是空白
		c.eval("dev.off()");// 出圖這個也是必不可少,自行百度瞭解

		// 獲取特徵選擇的結果,結果使用String接收,需要通過正規表示式過濾一下我們需要的結果
		c.eval("features <- rfProfile$optVariables");
		// 獲取R的結果使用的是paste()以及capture.output()方法,相當於把輸出全捕獲過來了
		String feature = c.eval("paste(capture.output(features),collapse='\\n')").asString();
		// 獲取重要性得分
		c.eval("impt <- varImp(rfProfile)");
		String imptScores = c.eval("paste(capture.output(impt$Overall),collapse='\\n')").asString();

		// 寫了個工具類過濾R返回的結果,可以根據你的輸出結果去定義
		handlerRresults = new HandlerRresults();
		List<Map<String, String>> stringStringMapList = handlerRresults.catchAndHandlerR(feature, imptScores);
		fileMapper.deleteFileInfo(-1,"train");//-1 檔案已使用
		String featsStr = handlerRresults.getFeatsStr(feature);
		featMapper.insertFeat(featsStr);
		return stringStringMapList;
	} catch (RserveException | REXPMismatchException e) {
		e.printStackTrace();
	} finally {
		c.close(); // 一定要這一行!!!用完一定要關!!!
	}
	return null;
}

總結一個簡易的Java呼叫R的模板,R語言是按行執行的,無情eval()

public void JavaCallRDemo(){
	RConnection c = null;
	try{
		c = new RConnection();
		
		c.assign();//通過Java新增變數至R
		
		c.eval();//Java執行R命令
		
	} catch (RserveException | REXPMismatchException e) {
		e.printStackTrace();
	} finally {
		c.close();
	}
}

3.4 Java呼叫R的特徵選擇前端演示

我的資料集是30維的,結果選取了其中5個特徵(Best trade-off)
這裡將特徵及其對應的重要性得分通過表格的形式展示
圖片則是通過Base64轉碼的方式傳給前端

image


4 Java呼叫Python

4.1 Java呼叫Python程式碼部分

Java呼叫Python,我使用的是Process類並通過Runtime呼叫其他程式
Runtime可以呼叫cmd、shell等,這裡我以我的專案為例稍作演示

/**
* Java使用Runtime呼叫python
**/
public String callPy(){
	StringBuffer arr = new StringBuffer();// 用於獲取結果
	String basePath = "d://fileAndData/process/";// demo都是將檔案直接存本地了,圖方便
	
	// 以下為呼叫Python時傳遞的引數
	String featName = featMapper.getFeat();
	String trainPath = fileMapper.selectFilePath("train",-2);
	String ptrainPath = basePath + fileMapper.selectFileName("train",-2);
	String ptestPath = basePath + fileMapper.selectFileName("test",-2);
	
	Process proc; //宣告一下Process
	try{
		// 字串陣列儲存一下呼叫命令:1.使用python3 2.呼叫某個.py檔案 3-6.傳遞的引數
		String[] args = new String[]{"python3","../kmm.py",featName,trainPath,ptrainPath,ptestPath};
		
		proc = Runtime.getRuntime().exec(args);// 呼叫命令,cmd方式
		
		BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));// 得到輸入流
		
		String line = null;
		while((line = in.readLine())!=null){
			arr.append(line).append("\n");// 寫入
		}
		in.close();
		proc.waitFor();
	} catch (IOException | InterruptedException e) {
		e.printStackTrace();
	}
	return arr.toString();
}

由於在我這個Demo中,Python指令碼執行完成後的結果全是散點圖
我的做法是python直接把圖儲存本地,python執行完成後呼叫介面通過Base64格式傳給前端
後來發現其實也可以直接將返回的Base64格式的圖片丟給前端,不用那麼麻煩

/**
* 這裡是一個我用於獲取某個資料夾下所有檔案,並轉為Base64格式的方法
* 因為我資料夾下只會有圖片,我Demo也就只做了一個判空校驗,直接開幹
* Controller層
**/
public List<String> getPyFigsListBase64(HttpServletResponse response){
	String pyFilePath = "d://fileAndData/kmmImgs";// 圖片本地路徑
	
	List<String> res = new ArrayList<>();
	
	handlerPyresults = new HandlerPyresults();// 寫個了工具類
	
	List<File> pyFiles = handlerPyresults.getAllFile(pyFilePath);// 獲取所有檔案
	
	for(File file : pyFiles) {
		byte[] fig = handlerPyresults.file2Byte(file);// file型別轉為byte[]型別
		String base64str = Base64.encodeBase64String(fig);// byte[]轉為base64
		String img = "data:image/png;base64," + base64str;// 新增頭,告訴前端這是個圖片
		res.add(img);
	}
	return res;
}

/**
* file轉byte[]
**/
public byte[] file2Byte(File file){
        if(file == null){
            return null;
        }
        FileInputStream fileInputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] b = new byte[1024];
            int n;
            while ((n = fileInputStream.read(b))!=-1){
                byteArrayOutputStream.write(b,0,n);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileInputStream.close();
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

4.2 Java呼叫Python結果演示

我的python指令碼主要是對資料集使用了KMM演算法,是一種協變數偏移糾正的方法
通過散點圖反映測試集和訓練集之間的分佈情況和差異,這裡略...

image


5. 總結

這個專案是我碩士期間導師丟給我的一個需求,這裡說一下為什麼要用Java呼叫R語言和Python。

  1. 首先我有一個伽馬射線的二分類任務,通過R語言使用多個傳統機器學習模型實現。

  2. 在此之前使用R語言實現了多維度資料集的資料預處理、特徵選擇等功能,並且出圖方便,程式碼簡單。

  3. Python則實現了資料集協變數偏移糾正的功能,最終得到的資料集用於丟進模型做分類。

  4. 這個平臺通過呼叫R和Python,整合了資料集預處理、協變數偏移糾正的方法,並且可以通過多個圖視覺化看到分析結果。平臺還實現了資料集上傳、下載等功能...

  5. 主要是針對Java呼叫R語言以及呼叫Python作一個記錄,實際上平臺有許多細節都沒有顧慮到,相當於一個學習筆記吧。