Stata小白编程:步步为营-以阶乘计算为例

发布时间:2021-04-11 阅读 172

Stata连享会   主页 || 视频 || 推文 || 知乎

温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验。

New! lianxh 命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh

课程详情 https://gitee.com/lianxh/Course

课程主页 https://gitee.com/lianxh/Course

⛳ Stata 系列推文:

作者:邱枞 (中山大学)
邮箱qiuz@mail2.sysu.edu.cn


目录


1. 基本思路

1.1 阶乘计算的实现

​阶乘是基斯顿·卡曼 (Christian Kramp, 1760~1826) 于 1808 年发明的一种数学运算符号。其相关表述如下:

  • 一个正整数的阶乘 (factorial) 是所有小于等于该数的正整数乘积;
  • 0 的阶乘为 1;
  • 自然数 n 的阶乘写作 n!。

那么如何在 Stata 中实现阶乘算法那?首先,让我们从一段代码开始,具体如下所示:

local k = 5  
local a = 1  
forvalues i = 1/`k'{
  local a = `a'*`i'
}
dis "`k'! = " `a'

​在这段代码中,我们定义了两个暂元 ka。其中,k 表示需要计算阶乘的自然数,而 a 则是被用于计算累乘。计算思路也很简单,就是遍历所有小于等于 k 的正整数,然后将它们累乘。

但是,当计算大量阶乘时,每次都要运行同样的代码,这就会导致重复工作,并且犯错的概率也会大增。为此,我们可以通过程序编写来简化过程。

capture program drop myfact1
program define myfact1
version 15

  args k    // 定义暂元 k,用于接收用户输入的数字
  local a = 1
  forvalues i = 1/`k'{
    local a = `a'*`i'
  }
  dis "`k'! = " `a'

end  

可以发现,我们只是在开头和结尾增加了几行代码。接下来,将对这些增加代码进行逐一解读。

  • 首先,需要为程序命名,即 program define myfact1。值得注意的是,按照 StataCorp 惯例,用户写的命令名必须严格小写;
  • 其次,添加 Stata 版本,即 version 15。通过标记版本,用户命令可以始终保持在该版本上运行;
  • 接着,接受输入内容,即 args karg 命令比 syntaxgettoken 命令更加简单粗暴,即它将命令名后一整行内容都读取到暂元 k 中;
  • 最后,需要结束程序,即 end

不过,一旦程序被定义,就会保存在 Stata 内存里,直到 Stata 关闭。若后面定义同名程序,Stata 就会报错。因此,需要在程序的最开始加上 capture program drop myfact1 命令,以删除内存中已经保存的同名程序。在这里,capture 实现了不显示报错,也不终止程序的功能。

至此,我们便可以直接调用程序来实现阶乘计算。

. myfact1  7
7! = 5040

1.2 升级为 ado 程序

如果需要在其它 do 文档或命令窗口中调用阶乘程序,我们又该如何做那?其实,Stata 的命令大都是以 ado 文档形式保存,例如 reg 命令。当我们需要使用命令时,直接输入即可调用。为此,我们将计算阶乘的程序保存为 ado 文档。具体操作步骤如下:

  • 将 do 文档的后缀改为 .ado,并且文件名和程序名必须相同;
  • ado 文档需要保存在专门的目录下,Stata 才能够识别并运行。在命令窗口输入 adopath 可以查看路径清单,或者你可以使用 adopath + pathname 命令添加新的路径至清单中。
. adopath
  [1]  (BASE)      "D:\Stata15\ado\base/"
  [2]  (SITE)      "D:\Stata15\ado\site/"
  [3]              "."
  [4]  (PERSONAL)  "D:\Stata15/ado\personal/"
  [5]  (PLUS)      "D:\Stata15/ado\plus/"
  [6]  (OLDPLACE)  "c:\ado/"
  [7]              "D:\Stata15/\ado\personal\_myado"

​最好是保存在 PERSONAL 文件夹下 (如果没有,就在 Stata 的 ado 文件夹下新建名为 "personal" 的文件夹。

执行完上述步骤后,我们就可以在任何需要计算阶乘的时候,直接调用阶乘命令。

2. 一些细节

尽管上述程序已经可以实现阶乘的计算,但仍有很多细节需要注意。接下来,我们将为大家逐一介绍。

第一步自然就是输入。除了sumdes2这类命令外,执行命令往往需要外部输入,否则这个命令毫无意义;而如何读取输入也是有讲究。今天我们主要介绍两个输入命令:argssyntax

2.1 输入的实现

2.1.1 利用 args 命令输入

args 命令基本语法如下:

args macroname1 [macroname2 [macroname3 ...]]

args 命令默认将程序名后的整行输入存储在暂元 macroname1 中。但是遇到多项输入时,args 也会根据空格对多个输入进行简单拆分,并存储在 macroname1macroname2macroname3,...,等暂元中。例如:

capture program drop myprog
program myprog
version 15
  args k1 k2 k3
  dis "`k1',`k2',`k3'"
end
. myprog 1 2 3
1,2,3

2.1.2 利用 syntax 命令输入

syntax 是 Stata 中功能比较齐全的输入命令,其基本语法如下:

syntax [varlist | namelist | anything]
          [if]
          [in]
          [using filename]
          [= exp]
          [weight]
          [, options]

当输入类型是变量 varlist 时,语法如下:

syntax [varlist/varname/newvarlist/newvarname](varlist_specifiers)

(varlist_specifiers) 主要包括以下可选内容:

  • default=:当可变参数列表是可选的且用户没有指定时,程序如何填充可变参数列表。默认情况下是用所有变量填充;
  • min=# max=#:指定可以输入的最小和最大变量数;
  • numericstringstr#strLvarlist 由完全数值型、完全字符串型 (即 str#strL)、完全 str# 或完全 strL 型变量组成;
  • fv:允许变量列表包含因子变量;
  • ts:允许可变列表包含时间序列运算符;
  • generate:对于 newvarlistnewvarnamegenerate 指定创建新变量并填充缺少的值。
syntax varlist ...
syntax [varlist] ...
syntax varlist(min=2) ...
syntax varlist(max=4) ...
syntax varlist(min=2 max=4 numeric) ...
syntax varlist(default=none) ...
syntax newvarlist(max=1) ...
syntax varname ...
syntax [varname] ...

当输入类型是名称 namelist 时,语法如下:

syntax [namelist](namelist_specifiers)

(namelist_specifiers) 包括以下可选内容:

  • name=:为存储输入内容的暂元命名,默认 name= namelist
  • id=:定义暂元在报错信息中的名称,当输入 id = height 时,系统会在用户无输入时显示错误信息 height required,以提示用户输入 height 相关的内容;
  • min=# max=#:指定可以输入的最小和最大 namelist 数。
syntax namelist ...
syntax [namelist] ...
syntax name(id="equation name") ...
syntax [namelist(id="equation name")] ...
syntax namelist(name=eqlist id="equation list")...
syntax [name(name=eqname id="equation name")] ...
syntax namelist(min=2 max=2) ...

​当输入类型没有限制 anything 时,语法如下:

syntax [anything](anything_specifiers)

(anything_specifiers) 包括以下可选内容:

  • name=:用法同上;
  • id=:用法同上;
  • equalok:规定 =exp 不作为标准语法的一部分,而是作为 anything 的一部分;
  • everything:规定 ifinuse 不作为标准语法的一部分,而是作为 anything 的一部分。
syntax anything ...
syntax [anything] ...
syntax anything(id="equation name") ...
syntax [anything(id="equation name")] ...
syntax anything(name=eqlist id="equation list") ...
syntax [anything(name=eqlist id="equation list")] ...
syntax anything(equalok) ...
syntax anything(everything) ...
syntax [anything(name=0 id=clist equalok)] ...

​鉴于 syntax 命令的强大功能,我们使用 syntax 替换 args 命令来处理输入内容。

syntax anything(name=k id="an integer") 

在上述命令语句中,​无论用户输入什么,我们都储存在名为 k 的暂元中。又由于指定 id="an integer",当检测不到输入时,系统自动报错 an integer required。如果想要更加灵活的无输入报错提醒,可以使用以下代码:

capture syntax anything(name=k) 
if _rc{  // 用户忘了输入数字
    dis as error _rc // 测试错误代码
    dis as error "You must enter an positive integer"
    exit
}  

由于使用了 capture 命令,当我们没有指定 id 时,系统并不会终止或显示报错信息,而是返回一个错误代码 _rc (此种错误类型对应的代码为 100)。利用这个特性,我们可以加入 if 条件判断语句,检测到无输入时,执行特定的报错提醒。Stata 的报错提醒是 dis as error 命令语句。

2.2 验证正整数

2.2.1 使用 assert 进行验证

​如果用户输入的不是正整数,则无法计算阶乘,因此我们需要判断用户的输入是否为正整数。

assert 语句是一种简单的验证命令。当判断条件为真时,命令没有返回值,当判断条件为假时,则返回错误代码 9。利用这个特性,我们依然可以结合 capture 命令进行错误提醒。

判断条件很简单,当输入内容整数部分等于它本身且大于 0 时,assert 函数不会报错,否则,assert 函数报错。

capture assert int(`k') == `k'&`k'>0
if _rc{
  dis as error "输入的不是一个正整数"
  exit	
}

2.2.2 使用 confirm 进行验证

confirm 是一个十分强大的验证命令,包括但不限于可以验证变量是否存在、数据类型、是否为正整数等。在这里,我们利用 confirm integer number 即可验证是否为正整数。

capture confirm integer number `k' // 用户输入了非整数或文字
if _rc{
    dis as error "You must enter a positive integer"
    exit
  }

