教育装备采购网
第七届图书馆 体育培训

在Stata中编写评估命令:编写Java插件

教育装备采购网 2018-11-27 17:35 围观965次

这篇文章是编写评估命令的第四篇,它演示了如何用另一种语言(如C、C 或Java)编写的代码插入Stata。这种技术被称为编写插件,或者为Stata编写一个动态链接库(DLL)。

在这篇文章中,我在Java中编写了一个插件,它实现了由mymean11.ado中mymean_work()所执行的计算,这个内容在之前的文章中有详细的讲解。

这篇文章类似于《在Stata中编写估计命令:编写C语言插件》和《在Stata中编写评估命令:编写C 插件》,不同之处在于插件代码是在Java而不是C或C 。

编写hello-world JAVA插件

在进行任何计算之前,我都会演示如何编写和编译一个与Stata通信的Java插件。Code block 1包含myhellojava.ado的代码,调用Java插件在Stata中显示Hello from Java。

第2行从sfi-api.jar导入Stata函数接口(SFI),我从Stata分发的Stata/utilities/jar目录中复制到当前目录。您应该将安装在Stata的版本复制到Java编译器需要的目录中。

第3行定义了HelloFromJava的公共类别,在myhellojava.ado的第6行中指定。

第4行定义helloJavaWork()方法,它是这个插件的入口点。入口方法的签名必须是这种形式。该方法是public static。这个方法可以返回一个int,并且它能接受字符串数组。

Stata将返回的int视为返回码,0意味着一切都很顺利而不是识别出一个错误条件。如果返回的int不是0,那么Stata将退出返回int中指定的错误。字符串数组包含javacall传递给插件的参数。

第5行使用SFI方法SFIToolkit.displayln()来显示来自Hello from Java的字符串,并附加一行返回。

第6行返回0到Stata,因此Stata不会以错误代码退出。

接下来将讨论如何从HelloFromJava.java创建JAR文件hellojavawork.jar。为了便于讨论,我使用了Java命令行工具。

在包含myhellojava.ado和HelloFromJava.java的目录中,还有从Stata/utilities/jar目录中复制的sfi-api.jar。在我的OS X Mac上安装了命令行开发者工具,我通过输入javac –release 8 -classpath sfi-api.jar HelloFromJava.java,从HelloFromJava.java和sfi-api.jar中使用javac创建HelloFromJava.class。

在本文中,Stata与Java 8一起工作,尽管Java 9已经发布初始版本。我必须指定-release 8,因为我的机器上的命令行工具默认使用Java 9。如果javac在您的机器上默认为Java 8,您可以省略这个选项。

要从HelloFromJava.class创建JAR文件hellojavawork.jar,我输入了

jar cf hellojavawork.jar HelloFromJava.class

javac和jar的这些命令可以在所有平台上工作,您可以将一个平台上的jar文件分发到其他平台上。这种跨平台的兼容性是Java的一大优势。为了确保Stata命令discard删除当前装载到Stata的所有Java类,我也删除了javac编译的.class,然后再使用Java类运行ado-command。

在OS X MAC操作系统上,我输入

rm HelloFromJava.class

已经创建hellojavawork.jar并且删除了HelloFromJava.class。然后可以在Stata中执行myhellojava.ado

示例1:myhellocjava

如果我更改HelloFromJava.java,重新编译、重制JAR文件、删除.class文件,在运行myhellojava之前在Stata中输入dicard,那么Stata将找到Java类的新版本。discard可以工作是因为Stata的Java插件接口使用自定义类加载器而不是Java system类加载器来加载插件的JAR文件。当您在Stata的当前工作目录中留下您的.class文件时,就会出现问题,因为java-system类加载器会在Stata的自定义加载器运行之前找到并加载.class文件。这个问题避免了Stata的discard命令,这意味着您必须重启Stata来卸载旧的类定义并加载新的版本。为了防止这个问题,在调用Java插件之前删除.class文件(或者,您可以在Stata的当前工作目录之外使用Java代码,但是我更喜欢删除.class文件,因为一旦我有了JAR文件,它们就是多余的)。

为简单起见,我将sfi-api.jar,HelloFromjava.java, myhellojava.ado,和hellojavawork.jar放在同一个目录中。对于大型项目,我将把.ado和.jar文件放在Stata的ADOPATH目录中,并使用我的IDE来管理我放置sfi-api.jar和Java源文件的位置。对于本文中的示例,我使用了sfi-api.jar、所有的.ado文件、所有的Java源文件以及创建的.jar文件都放在一个目录中。

在插件中访问Stata数据

helloJavaWork()使Stata显示插件中创建的内容。下一步是让插件访问Stata中的数据。为了说明这个过程,我将讨论mylistjava.ado使用插件来列出对指定变量的观测值。

