代码调用SPSS功能执行分析

一、 背景

由于项目需要使用到最优尺度回归算法对数据进行分析,但是该算法是闭源的,而SPSS内置了该分析算法,所以就打算通过代码操控SPSS Statistics来实现功能,而SPSS网上资源很少,目前我找到了相关java、R、python的外部接口的资料,由于本身是java程序员,继而对java调用spss进行了研究。由于我们的项目是桌面端的使用C#编写,后面大致研究了一下C#通过cmd调用java并获取结果。

二、 开发环境

  • idea 2020
  • 无maven
  • jdk 1.8
  • SPSS Statistics 24
    这里需要注意一下 ,SPSS是在其安装目录\IBM\SPSS\Statistics\24里内置了JRE以及spssjavaplugin.jar,这样我们只需要在客户电脑上安装SPSS就有了代码的运行环境了

三、 问题与解决

1. 如何通过java调用spss?

SPSS给我们提供了spssjavaplugin.jar,其中提供了api供我们访问调用,但是有一个问题是这个jar包是不能移动的,这里猜测他使用了相对路径调用了spss的一些组件。以下是我的代码:

// 这一部分是通过命令行获取一些自变量、因变量以及excel数据的参数,可以忽略
Gson gson = new Gson(); // GSON框架
// 这里直接用了数据做测试
        JsonRootBean jsonRootBean = gson.fromJson("{'varibles':['BJHF','CJTLX','SYYLX','CCCDJLT74','DZCLL'],'depVarible':'LCYAVG','excelPath':'C:\\\\Users\\\\xxc\\\\Desktop\\\\TrainData.xlsx'}".replace("'","\""), JsonRootBean.class);
        // 自变量
        String[] varibles = new String[jsonRootBean.getVaribles().size()];
        System.arraycopy(jsonRootBean.getVaribles().toArray(),0,varibles,0,varibles.length);
        // 因变量
        String depVarible = jsonRootBean.getDepVarible();
        // 我是用的Excel格式的数据文件,c#将数据转换成excel,然后调用java去分析返回结果
        String excelPath = jsonRootBean.getExcelPath();
        // 这里合并了一下两个数组用于后面字符串拼接
        String[] allVaribles = combine(varibles,new String[] {depVarible});
// 这是最优尺度回归的命令
// 中间部分其实才是真正的命令,而第一二行是指定命令的执行结果输出到指定的地方,这里的OXML可以让我们在工作空间去提取,也就是让下面的第三步得以执行,其中XMLWORKSPACE要与下面的handle变量
String command = "OMS SELECT TABLES" +
                "/DESTINATION FORMAT=OXML XMLWORKSPACE='desc_table'\n" +
                "VIEWER = NO.\n" + "GET DATA \n" +
                "  /TYPE=XLSX \n" +
                "  /FILE='" + excelPath +  "' \n" +
                "  /SHEET=name 'TrainData' \n" +
                "  /CELLRANGE=FULL \n" +
                "  /READNAMES=ON \n" +
                "  /DATATYPEMIN PERCENTAGE=95.0 \n" +
                "  /HIDDEN IGNORE=YES. \n" +
                "EXECUTE. \n" +
                "CATREG VARIABLES=" + String.join(" ",allVaribles) + "\n" +
                "  /ANALYSIS=" + depVarible + "(LEVEL=SPORD,DEGREE=2,INKNOT=2) WITH " + String.join("(LEVEL=SPORD,DEGREE=2,INKNOT=2) ",varibles).trim() + "(LEVEL=SPORD,DEGREE=2,INKNOT=2)" + " \n" +
                "  /MISSING=" + String.join("(LISTWISE) ",allVaribles) + "(LISTWISE) \n" +
                "  /MAXITER=100 \n" +
                "  /CRITITER=.00001 \n" +
                "  /PRINT=R COEFF ANOVA \n" +
                "  /INITIAL=NUMERICAL \n" +
                "  /PLOT=NONE \n" +
                "  /REGULARIZATION=NONE \n" +
                "  /RESAMPLE=NONE.\n" +
                "OMSEND.";
         // 第一步: 启动SPSS,后台运行 
        StatsUtil.start();
        // 设置为false就不会将分析结果打印,默认true,正常用于调试查看结果
        StatsUtil.setOutput(true);
        // 第二步:执行命令
        StatsUtil.submit(command);
        // 第三步: 取值
        // 这是OXML的表名,在命令中指定了
        String handle = "desc_table";
        // OXML的根路径
        String context = "/outputTree";
        // 通过XPath来获取到相关的数据
        String xpath = "//pivotTable[@subType='ANOVA']" +
                "/dimension[@axis='row']" +"/category[@text='Regression']" +
                "/dimension[@axis='column']" +
                "/category[@text='Sum of Squares']" +"/cell/@text";
         // 获取处理结果
        String[] result = StatsUtil.evaluateXPath(handle, context, xpath);
        System.out.println(result[0]);
        StatsUtil.deleteXPathHandle(handle);
        // 第四步: 关闭SPSS
        StatsUtil.stop();