2.2.3 验证是否存在多个输入

验证用户是否输入超过 1 个数字/标量/变量,有以下几种方法:

wordcount 命令:根据空格将输入内容拆分,并返回拆分得到的词数。

 if wordcount(`"`k'"')>1{  // 用户输入了多个数字
    dis as error "You can only enter one integer"
    exit
  }

index 命令:用于寻找特定字符串,返回找到的第一个特定字符串的位置。

if index("`k'"," ")!=0{
  di in error "You can only enter one integer"
  exit
  }

tokenize 命令:由于该命令相对重要,因此我们需要详细介绍一下。tokenize 命令的基本语法如下:

tokenize [[`]"][string]["[']] [, parse("pchars") ]

tokenize 命令后跟需要切割的字符串,parse 选项用以设定切割字符,默认为空格,并将得到的子字符串自动保存在名为 123,……,的暂元中。

​字符串左右两端可以加上简单双引号 "string",或者复合双引号 `"string"',不过这并不影响命令的使用。之所以有时区分简单双引号和复合双引号,是为了避免字符串中出现引号。

. tokenize some more words
. display "1=|`1'|, 2=|`2'|, 3=|`3'|, 4=|`4'|"
1=|some|, 2=|more|, 3=|words|, 4=||

. tokenize "some more words"
. display "1=|`1'|, 2=|`2'|, 3=|`3'|, 4=|`4'|"
1=|some|, 2=|more|, 3=|words|, 4=||

. tokenize `"some more words"'
. display "1=|`1'|, 2=|`2'|, 3=|`3'|, 4=|`4'|"
1=|some|, 2=|more|, 3=|words|, 4=||

​当需要将空格以外的字符作为切割字符时,可以通过 parse() 选项设定。

. local str "A strange++string"

. tokenize `str', parse("+")
. display "1=|`1'|, 2=|`2'|, 3=|`3'|, 4=|`4'|, 5=|`5'|, 6=|`6'|"
1=|A strange|, 2=|+|, 3=|+|, 4=|string|, 5=||, 6=||

. tokenize `str', parse(" +")
. display "1=|`1'|, 2=|`2'|, 3=|`3'|, 4=|`4'|, 5=|`5'|, 6=|`6'|"
1=|A|, 2=|strange|, 3=|+|, 4=|+|, 5=|string|, 6=||

. tokenize `str', parse( +)
. display "1=|`1'|, 2=|`2'|, 3=|`3'|, 4=|`4'|, 5=|`5'|, 6=|`6'|"
1=|A strange|, 2=|+|, 3=|+|, 4=|string|, 5=||, 6=||

在第一种情况下,当空格没有被作为切割识别字符时,第一个子字符串是 "A strange";而在第二种情况下,当空格和加号同时被作为切割识别字符时,前两个子字符串是 "A" 和 "strange"。括号里的引号也是可选的,即不加引号也可以得到同样的识别效果。值得注意的是,除了空格外,所有的切割识别字符都会在切割结果中作为子字符串保存。

​在本程序中,利用 tokenize 命令的代码如下:

tokenize `k'
if `"`2'"'!=""{
    di in error "You can only enter one integer"
    exit
}

3. 附加功能

完成基本操作后,若我们还想为程序加上一些额外的功能,又该如何?此时,我们就要利用到 syntax 命令的 [,option]

syntax 的可选项用法格式如下:

syntax ... [, MYopt(string) Thisopt(name)]
syntax ... , MYopt(string) Thisopt(name)

​基本用法为,在 syntax 语句中添加存储选项的暂元,来作为程序的附加选项。

  • MYopt 中大写部分为选项的简称。也就是说,使用 MYopt 时,可以用 mymyomyopmyopt 中任意一形式,但是都必须包含 my
  • 需要在括号内添加输入的参数类型,一般有正整数 integer、实数 real 等。如果输入的参数类型与要求的不同,系统会自动报错。

3.1 自定义输出格式

format 命令的一般形式如下:

format varlist %fmt
format %fmt varlist

​常见的数值变量的输出格式为 %#.#f%#.#e%#.#g,分别表示固定格式、科学计数和通用设定方法等。例如,输出格式为 %w.dfw 表示整体输出宽度,包括符号和小数点,而 d 表示小数点右边的位数。关于 format 的具体使用方式,可以参考 help format

​在本程序中,我们将定义一个简易版的 format 选项,即限定输入格式为字符串,并储存在暂元 format 中。接着,利用 captureformat 命令检测输入的自定义格式是否标准格式,正确则不提示,否则提示错误信息 (错误代码为 7)。

capture program drop myfact8
program define myfact8
version 15

syntax anything(name=k) [, Format(string)]
if "`format'" != ""{
  capture confirm format `format'
if _rc{
  dis as error "invalid format(`format'). It should be format(%10.0g)."
  dis as text  "for help, see {help format}"
  exit 198
}
}
local a = 1
forvalues i = 1/`k'{
  local a = `a'*`i'
}
dis "`k'! = " `format' `a' 
  