让我们先看看ado-code。

第6行,syntax创建三个本地宏。它将用户指定的变量放入本地宏varlist中,将用户指定的任何if条件放入本地宏if中,将用户指定的任何in范围放入到本地宏in中。我将max=3指定到syntax,将变量的数量限制为3。这个限制是愚蠢的,我不需要它作为Stata/Mata程序的例子,但是它可以简化Java插件的示例。

第7行中,marksample创建了样本包容变量,并将样本包含变量的名称放到本地宏touse中。每个被排除观测值的样本包含变量为0,而每个非排除观测值的包含变量为1。marksample使用本地宏varlist中的变量、本地宏if中的if条件以及本地宏中in范围中in来创建样本包含变量(所有三个本地宏都是由syntax创建的)。如果本地宏varlist中的任何变量都包含一个缺失值,被本地宏if的条件排除,或者被本地宏in的范围排除在外,则会排除一个观测结果。如果没有被排除的话,那么样本包含变量就是1。

第9行,我进一步通过显示变量的名称来简化Jave插件,这些变量的值是由方法插件列出的。

第10行,javacall调用了这个插件。入口点是在MyListJava类中的myListJW()方法,由JAR文件myListJW.JAR定义。因为“varlist”是指定的,SFI方法将能够访问本地宏varlist中包含的变量。因为if “touse”是指定的,如果touse中的样本包含变量为0,那么SFI方法Data.isParsedIFTrue()将返回0,如果样本包含变量为1,那么会返回1。因为“in”被指定,SFI方法Data.getObsParsedIn1()和Data.getObsParsedIn2()分别返回在范围内指定的任何用户的第一个和最后一个观测值。

指定“in”不是识别用户指定的样本所必需的,因为if “touse”已经指定了这个样本包含的信息。然而,指定“in”可以极大地减少循环中对数据的观测范围,从而加快代码的速度。

Code block 4中的MyListJava的代码。在包含MyListJava.java和sfi-api.jar的目录中,我创建了mylistjw.jar,在我的Mac上输入以下三行代码:

javac –release 8 -classpath sfi-api.jar MyListJava.java

jar cf mylistjw.jar MyListJava.class

rm MyListJava.class

解释了MyListJava.java如何说明Stata的Java插件的结构,并且讨论了代码中使用的SFI方法。

如果Stata运行顺利,myListJW.java会返回0,如果出错,它会返回一个非零错误代码。

因为所调用的方法都不能失败,唯一的错误条件是遇到缺失值,这些值在第30-34行中处理。在出现错误的情况下,第32行使用SFIToolkit.errorln()来确保Stata显示错误消息,并以红色显示。SFIToolkit.display()是代码中其他地方使用的标准显示方法。

Java插件使用SFI中定义的方法从或写入Stata对象。myListJW()不会返回任何结果,所以它有一个简单的结构。

� 使用SFI方法从Stata中指定的数据样本中读取数据。

� 使用标准的Java和SFI方法,使Stata显示对指定样本的变量的观测,并保留了在指定样本中有多少观测值的计数器。

� 使用标准的Java和SFI方法来显示样本中的第一个观测值、样本中最后的观测值,以及在指定的样本中有多少观测值。

现在我们来讨论MyListJava.java部分。

第10、12和14行使用SFI Data类方法。Data.getParsedVarCount()将varlist中指定的变量数量放入nVariables中。Data.getObsParsedIn1()将in范围指定的第一个观测值放入firstObs。Data.getObsParsedIn2()将in范围指定最后一个观测值放入lastObs。如果in范围没有指定到javacall,那么firstObs将包含1,而lastObs将包含数据集中观测值数量。

firstObs、lastObs和所有Stata观测数量的Java变量都是long类型的,因为比适合int类型的Java变量,Stata数据集可以包含更多的观测数量。

第20-22行确保我们跳过了被mylistjava.ado第10行指定的javacall的if限制排除的观测结果。为了说明一些细节,请看示例2。

示例2mylistjava

在第20行中,当指定给javacall的if限制的obs观测值是1时,Data.isParsedIfTrue(obs)返回1,否则返回0。在mylistjava.ado的第10行中,我们看到传递给javacall的if限制是if“touse”。正如上面所讨论的,对于排除变量,本地宏touse的样本包含变量是0,对于非排除变量是1。

包含了mylistjava.ado第10行in范围,因此,MyListJava.java第19行的观测值循环,从in范围内指定的开头到结尾。在示例2中,不是循环auto数据集中所有74个观测值,而是循环MyListJava.java第19行的第2个观测值到第10个观测值。

在示例2中,6个观测值的样本包含变量为1,而其他68个观测值的变量为0。in 2/10的范围不包括观测值1和11-74的观测值。在前10个观测值中,2被排除在外,因为rep78缺失。排除一个观测值,因为trunk是21。

