这篇文章是本系列文章中的第一篇,阐述了如何用其他语言(如C,C 或Java)编写的代码插入到Stata中。这种技术被称为编写插件或为Stata编写动态链接库(DLL)。
可以为任何任务编写插件,包括数据管理、图形分析或统计估算。根据本系列的主题,我们先讨论估算命令的插件。
本文中,将讨论编写插件的利弊,并且讨论一个简单的程序,其计算将在以后的文章中用插件来替换。
什么是插件?
插件是一种或多种用另外一种语言编写的函数,可以从Stata中调用。从技术上讲,另一个程序在被调用时会链接到Stata,这个过程被称为包含其他程序的库到Stata的动态链接。 出于这个原因,插件也被称为DLL。
人们用其他语言编写插件来解决真正困难的问题。 可以为Stata编写插件的三个最常见的原因如下。
使用另一种语言编写的代码不适用于Stata或Mata。
在Stata / Mata中有一个编译系统,它不使用内置、快速、矢量化的函数,可以通过使用低级别语言(如C)进行一些计算来使其更快。
可以使用像C语言这样的低级别语言开发方法,然后将这些方法插入到Stata和其他统计软件包中。
插件的问题在于它们很难。用另一种语言编写和编译的插件要比仅仅使用Stata和Mata编写的插件困难得多。此外,错误的成本很高。插件可能很危险; 可能会损坏内存或做其他事情而导致Stata退出或以其他方式“崩溃”。
在C、C 或Fortran中维护插件是很难的。对于这些插件,您希望其为工作的每个操作系统都编译一个版本。 例如,您可能需要Windows、Mac和Linux或两个版本的编译库。此外,当操作系统获得新版本时,必须重新编译插件,并且可能需要为操作系统的旧版本和新版本分发版本。
Java插件更容易维护,但它们通常比用C、C 或Fortran编写的插件慢。可以分发单个Java库以便在所有支持的操作系统上运行。只需在Java环境中需要更改时重新编译即可。尽管存在这些困难,但在某些情况下,插件可以使Stata实现可用或可行。本系列文章演示了如何用不同语言编写和编译插件。
平均估计量
首先讨论在代码块1中给出的mymean10.ado,它实现了命令mymean10。mymean10计算用户指定的变量均值和估计量的方差 - 协方差(VCE)的样本平均估计量。 mymean10允许用户指定if限制和in范围,处理指定变量中的缺失值,但不允许任何选项。
mymean10.ado使用Mata进行计算。随后的文章中将用插件计算代替这些Mata计算。
请注意程序的结构。第6-7行解析用户输入的内容,第10行使用Mata工作函数mymean_work()进行计算,第12-18行存储并显示结果,第21-40行定义mymean_work()。
我们来看看这些部分。
在第6行,syntax创建本地宏varlist,其中包含用户指定的变量。syntax还创建了本地宏if 和in,分别包含任何限制或用户指定的范围。第7行中,marksample使用本地宏varlist,if和in来创建样本包含变量。该样本包含变量对于每个包括的观察值是1,对于每个排除的观察值是0。样本包含变量说明了用户指定的if限制条件或明确排除了观察结果的in范围,并且在varlist中的任何变量中都缺失的值,这些值隐含地排除了观察结果。marksample将此样本变量的名称放在本地宏touse中。
第8行,tempname将临时名称放入本地宏b,V和N中。这些名称未被使用,当mymean10完成时,将删除存储在这些名称中的对象。我们使用临时名称来避免覆盖用户创建的全局对象,如Stata矩阵和Stata标量。
第10行使用了一行调用Mata工作函数mymean_work()来计算点估计值,VCE和样本大小。mymean_work()将点估计的矢量放入Stata矩阵中,其名称存储在本地宏b中。mymean_work()将VCE放入Stata矩阵中,其名称存储在本地宏V中。并且mymean_work()将包含的观察数量放在Stata标量中,其名称存储在本地宏N中。
第12-14行将行和列名称放在存储点估计矢量和VCE的矩阵上。第15-17行将结果存储在e()中,第18行产生标准的Stata输出表。
第21-40行定义了mymean_work()。
第22-24行指定mywork_work()不向其调用者返回任何内容并接受五个字符串标量参数。第一个参数vlist包含用户指定变量的名称。第二个,touse,包含上面讨论的样本包含变量的名称。 最后三个包含将存储结果的名称。当mymean_work()完成时,本地宏bname包含存储点估计向量的Stata矩阵的名称,本地宏vname包含存储在VCE的Stata矩阵的名称,本地宏nname包含存储样本观察数量的Stata标量名称。
现在讨论mymean_work()。第26-28行声明函数中使用的变量。第30行将Stata中的样本包含变量为1的观察副本放入矩阵X。
第31-34行计算结果。31行将点估计值放入Mata向量b中。第32-34行计算VCE并将其存储在Mata矩阵V中。
在36行,st_matrix()将点估计值从b复制到Stata矩阵,其名称存储在bname中。在第37行中,st_matrix()将VCE从V复制到Stata矩阵,其名称存储在vname中。在第38行中,st_numscalar()将样本观察的数量从Mata标量n复制到Stata标量,名称存储在nname中。
当我们编写插件时会发生什么变化?
当我们编写插件时,所有通用结构和许多细节都保持不变。 我们称插件为计算而不是Mata函数有什么变化。
考虑用C语言编写代码来复制mymean_work()执行的计算。三件事可能被改变。首先,我们不会将数据从Stata复制到我们的插件中。 插件环境为我们提供了Stata中数据的视图。 其次,我们必须编写一个函数来实现在第31行执行的平均值。第三,我们必须编写一个函数来实现在第32-34行上执行的VCE计算。
为了便于介绍插件,我在Mata中进行了这些更改,如代码块2中的mymean11.ado所示。
第1-19行中的Stata部分保持不变,调用mymean_work()也是如此。
31和32行与mymean10.ado中的对应行不同。mymean10.ado中的第30行将用户指定变量的观察副本放入矩阵X中,其中样本包含变量为1。第32行mymean11在用户指定变量的所有观察中获得一个名为X的单独视图。 这些关于samp和X的视图更像是插件环境提供的数据访问功能。
在mymean11.ado的第34行中,我们使用MyAve()函数将样本平均值放入b中,将样本包含变量为1的观察数量放入n中。第42-59行的MyAve()代码类似于可写入的代码,例如C。最大的区别是第58行使用Mata运算符将b的每个元素除以标量。插件将包含执行这些操作的函数。
在第35行,我们使用MyV()函数将VCE放入V。 MyV()的代码在61-95行。这段代码也类似于用C编写的代码,同样需要注意的是94行将作为一个函数来实现。
myean11.ado中的第37-39行与mymean10.ado中36-38的对应行相同。
如果我在Mata中编写估算器,不会在myean11中使用该实现。只需将样本包含变量为1的观察值放入Mata视图中,就可以大大加快速度。MyAve()和MyV()函数说明了如何在数据循环中执行计算。插件将实现这些功能的版本。