end 
. myfact8 6, f(%8.3f)
6! =  720.000

. myfact8 6, format(%8.3f)
6! =  720.000

3.2 将阶乘结果生成新变量

​为了保存输出的结果,便于后续研究调用,我们可以生成新变量,将阶乘的结果保存进去。我们通过定义generate选项实现这个功能。下述代码定义了名为generate的命令,缩写为gen,它的功能将用户输入的内容作为变量名,计算的阶乘结果作为值进行保存。类似的gen选项我们在很多命令的选项中也能见到。

capture program drop myfact9
program define myfact9
version 15

syntax anything(name=k) [, GENerate(string) Format(string)]
local a = 1
forvalues i = 1/`k'{
  local a = `a'*`i'
}
if "`generate'" != ""{
  gen `generate' = `a'
}
else{
  dis "`k'! = " `format' `a'
}

end

. clear 

. set obs 1
number of observations (_N) was 0, now 1

. myfact9 6, gen(x1) 

. list x1, noobs 

  +-----+
  |  x1 |
  |-----|
  | 720 |
  +-----+

. myfact9 6, f(%8.3f)
6! =  720.000

4. 计算多个数值的阶乘

4.1 利用 tokenize 命令输入

在前文中,我们已经介绍过利用 tokenize 命令将字符串进行切割,并将返回子字符串保存在名为123,……,的暂元中。对于多个数值输入,我们只需要对其进行切割,然后使用循环程序遍历所有子字符串便可输出所有数的阶乘。