上面的代码可能看的不是很明白,不过目前你只需要知道如何启动关闭SPSS,如何给SPSS传递命令即可。

2. 如何获取到相关算法的命令?

我们可以先将数据导入SPSS,然后通过人工操作的方式选择算法,设置变量进行分析,SPSS在显示分析结果的同时会显示执行的命令,如下:
example
command
我们可以将以上命令复制到java代码中,通过submit即可获得同样的结果(注意一下excel文件的位置)。

3. 为什么使用excel

我觉得excel比较容易读写,可以将数据库中的内容写入excel表格然后分析,如果你有更好的方式也可以,我尝试将数据转换为sav,但是网上不是很多相关资料。还有一点我们的项目是客户端的,所以可以通过excel的路径进行加载,而不需要网络传输。

4. spss命令

SPSS命令我也是不懂的,但是我通过看spss java plugin的文档结合spss命令的文档,得知了OMS的作用以及如何提取分析结果的内容。具体看我第1点的代码。如果感兴趣可以去看看IBM SPSS Java Plugin使用指南(官方英文版) ,SPSS 25 语法命令参考

5. 外部jar问题

由于spssjavaplugin.jar是外部的不能移动的,于是我试图将它加入CLASSPATH,但是最终没有成功,目前我也不知道什么原因,如果有懂的老哥可以留言说一下。

  1. 我刚开始是打成jar包,然后java -jar 运行,这种情况只有我的jar包与plugin在同一个路径才有用,因为打jar包的时候我用idea配置的是不复制进jar包,而是通过MANIFEST.MF链接,解压可以发现里面的Class-Path: spssjavaplugin.java,也就是说我运行jar包的时候他会在当前路径找这个plugin然后加载到虚拟机。由此我做了更多的一些研究,我想通过绝对路径去引用这个jar包,但是我写了绝对路径然后重新打成jar包之后要么就是空指针异常,要么就是ClassDefNotFound.具体可以看java -jar命令运行jar包时指定外部依赖jar包,这里面的方法我都试了,都没有用,然后我找到了-jar参数运行应用时,设置classpath的方法,虽然没有解决问题,但是我大概知道原因了。

因为使用“-jar”选项(形如:java -jar xxx.jar )来运行一个可执行的jar包时,jar包会覆“-cp”的值。
换句话说,-jar 后面所跟的jar包的优先级别最高。如果指定了-jar选项,所有环境变量和命令行制定的搜索路径都将被忽略。JVM APPClassloader将只会以jar包为搜索范围.

在这里插入图片描述2. 于是我想着查看idea是如何运行代码的,毕竟他能运行,然后我发现了第一行的执行命令
在这里插入图片描述具体来看是这样的,可以复制看看,里面起作用的是-classpath(与-cp功能一样)选项将spssjavaplugin.jar放入了classpath中由jvm加载。可以注意到这里并不是调用jar的方式运行的,而是使用class文件