为了进行比较,在示例3中列出了2到10之间的9个观测值。

示例3list

回到MyListJava,我们可以看到第28-29行说明了如何将Stata数值变量的值放入Java变量中。请注意,Data.getNum()返回所有Stata数值变量类型double。在示例2中,mpg、trunk和rep78都是Stata中的int类型。

如果一个变量中任何观测值包含一个缺失值,那么第30-34行会导致myListJW()退出,错误信息为416。这些行是多余的,因为指定给javacall的touse样本包含变量,排除包含缺失值的观测值。我包含了这些行来说明如何安全地从插件内部排除缺失值,并重申Java代码必须小心处理缺失值。Stata缺失的值是Java中是有效的双精度数。如果在计算中包含Stata缺失值,就会得到错误的结果。

估算Java插件中的平均值

现在,我将讨论ado命令mymeanjava,在MyCalcs类中使用myWork()方法来实现mymean11.ado中执行mymeanwork()的计算。

在code block 5中,Mymeanjava的代码来自于mymeanjava.ado。

这个程序的大概结构跟mymean10.ado和mymean11一样,这个内容在《在Stata中编写估计命令:编写插件》讨论过。整体来看,mymeancpp.ado可以:

� 解析用户输入

� 创建样本包含变量

� 为保存结果的对象创建临时名称

� 调用工作程序来进行计算

� 保存工作程序返回的结果到e()中,并

� 显示结果

Mymeanjava.ado和mymean11.ado的主要区别在于,工作程序是一个Java插件,而不是一个Mata函数。

第6行和第7行与mylistjava.ado的代码相同。想要了解这些行是如何创建本地宏varlist的,在本地宏touse中样本包含变量,本地宏in包含任何用户指定的范围,可以参看Getting access to the Stata data in your plugin中对mylistjava.ado的说明。

第8行将临时名称放入本地宏b、V和N中。我们可以使用这些名称来计算由Java插件计算的结果,并且知道我们不会覆盖用户保存在全局Stata内存中的任何结果(回想Stata矩阵和标量是Stata中的全局对象)。请参阅Programming an estimation command in Stata: A first ado-command)。此外,Stata还会在mymeanjava终止时,将tempname创建的临时名称中的对象删除。

mymeanjava中的第10行类似于mylistjava.ado中的第10行。在这种情况下,myWork()是在类MyCalcs中定义的入口方法,位于JAR文件mycalcs.jar中。上面已经讨论了varlist, if,’touse’和’in’的详细情况。新的情况是,我们使用args(b,V,N)将临时名称传递到myWor()中。

myWork()可以:

� 执行计算

� 将估计的方法放入一个新的Stata矩阵中,该矩阵的名称在本地的宏b中

� 将估计方差(协方差)的估计量(VCE)放入一个新的Stata矩阵中,该矩阵的名称在本地宏V中

� 将样本内的观测数量放入Stata标量中,该标量的名称位于本地宏N中

第13-15行将变量名称放在估计方法向量的列条纹上和VCE矩阵的行和列条纹上。第16-18行将结果保存在e()中,第19行显示结果。

在讨论myWork()的细节之前,我们先创建插件并运行一个示例。

在一个包含MyCalcs.java,MyCalcsW.java,MyMatrix.java,MyLong.java和sfi-api.jar的目录中在Mac电脑中输入以下内容创建了mycalcs.jar。

javac --release 8 -classpath MyCalcs.java MyCalcsW.java MyMatrix.java MyLong.java sfi-api.jar

jar cf mycalcs.jar MyCalcs.class MyCalcsW.class MyMatrix.class MyLong.class

rm MyCalcs.class MyCalcsW.class MyMatrix.class MyLong.class

创建完mycalcs.jar,运行示例3。

示例4mymeanjava

现在讨论Java代码的一些方面,从code block 6中的MyCalcs.java开始。

MyCalcs.java只包含入口方法myWork()。综上所述,myWork()可以执行以下任务:

1把名称作为参数传递给Java String对象的实例中,这些对象可以被传递到SFI方法中。

2将指定Stata变量的数量放入用于循环变量的Java变量中。

3将样本观测的范围放入Java变量中,用于循环观测值。

4创建MyMatrix类的bmat和vmat实例来保存样本平均值和VCE。

5创建了MyLong类的nObs实例,它将保存样本观测值的数量。

6使用MyCalcsWmyAv()和MyCalcsWmyV()方法来计算结果并保到在bmat、vmat和nObs中。

7使用MyMatrix类的CopyCtoStataMatrix()方法来将bmat和vmat的结果复制到新的Stata矩阵。新的Stata矩阵的名称是传递给myWork()的第一个和第二个参数。