我们可以利用 macro shift 函数计算 tokenize 生成的暂元数量。macro shift 函数的作用是舍弃第一个暂元储存的内容,将后面暂元的内容依次储存在上一个暂元中。因此,只要计算并输出第一个暂元的阶乘后,我们便可以使用该命令,将下一个暂元内容前移。这使得在不知道暂元数量的前提下,也可以完成循环。

*利用循环
capture program drop myfact10
program define myfact10, rclass //利用可选项 rclass,生成并调用返回值
version 15

syntax anything(name=numlist) [, Format(string)]

tokenize "`numlist'"

local j = 1  
while "``j''" !=""{     // `j' 表示房间号;``j'' 表示房间中的内容
  local a = 1
  local k = ``j''       // 注意暂元的引用方式
  forvalues i = 1/`k'{
    local a = `a'*`i'
  }
  dis "`k'! = " `format' `a'
  return scalar k`k' = `a'   
  local j = `j' + 1
}

end 
. myfact10 6 4
6! = 720
4! = 24

. return list

scalars:
                 r(k4) =  24
                 r(k6) =  720
*利用 macro shift
capture program drop myfact11
program define myfact11, rclass //利用可选项 rclass,生成并调用返回值
version 15

syntax anything(name=numlist) [, Format(string)]
tokenize "`numlist'"
while "`1'" !=""{     // `j' 表示房间号;``j'' 表示房间中的内容
  local a = 1
  local k = `1'       // 注意暂元的引用方式
  forvalues i = 1/`k'{
    local a = `a'*`i'
  }
  dis "`k'! = " `format' `a'
  return scalar k`k' = `a'   
  macro shift
}