"C:\Program Files\Java\jdk1.8.0_191\bin\java.exe" -Dvisualvm.id=257640514635900 -javaagent:C:\Users\xxc\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5284.40\lib\idea_rt.jar=56314:C:\Users\xxc\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5284.40\bin -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;C:\Users\xxc\IdeaProjects\statistics\out\production\statistics;C:\IBM\SPSS\Statistics\24\spssjavaplugin.jar;C:\Users\xxc\IdeaProjects\statistics\lib\gson-2.8.9.jar" com.company.Test1

在这里插入图片描述于是我直接将build获得的class文件整个目录拷贝使用,而不使用jar包,然后通过命令指定classpath成功实现,这个statistics就是最终的项目,最终执行命令

C:\\IBM\\SPSS\\Statistics\\24\\JRE\\bin\\java -cp C:\\IBM\\statistics;C:\\IBM\\statistics\\gson.jar;C:\\IBM\\SPSS\\Statistics\\24\\spssjavaplugin.jar; com.company.Test1 

这里使用的是SPSS中的JRE运行的,同时将gson和spssjavaplugin以及我的项目路径加入到了classpath,这样我运行java命令这些class就能都被加载,如果没有C:\\IBM\\statistics那么就找不到我的启动类了。

6. C# 如何调用java

我是通过Process类调用cmd,然后cmd执行命令然后获取到输出这种方式实现的,具体看代码如下

// 执行命令返回输出的方法
private static string RunCmd(string command)
        {
            //實例一個Process類,啟動一個獨立進程
            Process p = new Process();
            //Process類有一個StartInfo屬性,這個是ProcessStartInfo類,包括了一些屬性和方法,下面我們用到了他的幾個屬性:
            p.StartInfo.FileName = "cmd.exe";             //設定程序名
            p.StartInfo.Arguments = "/c " + command;     //設定程式執行參數
            p.StartInfo.UseShellExecute = false;          //關閉Shell的使用
            p.StartInfo.RedirectStandardInput = true;   //重定向標準輸入
            p.StartInfo.RedirectStandardOutput = true;  //重定向標準輸出
            p.StartInfo.RedirectStandardError = true;   //重定向錯誤輸出
            p.StartInfo.CreateNoWindow = false;           //設置不顯示窗口
            p.Start();  //啟動
                        //p.StandardInput.WriteLine(command);		 //也可以用這種方式輸入要執行的命令
                        //p.StandardInput.WriteLine("exit");		  //不過要記得加上Exit要不然下一行程式執行的時候會當機
            string output = p.StandardOutput.ReadToEnd();
            string err = p.StandardError.ReadToEnd();
            Console.WriteLine(err);
            p.WaitForExit();
            return output;          //從輸出流取得命令執行結果
        }
        // 使用方法
        Console.WriteLine(RunCmd("C:\\IBM\\SPSS\\Statistics\\24\\JRE\\bin\\java -cp C:\\IBM\\statistics;C:\\IBM\\statistics\\gson.jar;C:\\IBM\\SPSS\\Statistics\\24\\spssjavaplugin.jar; com.company.Test1")) ;

7. 如何传参

我是通过json进行交互的,但是因为cmd会屏蔽掉**"**,所以呢,我通过替换将双引号转换为单引号,然后java那边再转换回来即可。这样直接将转义后的json串放在命令的最后即可(需要与前面隔一个空格)

三、 当前问题

  1. -Xbootclasspath如何使用才能有效?
    由于windows和linux分隔符以及其他的一些差异,一直尝试使用这个选项但是仍然不加载相关jar,可以通过-verbose:class选项来查看加载的类和jar
  2. 为什么要打成jar包?
    通过查资料知道了jar包的问题,jar包能将这些文件以及变成一个文件,方便传输以及运行,但是在我这个场景下似乎不那么友好
  3. CLASSPATH环境变量在什么时候才能生效?
    我尝试通过java运行class文件,我将spssjavaplugin的路径配在了环境变量中,仍然无效,配置如下:
.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;C:\IBM\SPSS\Statistics\24\spssjavaplugin.jar;
  1. 有没有C#能够直接调用的API?

三、参考资料

IBM SPSS Java Plugin使用指南(官方英文版)
SPSS 25 语法命令参考
-jar参数运行应用时,设置classpath的方法
SPSS.NET第三方开源代码

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值