使用 Rserve 實現 R 程式的複雜資料返回
阿新 • • 發佈:2018-11-30
在 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 不夠友好,沒有跟上潮流啊。