end 
. myfact11 6 4
6! = 720
4! = 24

. return list

scalars:
                 r(k4) =  24
                 r(k6) =  720

4.2 利用 gettoken 命令输入

tokenize 命令不同,gettoken 只进行一次切割,其语法如下:

 gettoken emname1 [emname2] : emname3 [, parse("pchars")]

​将需要切割的字符串放在 emname3 的位置,gettokenemname3 进行一次切割,得到的首个子字符串保存储存在暂元 emname1 中,剩下的部分保存在 emname2 中。emname2 是可选的,即可以不保存切割剩下的部分。切割符由 parse() 中指定,如不定义,则默认为空格。例如:


.   local k = "3 5 7"
.   gettoken 1 k:k
.   dis `1'
3
.   dis "`k'" // 暂元k保存了切割剩余部分
5 7
.   gettoken 2 k:k
.   dis `2'
5
.   gettoken 3 k:k
.   dis `3'
7

在阶乘的例子中,具体思路如下:

  • 对暂元 k 进行一次切割,得到的第一个数储存在暂元 1 中,并将其作为循环判断的对象,剩余部分,包括接下来每次循环也是,都储存在暂元 3 中;
  • 在循环中,把暂元 1 中的数值重新赋给 k,并用以计算阶乘;
  • 使用 macro shift 命令,此时暂元 1 中的数值被删除,第一次切割剩余部分被保存到了暂元 2 中。此时再进行一次切割,同样将得到的数储存在暂元 1 中,把剩余部分储存在暂元 3 中。
capture program drop myfact12
program define myfact12
version 15

syntax anything(name=k) [, Format(string)]
gettoken 1 3: k
while "`1'"!=""{
  local a = 1
  local k = `1'
  forvalues i = 1/`k'{
	  local a = `a'*`i'
  }
  dis "`k'! = " `format' `a' 
  macro shift  // 将暂元 3 的内容赋予了暂元 2
  gettoken 1 3: 2
}
  
end 

myfact12 6 4 
. myfact12 6 4 
6! = 720
4! = 24

5. 帮助文档

命令的编写者一般都会提供帮助文档以帮助用户理解程序的使用。帮助文档一般包括程序语句、描述、附加选项介绍和例子等。以下是帮助文档的编写格式,更多详细内容可以通过 help smcl 命令查看。

Code                    Description
--------------------------------------------------------------------------------------------
{smcl}                  回车
{hline}                 产生一行黑色直线
{p 4 9 2}               首行前4个空格; 之后每行前9个空格; 行右侧留2个空格的空间
{pstd}                  和{p 4 4 2}相同
{phang}                 和{p 4 8 2}相同
{phang2}                和{p 8 12 2}相同
{p_end}                 代替空白行表示段落的结束
{hi:xxx}                highlight/突出显示,一般用于引用
{title:xxx}             以级标题格式显示
{bf:xxx}                粗体
{marker xxx}            加下划线
{it:xxx}                斜体
{cmdab:text1:text2}     显示 Stata 命令; text1 表示最小缩写, text2 表示最小缩写正文的其余部分
{cmd:xxx}               显示 Stata 命令(以加粗形式)
{help format:fmt}       对 fmt 添加超链接 help format
{inp:.}                 显示用户输入命令

