1. 程式人生 > >使用 Rserve 實現 R 程式的複雜資料返回

使用 Rserve 實現 R 程式的複雜資料返回

在 RCaller 無法返回複雜資料的研究 我們知道 RCaller 無法處理複雜資料的返回,那麼就看看其他工具吧,比如 Rserve。

使用下來的感覺是 Rserve 雖然能把值都給你,但是怎麼獲取卻是一個艱難的過程,比如和 RCaller 一樣,對矩陣值的讀取也是先列後行,定位到具體的值的變數上,API 顯然沒有與時俱進,不用泛型,使用古老的 Vector 型別,經常要強制轉型,要不是 debug, 我怎麼知道那個列是什麼資料型別啊。

private RList exeRMatchCode(PropensityMatchInput input, Map<String, List<String>> varResultMap,
			List<String> covairateNames) {
		logger.info("entering exeRMatchCode()");
		try {
			RConnection rc = new RConnection();
							
			for (String key : varResultMap.keySet()){
				logger.info("key is :: {}", key);
				List<String> varVals = varResultMap.get(key);
				String[] valStrs = new String[varVals.size()];
				int num = 0;
				for (String s : varVals){
					valStrs[num++] = s;
				}
				rc.assign(key, valStrs);
			}
			logger.info("各個變數值的陣列已經加入到了 R 的變數裡");
			
			StringBuffer sb = new StringBuffer();
			StringBuffer cbind = new StringBuffer();
			cbind.append("matrix <- cbind(").append(OUTCOME_VARIABLE).append(",")
				.append(StringUtils.join(covairateNames, ",")).append(")\n");
			sb.append(cbind.toString());
			
			sb.append("df <- as.data.frame(matrix)\n");			
			sb.append("library(MatchIt)\n");
			String replaceStr = input.isReplacement() ? "TRUE" : "FALSE";
			StringBuffer propensity = new StringBuffer();
			propensity.append("fm <- matchit(").append(OUTCOME_VARIABLE).append(" ~ ")
					.append(StringUtils.join(covairateNames, " + ")).append(", data = df, method = \"")
					.append(input.getAlgorithm().getValue()).append("\", caliper = ").append(input.getCaliper())
					.append(", replace = ").append(replaceStr).append(", ratio = ").append(input.getMatchRatio())
					.append(")\n");
			sb.append(propensity.toString());
			
			sb.append("result <- summary(fm)\n");				
			sb.append("sum <- result$sum.all\n");
			sb.append("mat <- result$sum.matched\n");
			sb.append("red <- result$reduction\n");
			sb.append("ss <- result$nn\n");
			sb.append("mData <- match.data(fm)[1]\n");
			sb.append("out <- list(sum, mat, red, ss, mData)\n");
			
			String code = sb.toString();
			logger.info("完整的 Match 演算法的 R 程式:: \n {}", code);
			
			REXP rexp = rc.eval(code);
			logger.info("完成 Propensity Score Match 匹配邏輯");
			
			return rexp.asList();
		} catch (Exception e) {
			logger.error(e.getMessage());
			throw new RuntimeException("執行 Propensity Score Match 匹配出錯了");
		}
	}

將變數值陣列傳入 Rserve 裡使用 assign 函式,對於獨立的語句比如 “library(MatchIt)” 也可以直接使用:

rc.eval("library(MatchIt)");
這裡我將多條語句放入快取池,然後一起 eval , 注意:需要在每一行末尾加上換行符,模擬命令列的執行。執行完,從 list 裡面取出各個物件:

logger.info("將 Map 物件裡的資料注入到 R 程式裡,同時執行 R 的匹配演算法");
RList result = exeRMatchCode(input, varResultMap, covairateNames);

logger.info("從 R 的匹配結果裡取出需要的資料,放入到 R 對應的 java 物件備用");
REXPGenericVector sumAllVector = (REXPGenericVector)result.elementAt(0);
REXPGenericVector matchedVector = (REXPGenericVector)result.elementAt(1);
REXPGenericVector balImproveVector = (REXPGenericVector)result.elementAt(2);
REXPDouble sampleSize = (REXPDouble)result.elementAt(3);
REXPGenericVector matchedPersonsVector = (REXPGenericVector)result.elementAt(4);

下面就挑其中一個來看看,怎麼獲取每個具體的值:

// 由於 R 對應的物件在取 R 矩陣的值,讀取是先列後行,所有資料處理比較特殊
private Collection<PropensitySum> extractMatchVectorData(REXPGenericVector vector,
		LinkedList<String> variableNamesR) {		
	LinkedHashMap<String, PropensitySum> map = new LinkedHashMap<>();			
	for (String name : variableNamesR){
		map.put(name, new PropensitySum());
	}
	
	RList t =  vector.asList();
	Vector names = t.names;
	for (int i = 0; i < names.size(); i++){
		String name = ((String)names.get(i)).trim();
		double[] vals = ((REXPDouble)t.elementAt(i)).asDoubles();
		
		if ("Means Treated".equalsIgnoreCase(name)){
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).setMeansTreated(vals[j]);
			}
		} else if ("Means Control".equalsIgnoreCase(name)){
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).setMeansControl(vals[j]);
			}
		} else if ("SD Control".equalsIgnoreCase(name)){
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).setSdControl(vals[j]);
			}
		} else if (name.startsWith("Mean Diff")){ // 這個列名比較特殊, Bal Improve 矩陣這個列名後面有個點號
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).setMeanDiff(vals[j]);
			}
		} else if ("eQQ Med".equalsIgnoreCase(name)){
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).seteQQMed(vals[j]);
			}
		} else if ("eQQ Mean".equalsIgnoreCase(name)){
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).seteQQMean(vals[j]);
			}
		} else if ("eQQ Max".equalsIgnoreCase(name)){
			for (int j = 0; j < vals.length; j++){
				map.get(variableNamesR.get(j)).seteQQMax(vals[j]);
			}
		} 
	}
	return map.values();
}

可以看到,有強轉型,有陣列下標取值,就這兩個就覺得程式耦合性太高了。總之,感覺 API 不夠友好,沒有跟上潮流啊。