8使用SFI方法Scalar.setValue()将结果从nObs复制到新的Stata标量中,该标量的名称是传递给myWork()的第三个参数。

MyCalcs.java很容易读懂,因为我把所有的细节都放到了MyMatrix、MyCalcsW和MyLong类中,下面我将对此进行讨论。

如同Stata的所有Java插件一样,myWork()使用返回码rc来处理错误条件。如果一切顺利,每个调用的方法都返回0,如果它不能执行所请求的工作,它会返回一个非零错误代码。如果返回的代码不是0,myWork()会立即返回到Stata中。与错误条件相关联的错误消息由这些方法来显示。

在(3)中,我注意到bmat和vmat是MyMatrix类的实例。样本平均值和VCE最好保存在矩阵中。为了事情简单和自包含,我定义了一个简单的矩阵类MyMatrix,它使用row-major保存,并且只使用我需要的方法。除了copyJavatoStataMatrix()方法之外,MyMatrix的代码是标准Java,可以在code block 7中看到。

第33-58行包含copyJavatoStataMatrix()的代码。第40和49行使用了我还没有讨论过的SFI方法。Matrix.createMatrix(字符串sname、int rows、int cols、double val)使用rows行和cols列创建了一个新的Stata矩阵。这个矩阵的每一个元素都被初始化为值val。sname包含这个Stata矩阵的名称。

Matrix.storeMatrixAt(字符串sname,int i,int j,double val)将值val保存到Stata矩阵行i和列j中,该矩阵的名称包含在sname中。基于零的索引给出了行i和列j。

在(4)里,我注意到,我使用了MyLong类的一个实例来保存样本观测值的数量。Java中的原始类型不能通过引用传递,标准的包装类型是不可变的,所以我创建了long counter,nObs传递到MycalcsW.myAve()中。当MyCalcsW.myAve()完成时,nObs包含样本观测值的数量。MyLong的代码是标准Java,由code block 8中给出。

在(5)中,我注意到MyCalcsW.myAve()和MyCalcsW.myV方法计算了样本平均值和VCE。这些方法在类MyCalcsW中,它的代码由code block 9给出。

MyCalsW.myAve()是Mata函数MyAve()在Java中的实现,《在Stata中编写估计命令:编写插件》这篇文章中有过讲解。它将样本平均值放入MyMatrix类的bmat实例中,并将样本中观测的数量放入nObs中。这个方法的大部分代码是标准Java,或者使用我已经讨论过的SFI方法。第18、34和38行值得讨论。

第18行MyCalcsW.java使用MyLong的方法incrementValue()来增加保存在nObs中的观测数量。它使nObs的当前值递增1。

第34行使用MyMatrix方法incrementByValue()。当计算样本均值并将其保存在向量名称为b的jth元素中时,您需要保存b[j] value到b[j]中。换句话说,通过value,在b中增加jth元素的数量。通过value,在bmat中bmat.incrementByValue(0,var-1, value)增加元素var-1。

第38行使用MyMatrix的divideByScalar()方法。bmat.divideByScalar(z)将bmat的每个元素替换为可以除以z的元素。

MyCalsW.myV()是Mata函数MyV()在Java中的实现,《在Stata中编写估计命令:编写插件》这篇文章中有过讲解。它将VCE放入MyMatrix类的vmat实例中。这个方法的大部分代码是标准Java,或者使用我已经讨论过的方法。第72、77和85行使用MyMatrix方法storevalue()和getValue()。vmat.storeValue(i,j,z)将z值保存在MyMatrix的vmat实例中(i,j)。vmat.getValue(i,j)返回保存在MyMatrix中vmat实例中的元素(i,j)中的值。

完成和撤销

这篇文章展示了如何实现一个Java插件,它可以在mymean10.ado和mymean11.ado中执行Mata工作函数的计算。这个内容在《在Stata中编写估计命令:编写插件》中讨论过。

点击进入北京天演融智软件有限公司展台查看更多 来源:教育装备采购网 作者:科学软件网 责任编辑:张肖 我要投稿
校体购终极页

相关阅读

版权与免责声明:

① 凡本网注明"来源:教育装备采购网"的所有作品,版权均属于教育装备采购网,未经本网授权不得转载、摘编或利用其它方式使用。已获本网授权的作品,应在授权范围内使用,并注明"来源:教育装备采购网"。违者本网将追究相关法律责任。

② 本网凡注明"来源:XXX(非本网)"的作品,均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,且不承担此类作品侵权行为的直接责任及连带责任。如其他媒体、网站或个人从本网下载使用,必须保留本网注明的"稿件来源",并自负版权等法律责任。

③ 如涉及作品内容、版权等问题,请在作品发表之日起两周内与本网联系,否则视为放弃相关权利。

校体购产品