以下为 help myfact 的结果展示:

help myfact
-------------------------------------------------------------------------------------

Title

    myfact -- Cualculate factorial of an integer.


Syntax

        myfact number [, generate(newvarname) format(format)]

where number is an positive integer number.


Description

    myfact calculates the factorial of a positive integer below 171 and outputs the result in the format specified by the user and generate a new variable to restore the value.


Options for myfact

    format[(%fmt)] determind the output form of the answer.

    generate[(newvarname)] generate a new variable to restore the value.

Example


    myfact 10

    Calculate the factorial of number 10.

    myfact 10, format(%12.3f)

    Specify the output form.

    myfact 10, gen(x1) format(%12.3f)

    Specify the output form and generate a new variable named x1 to restore the value.

Author

    Cong Qiu
    SUN YAT-SEN UNIVERSITY(中山大学)
    Guangzhou, China
    qiuz@mail2.sysu.edu.cn

以下为帮助文档源代码:

{smcl}
{* 6October2017}{...}
{hi:help myfact}
{hline}

{title:Title}

{phang}
{bf:myfact} {hline 2} Cualculate factorial of an integer.


{marker syntax}{...}
{title:Syntax}

{p 8 17 2}
{cmdab:myfact} {it:number} [{cmd:,} {cmdab:gen:erate(}{it:newvarname}{cmd:)} {cmdab:for:mat(}{it:format}{cmd:)}]

where number is an positive integer number.


{marker description}{...}
{title:Description}

{pstd}
{cmd:myfact} calculates the factorial of a positive integer below 171 and
    outputs the result in the format specified by the user and generate a new
	variable to restore the value.
 {p_end}


{marker options}{...}
{title:Options for myfact}

{phang}
{opt format}{opt [}{opt (%fmt)}{opt ]} determind the output form of the answer. {p_end}

{phang}
{opt generate}{opt [}{opt (newvarname)}{opt ]} generate a new variable to restore the value. {p_end}

{marker example}{...}
{title:Example}

{pstd}

{phang}
{cmd: myfact 10}
{p_end}

{pstd}
Calculate the factorial of number 10.

{phang}
{cmd: myfact 10, format(%12.3f)}
{p_end}

{pstd}
Specify the output form.

{phang}
{cmd: myfact 10, gen(x1) format(%12.3f)}
{p_end}

{pstd}
Specify the output form and generate a new
	variable named x1 to restore the value.

{title:Author}

{pstd}Cong Qiu{p_end}
{pstd}SUN YAT-SEN UNIVERSITY(中山大学){p_end}
{pstd}Guangzhou, China{p_end}
{pstd}qiuz@mail2.sysu.edu.cn{p_end}

6. 相关推文

Note:产生如下推文列表的 Stata 命令为:
lianxh 程序, m
安装最新版 lianxh 命令:
ssc install lianxh, replace

相关课程

免费公开课

最新课程-直播课

专题 嘉宾 直播/回看视频
最新专题 文本分析、机器学习、效率专题、生存分析等
研究设计 连玉君 我的特斯拉-实证研究设计-幻灯片-
面板模型 连玉君 动态面板模型-幻灯片-
面板模型 连玉君 直击面板数据模型 [免费公开课,2小时]
  • Note: 部分课程的资料,PPT 等可以前往 连享会-直播课 主页查看,下载。

课程主页

课程主页

关于我们

  • Stata连享会 由中山大学连玉君老师团队创办,定期分享实证分析经验。
  • 连享会-主页知乎专栏,400+ 推文,实证分析不再抓狂。直播间 有很多视频课程,可以随时观看。
  • 公众号关键词搜索/回复 功能已经上线。大家可以在公众号左下角点击键盘图标,输入简要关键词,以便快速呈现历史推文,获取工具软件和数据下载。常见关键词:课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法

连享会主页  lianxh.cn
连享会主页 lianxh.cn

连享会小程序:扫一扫,看推文,看视频……

扫码加入连享会微信群,提问交流更方便

✏ 连享会学习群-常见问题解答汇总:
https://gitee.com/arlionn/WD

New! lianxh 命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh