Stata连享会 主页 || 视频 || 推文 || 知乎 || Bilibili 站
温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验。
New!
lianxh
命令发布了:
随时搜索推文、Stata 资源。安装:
. ssc install lianxh
详情参见帮助文件 (有惊喜):
. help lianxh
连享会新命令:cnssc
,ihelp
,rdbalance
,gitee
,installpkg
⛳ Stata 系列推文:
作者: 邹恬华 ( 对外经济贸易大学 )
邮箱: zoutianhua2022@163.com
Source: Michael Stepner 团队的 代码风格文档:
Github 仓库地址:https://github.com/michaelstepner/healthinequality-code
目录 [TOC]
使用杂乱无章的代码写作习惯就像背上高息的“技术债务”,不仅使得今后的代码维护变得困难而耗时,而且使得他人几乎不可能重复文章的结果。
本文翻译并整理了 Michael Stepner 团队的代码风格文档,并复制了其团队 2016 年发表在 JAMA 的 论文,以飨读者。代码与数据下载地址如超链接所示。文中介绍的代码风格主要包括以下要点:
开始我们的优雅代码之旅吧!
如何组织一个项目的代码、数据、文档和结果的方法论被称为代码风格。
当您独自处理一个短期项目时,不统一代码风格不会有严重的后果;但当许多人在一个项目上长期合作时,团队共同学习一套代码风格将会有效避免可能的混乱。具体而言,在大型合作研究项目中,您可能面临以下问题:
下文描述的代码风格有助于确保上述的问题能够顺利解决,从而节约时间并减少错误。
本文通过介绍健康不平等这一项目实例来阐述原作者研究团队的代码风格。
这一项目的代码由三个“管线” ( 英文原文 pipelines,亦可理解为流水线、流程 ) 组成,包括:
这些管线串连起了原始数据到研究结果 ( 表格、图片、数据等 ) 的研究流程。研究流程如以下流程图所示:
原始数据 ( raw data ) ---通过初始数据生成管线 ( mortality_init ) 和派生数据生成管线 ( mortality_datagen ) ---> 获得派生数据 ( derived data ) ---通过分析管线 ( mortality ) ---> 获得临时文件 ( scratch ) 和结果 ( results )
上述三个“管线”在具体运行时通过 project
命令 ( -project- command ) 实现,该命令属于自动化工具 ( build automation tool ) 的一种 ( 将在本文第五部分具体展开描述,译者注 ) 。在存储库的根目录下,包含三个 do 文件:
mortality_init.do
) 将作者无权公开的原始死亡率数据处理成可以公开的汇总数据。汇总数据也即本文使用的初始数据。mortality_datagen.do
) 将原始数据处理为派生数据。mortality.do
) 将原始数据和派生数据处理成结果。此外,还有第四个管线单元测试管线 mortality_tests.do
用来验证代码是否按照预期工作。复制文章结果时无需运行这一管线。
一个好的文件夹组织方式有助于更好地存放和提取文件。本文提到的健康不平等项目主要包含以下五个文件夹:
以下具体阐述各文件夹包含的内容。
除定义管线的主代码外,所有的代码都应存储在名为代码 ( code ) 的文件夹中。当然,为了便于浏览与查找,代码文件夹包括子文件夹。子文件夹可以根据需要随时自由安排。
代码文件夹中有三个特殊的子文件夹:
binscatter
命令。
本文的代码可以自动将自己撰写的 ado 文件 ( code/ado ) 与 SSC 上下载的 ado 文件 ( code/ado_ssc ) 加入 Stata 的 ado 路径的最前端,从而使得 Stata 在运行过程中能够自动找到这些 ado 文件的位置 ( 详见 code/set_environment.do 中的 adopath ++
命令,译者注 ) 。同时,当每次使用到 maptile
命令时,本文都会使用 geofolder()
选项来指明地图模板的 ado 文件 ( code/ado_maptile_geo ) 所在路径。
所有的数据被存储在以下两个文件夹:
如何区分原始数据与派生数据?可以被代码文件夹中的代码导出的数据被称为派生数据,其它数据即为原始数据。也即,如果删除了代码 ( code ) 和原始数据 ( data/raw ) 文件夹之外的所有内容,之后再运行代码,代码将会运行出所有内容,包括派生数据 ( data/derived ) 文件夹、临时文件 ( scratch ) 文件夹和结果 ( result ) 文件夹的所有内容。原始数据与派生数据的划分与数据的“原始”或“处理”程度无关;原始数据文件夹中的数据可能是较早时候派生的,比如与已发表论文相关的数据集。
所有的数据都应放在具有描述性命名 ( 即对数据内容有简要说明 ) 的原始数据 ( data/raw ) 和派生数据 ( data/derived ) 的子文件夹中。
原始数据 ( data/raw ) 文件夹中的每一个子文件夹都应该包含一个名为 source.txt 的文件,以说明数据的获取方式与获取日期。如果是公共数据,描述应该详细到陌生人可以按照提示找到对应数据;如果是非公开数据,描述应该详细到新的合作者可以在不联系您的情况下找到对应数据。例如,非公开数据的 source.txt
可以这样写:“Raj Chetty 在 2015 年 8 月 19 日通过电子邮件从 Emmanuel Saez 收到了 elasticities.dta。该文件来自 Saez 的论文“应税收入的弹性:证据和影响” ( 2002 年 ) 。 ”
在一个项目的过程中,我们将产生许多分析,例如大量的表格和数字。当项目结束时,其中的一些将作为结果 ( 如“图 6B”或“表 2” ) 。但在此之前,它们将被称为“预期寿命最低四分位地图”或“预期寿命前 10 大通勤区列表”。这是我们的临时工作,它将位于临时文件夹中。
临时文件夹中的所有内容都存储在描述性命名的子文件夹中,以便随时查看。如何确定数据集属于派生数据 ( data/derived ) 还是临时文件 ( scratch ) ?如果数据集仅用于查看或者它是分析中的临时步骤,则应该被划分为临时文件。如果数据集要被代码再次使用,则应该被划分为派生数据 ( data/derived ) 。
临时文件夹中的所有文件名都应该是描述性的。文件名指定文件包含的内容 ( 例如:一季度预期寿命水平相关性.png 或 Q1 LE levels correlations.png
),而不是它在论文中出现的位置(例如:图 7.png 或 Figure 7.png
) 。
将在论文中汇报的图与表格存储在结果 ( results ) 文件夹中。
本文使用一个 do 文件 ( code/assign_fig_tab_numbers.do
) 将文件从临时 ( scratch ) 文件夹移动到结果 ( results ) 文件夹中,并重命名。例如,这个 do 文件会复制临时 ( scratch ) 文件夹中的 scratch/Correlations with CZ life expectancy/Q1 LE levels correlations.png
到结果 ( results ) 文件夹,并重命名为 results/Figure 7.png
。
需要注意的是, code/assign_fig_tab_numbers.do
应该是所有代码中唯一一个可以在结果 ( results ) 文件夹中创建文件的 do 文件。如果在编辑或修订过程中需要对于图或表格进行重新编号时,只需要修改 code/assign_fig_tab_numbers.do
。如果有人需要找到某个特定图或表格来自何处,这个 do 文件也可以作为一个简单的参考。
项目中主要包括五个主要文件夹:
当运行存储于代码 ( code ) 文件夹中的代码时,数据在其它文件夹中的流动方向为: 原始数据 ( data/raw ) --->派生数据 ( data/derived ) --->临时文件 ( scratch ) --->结果 ( results )
对每个文件夹的简要介绍:
项目中的每个 do 文件都应该满足简短、独立、专用的特点,即每个 do 文件都有一个可以用一句话解释的用途。每个特点的具体要求如下:
这样的代码风格可以使得修改代码不再那么令人生畏、耗时且容易出错。
当然,上述包含多个 do 文件的代码风格也存在缺点,即您需要追踪以下问题:
幸运的是,上述问题可以通过自动构建系统 ( automated build system ) 解决 ( 详见本文第五部分,译者注 ) 。
根目录即包含代码 ( code ) 、原始数据 ( data/raw ) 、派生数据 ( data/derived ) 、临时文件 ( scratch ) 和结果 ( results ) 五个子文件的文件路径。每个 do 文件都应当能够获取根目录。然而,每个人电脑路径不尽相同,因此直接在 do 文件中绝对路径是不恰当的。相反,它应该被添加到您电脑的 Stata 副本的配置中,并在代码中使用相对路径。具体步骤如下: ( 具体实现案例与可能遇到的问题,详见本文附录部分,译者注 )
doedit "c(sysdir_personal)'/profile.do
命令,以编辑个人文件夹中的 profile.do 文件。global mortality_root "<path_to_mortality_root_folder>"
命令并保存 do 文件。这使得你的电脑中的$root 被设定为<path_to_mortality_root_folder>。ls "$mortality_root/"
命令,结果栏中应当包含代码 ( code ) 、原始数据 ( data/raw ) 等文件夹名称,这说明设置成功。在代码编写过程中,你可以使用相对路径引用。例如:
use "$root/data/derived/le_estimates/cz_leBY_gnd_hhincquartile.dta", clear
$root
代表您电脑设置中的相对路径,也即在其他人的电脑中不一样。
每一个 do 文件都应该以以下代码开头:
* Set $root
return clear
capture project, doinfo
if (_rc==0 & !mi(r(pname))) global root `r(pdir)' // using -project-
else { // running directly
if ("${mortality_root}"=="") do `"`c(sysdir_personal)'profile.do"'
do "${mortality_root}/code/set_environment.do"
}
这些代码保证每个 do 文件既可以单独运行,也可以在项目中与其它 do 文件一起运行(通过 project, build
实现)。它所做的最重要的事情是确保 $root
正确定义全局。这些代码的具体作用如下:
return clear
) ,并且报告正在运行的项目 ( capture project, doinfo
)mortality.do
) 所在的目录。$mortality_root
的路径$root
的值设置为 $mortality_root
,并将 code/ado 和 code/ado_ssc 添加至 Stata 的 adopath ( 从而使得 Stata 能够在运行时自动找到这些 ado 文件的位置 ) ,同时可以暂时禁止 project
命令除了上述八行代码以外,典型的 do 文件也会包含以下代码 ( 或其中的一部分代码 ) :
* 设置您需要使用的全局变量
global derived "${root}/data/derived"
* 根据系统设置输出图片的格式
* c(os)会返回该电脑的系统,可能的返回值包括"MacOSX", "Unix", or "Windows"
if (c(os)=="Windows") global img wmf //如果是Windows系统,输出wmf格式 ( 将全局变量 img 设为 wmf )
else global img png //如果是其它系统,输出 png 格式(将全局变量 img 设为 png )
* 创建需要的文件夹
cap mkdir "${root}/scratch/Correlations with CZ life expectancy"
cap mkdir "${root}/scratch/Correlations with CZ life expectancy/data"
* 删除临时数据
cap erase "${root}/scratch/Correlations with CZ life expectancy/Reported correlations.csv"
* 导入配置设置
project, original("$root/code/set_bootstrap.do")
include "$root/code/set_bootstrap.do"
/*** Estimate CZ-level correlations between life expectancy (levels and trends)
and local covariates.
***/
第一部分,设置全局变量。这一部分代码会定义在 do 文件中您可能需要使用的全局变量。在这一 do 文件中使用到的全局变量必须在同一个 do 文件中声明 ( 除了根目录 $root ,因为已经在前八行声明过了 ) 。这是因为 project
命令会在运行每个 do 文件时清除全局变量,所以实际上每个全局变量仅仅在本 do 文件中有效。但是如果您交互式地运行 do 文件的片段,全局变量将保存在内存中,这在开发和调试期间通常很有用。
第二部分,创建所需文件夹。这一部分代码会创建需要写入的文件夹。如果尝试写入不存在的文件夹,程序将崩溃。因此,在 do 文件 中自动创建文件夹可确保代码即使在第一次运行或删除了过去创建的文件夹的情况下都能正确运行。 cap
命令表示如果该文件夹不存在时,则使用 mkdir
命令创建新文件夹。
第三部分,删除临时数据,以保证每次运行 do 文件的时候都重新计算。
第四部分,导入配置设置。比如本文使用 set_bootstrap.do
定义了迭代次数全局变量。通过从单个文件中导入这些设置,而不是在每个 do 文件的顶部定义,可以确保设置在不同 do 文件中始终保持一致,并且在需要改变设置时只需在单个文件中修改即可。
* 示例:set_bootstrap.do内容如下
* 迭代次数设置为1000次
global reps 1000
* 样本数设置为625000
global splitobs 625000
最后一部分,注释部分。注释由 /***
开头,由 ***/
结尾,简要解释了 do 文件的用途。此注释旨在让尚未阅读 do 文件的人快速了解其用途。
变量名称应该能够清楚地表明变量的含义,这能够为之后阅读代码的人节约更多事件。试图通过缩短变量名来节约写代码时间是不合算的。
例如,您估计了一个模型,并将截距和斜率保存成变量,不要将其命名为 a 和 b,而是应当命名为intercept ( 截距 ) 和 slope ( 斜率 ) 。进一步的,如果这两个参数是由 Gompertz 模型得出,这两个变量可以命名为gomp_intercept和gomp_slope。
Gentzkow 和 Shapiro 为选择变量名称提供了以下经验法则:
默认情况下,变量、函数、文件等的名称应该由完整的单词组成。仅在您确信不熟悉您的代码的读者能够理解并且没有歧义时才使用缩写。大多数经济学家会理解 “income_percap” 是指人均收入,因此无需写出 income_percapita。但是根据上下文的不同,income_pc 可能意味着很多不同的东西,因此不是恰当的变量名。st、cnty 和 hhld 之类的缩写变量名只要在整个代码中含义保持一致也是可以使用的。但是使用 blk_income 来表示人口普查区块中的收入可能会令人困惑,因此不是恰当的变量名。
避免多个变量具有相似含义的情况。比如名为 state_level_analysis.do
与 state_level_analysisb.do
的 do 文件,或名为 x 和 xx 的变量。
如果仅从变量名不能清晰解释其内涵时,可以使用 label
命令为变量添加描述性的标签。例如,您可能有一个名为 age 的变量的年度数据,该变量的标签为“12 月 31 日的年龄”。
综上,如果变量名称有歧义,应当使用更好的变量名称,并更新所有代码。至少,也可以给变量一个描述性标签。
文件与文件夹的命名也是同理。以下列出一些例子:
c_1.png
是不恰当的文件名。correlations.png
是不恰当的文件名,如果您使用了不止一次的相关性分析。Q1 LE levels correlations.png
是一个很好的文件名。med_v_ext.do
是不恰当的文件名。Decompose mortality into medical v external causes.do
是一个好的文件名。data/raw/intercensal pop
是不恰当的文件夹名。data/raw/Census County Intercensal Estimates 2000-2010
是一个好的文件夹名。通过描述性地命名文件和文件夹,我们能够在不阅读代码的情况下查找内容。一个命名良好的文件可以在无需重命名的情况下附加到电子邮件中,收件人也能很好地了解文件内容。
我们创建的每个数据集通常都应该有一组经过深思熟虑的独特 ID 变量。通俗地说,我们经常称其为数据的“级别”:一个数据集具有“个人级别”数据,另一个具有“年份级别”数据数据,另一个有“国家级”数据。
例如,本文有一个按区域、性别和家庭收入四分位数划分的预期寿命数据集。对于该数据集,变量 cz、gnd、hh_inc_q 是一组独特的 ID 变量,也即区域-性别-家庭收入四分位数是数据集的“级别”。
了解数据集的独特 ID 是非常重要的,不仅能够表明数据集描述的内容,也能表明其如何与其它数据集合并。
具有独特 ID 的数据集具有以下特征:
可以使用 isid
命令检验上述两个特征是否成立,例如 isid cz gnd hh_inc_q
。当允许缺失值时,也即放松特征 2 时,可以使用 isid cz gnd hh_inc_q, missok
。
将独特 ID 作为文件名通常是有意义的,尤其是需要存储多个聚合级别的数据的时候。比如本文的预期寿命文件夹包含以下数据集,您可以从文件名轻松看出数据集在什么级别上进行聚合。例如, cty_leBY_gnd_hhincquartile.dta
数据集的一组独特 ID 是 cty、leBY、gnd 和 hhincquartile。
cty_leBY_gnd_hhincquartile.dta
cty_leBY_gnd.dta
cz_leBY_gnd_hhincquartile.dta
cz_leBY_gnd.dta
cz_leBY_year_gnd_hhincquartile.dta
national_leBY_gnd_hhincpctile.dta
national_leBY_year_gnd_hhincpctile.dta
st_leBY_gnd_hhincquartile.dta
st_leBY_gnd.dta
st_leBY_year_gnd_hhincquartile.dta
本节的拓展内容可以参考 Gentzkow 和 Shapiro 文章的第五章 。
您编写的代码通常依赖于数据的特征。比如,在您的数据集中 x 是非负数,因此您的代码直接对其取对数而不会造成样本数量的损失。但是,数据的情况可能会在未来发生变化,而这些变化可能造成意想不到的后果。因此在写代码时,最好明确使用 assert
、 isid
等命令将假设条件写入代码。例如:
* Full sample regression of y on log(x)
assert x>0 //不满足 x>0 条件时报错
gen log_x=log(x)
reg y log_x
* Collapse away income dimension
isid age gnd hh_inc_pctile //不满足独特ID时报错
collapse (sum) pop_*, by(age gnd)
这些命令使得代码在不满足前提条件时报错,从而有助于避免问题。此外,这也使得代码更易阅读,读者能够更好了解每一步骤需满足的前提条件。相比之下,写注释的方式,例如 * x is always positive!
,则会令读者花费更多时间检查前提条件是否满足,因此是不恰当的。
需要特别注意的是,在使用 merge
命令进行数据集合并时,应当使用 assert()
或 assert() keep()
选项来明确期望的合并结果。这是因为合并特别容易以意想不到的方式发生错误,我们希望在错误发生时得到通知。知道预期合并结果对于写代码的人也是很有益的。
那么我们的程序中应当包含哪些前提条件?头脑风暴所有可能出错场景是没有必要的,前提条件的价值取决于:
每当您遇到代码出错并修复错误时,请编写测试以保证该错误不会再未来再次出现。
重复代码难以阅读和维护,因此容易出现隐藏错误。因此,当您需要您的代码重复执行相同的操作时 ( 对于多个组、多个样本等 ) ,您应该编写一个循环或一个函数,而不是复制代码来做同样的事情。
例如,假设您已经编写了代码来估计和生成对种族调整的预期寿命 ( le_raceadj
) ,现在您需要为未经调整的预期寿命 ( le_unadj
) 生成相同的数字。复制和粘贴代码很诱人,只需将原代码中所有的 le_raceadjto
替换成 le_unadj
。但是复制代码有严重的缺点:
什么时候应当避免代码重复?
重复并不完全是坏事,甚至像“不要重复自己”这样的好习惯有时也过分了。重复代码在以下情况中会变得很麻烦并且容易出错:
把一个简单的步骤写两次是完全合理的:
xtile incdecile_m = income if gender=="m", nq(10)
xtile incdecile_f = income if gender=="f", nq(10)
但是如果你要重复同样的步骤 15 次,你应该写一个循环:
forvalues y=2001/2015 {
xtile incdecile_`y' = income if year==`y', nq(10)
}
如果这个过程涉及很多行代码,你应该使用一个循环,即使它只重复两次:
foreach g in "m" "f" {
assert income >= 0 if gender=="`g'"
xtile incdecile_`g' = income if gender=="`g'" & income>0, nq(10)
replace incdecile_`g' = 0 if gender=="`g'" & income==0
label var incdecile_`g' "Deciles of positive income"
}
如何避免重复代码
我们通常使用以下三种方式避免重复代码:
foreach
或 forvalues
命令会执行循环,并在每次循环时改变一个变量的值。program
定义一个程序。然后,您可以在调用程序时传递参数以指定多个变量的值。具体语法可以参考 help syntax
。 program
具体应用可以参考原文代码 code/raceshares/national_racepopBY_year_age_gnd_incpctile.do 与 code/raceshares/national_racepopBY_year_age_gnd.do。compute_racefracs
的 ado 文件,将其存储在 code/ado 路径下,并在多个 do 文件中调用。注意需要将 code/ado 文件添加至 Stata 的 adopath 中。当然,您不需要从一开始就决定编写循环、程序还是 ado 文件。您只需要自然地编写程序,当代码需要重复时,将其转换成一个循环;当代码在每次重复时需要改变多个变量,将其转换为程序;当该程序在多个 do 文件中都有用时,将其转换为 ado 文件。
一些技巧
if
/ else if
/ else
。这些语句可以使得编程者更容易看出重复代码间的不同之处。local g "m"
使得本来遍历 m 与 f 的代码只遍历 m。*foreach g in "m" "f" {
local g "m"
...lots of code...
}
program define <program_name>
之前添加一句 cap program drop <program_name>
。每次运行 do 文件时,这将自动删除并重新定义程序。它允许您在调试时多次运行 do 文件。否则,当你重新运行它时,do 文件 会崩溃,告诉你程序已经被定义。但在 ado 文件中, cap program drop
是不必要的。program define <program_name>
行的正下方,以 /***
开头,以 ***/
结尾。如果代码缩进良好并且将长行代码分成多行,则代码更容易阅读。关于如何缩进你的代码,以及如何打断长行,有很多约定。许多编码人员对他们使用的约定有着强烈的偏好。
使用缩进和换行符来生成看起来干净的代码的方法不止一种。但值得遵循以下两条准则:
当您在代码注释、 source.txt
、文件或文件名中写入日期时,请使用 YYYY-MM-DD 格式,因为它比其他所有格式都好。这是一个全球标准,所有人都可以阅读和书写它,而不会混淆日期或月份是在前,且字母排序相当于时间排序。
原文代码在 Windows 系统输出 .wmf
格式图片,在其它系统输出 .png
格式代码。地图图片是唯一的例外,如果输出 .wmf
格式,文件会过大,因此使用 maptile, savegraph()
命令导出 .png
格式文件。
为了让代码根据操作系统自动切换文件格式,请在 do 开头加上以下代码:
* Set convenient globals
if (c(os)=="Windows") global img wmf
else global img png
然后在后续代码中使用 .${img}
作为拓展名。地图除外,直接使用 .png
作为拓展名。
对于论文中的每个图形,我们都会生成一个 CSV 文件,来存储该图像绘制中使用的数据。该数据应该是图中所示内容的直接表示。例如:
binscatter
,CSV 文件保存的是图中散点的坐标,而非绘图时使用的原数据。 ( binscatter
是用来绘制条件期望的 Stata 命令,详见连享会主页 → Stata:分仓散点图绘制-binscatter-binscatter2,译者注 )我们保存这个 CSV 文档是出于以下目的:
论文中报告的每个数字都应直接出现在结果文件夹中的 CSV 文件中。
这要求无需进一步计算即可获得论文中报告的数字。例如,如果我们报告一个 p 值,那么 p 值必须直接出现在 CSV 文件中,而不能仅仅汇报系数和标准误差是不够的。
数字可以通过三种方式出现在结果文件夹中:
Figure 7
论文正文中描述的相关性的系数和 p 值。尽管 p 值没有直接出现在图中,但它们已存储在与该图关联的 CSV 中。scalarout
命令将其输出到 CSV。
code/lifeexpectancy/le_correlations.do
提供了一个很好的例子。在 do 文件的开头,CSV 文件被删除;计算得到 CSV 文件后,它被写入 scalarout
以便在 do 文件中重复使用;在 do 文件的最后,它被使用 project, creates()
命令以加入项目构建。scalarout, fmt(%9.Nf)
选项。assign_fig_tab_numbers.do
将 CSV 复制到结果文件夹中,命名为 Reported numbers - <内容的简要描述>
本文代码使用 Github 平台进行存储。这使得每个历史版本的代码都得到存储,并且能够比较新旧版本间的不同。同时这也使得团队协作成为可能,团队成员在不同电脑终端上工作,并将最新版本的代码上传至 Github。
在将修改后的代码上传 Github 之前,应当确保:
mortality.do
是分析管线的主 do 文件 ) 。以便新添加的 do 文件在项目构建时运行。project <管线名称>, build
以构建项目。这样,您可以确保当其他人从存储库中提取您的最新提交时,他们会获得能够运行的代码。当您提交未构建的代码时,软件开发人员将其称为“破坏构建”。
为 Stata 研究项目组织代码的经典方法是将所有代码放在一个 do 文件中。在那种风格中,如何生成结果很清楚:从上到下运行 do 文件。但是,在一个巨大的 do 文件中协作和维护一个大型项目变得异常困难。只有全职编写代码的人才能希望了解整个 do 文件的结构以及所有部分如何组合在一起——即便如此,也相当耗时。您将面临如下的问题:
因此,将代码拆分为多个 do 文件是很自然的。但是接下来的挑战是弄清楚所有的 do 文件是如何相互关联的。再一次,只有全职编写代码的人才能希望了解整个 do 文件集合的结构以及所有部分如何组合在一起,您仍然会面临相似的问题。
在任何大型编码项目中,“了解整个 do 文件集合的结构以及所有部分如何组合在一起”并不是人类头脑中要做的工作。它占用了我们的时间,而且我们不是很擅长。相反,我们将通过使用“自动构建系统”将该工作转移到我们的计算机上。这样做对许多研究人员来说是新事物,但我们落后于软件开发人员数十年。经典的构建自动化工具 make
最初是在 1976 年编写的。
我们编写的每个 do 文件不仅简短而且重点突出,而且还是一个独立的模块。协作者可以编辑任何 do 文件,并且知道该代码仅通过它保存的文件影响其他代码和结果。do 文件仅通过它加载的文件受到其他代码的影响。当我们编写 do 文件时,我们将告诉自动构建系统我们的代码加载和保存哪些文件。我们还将使用构建脚本 ( 也称为“主执行文件”或“管线” ) 告诉自动构建系统运行我们的执行文件的顺序。
在告诉自动化构建系统如何运行我们的 do 文件以及每个 do 文件加载和保存哪些文件之后,自动化构建系统可以:
通过软件处理这些任务,没有一个人需要了解整个 do 文件集合以及所有部分如何组合在一起。您可以专注于您正在处理的 do 文件,这很容易管理,因为它们简短、独立、有唯一的用途。如果您更改正在编辑的 do 文件的输出,您可以查询构建软件以找出哪些后续文件将受到影响。进行更改后,您可以运行构建并知道所有结果都已更新,无需等待重新运行任何不受您的更改影响的代码。
我们所有的代码都是使用 Robert Picard 为 Stata 创建的自动构建工具 project
运行的。尽管有更通用的自动化构建工具和更强大的自动化构建工具,但对 Stata 使用者而言, project
特别方便。使用自动化构建工具最具挑战性的部分是正确记录代码依赖或创建的所有文件。 project
有助于将这些信息直接记录在您的 do 文件中。
我强烈建议您阅读 help project
以了解 project
功能及其提供的功能的全部细节。我将在这里介绍其重点功能。当您运行诸如 project mortality, build
的构建命令时,项目会检查自上次构建以来哪些文件已更改。然后它会自动运行任何已更改或依赖于已更改文件的 do 文件。例如,如果您更改生成一个数据集的代码,并且另一段代码使用该数据集生成结果,project 将重新运行两者以确保更新最终结果,不依赖这些结果的其他代码将不会重新运行。
构建 ( build ) 命令
为了实现以上功能, project
要求每个 do 文件都指明其加载或生成的相关文件,这可以使用构建 ( build ) 命令实现。每一次加载文件 ( 比如数据集 ) 或生成文件 ( 比如数据集、表格或图片等 ) 时,都应当运行构建命令。构建命令主要有以下三种类型:
project, original(filepath)
意味着您正在使用不在此项目中生成的文件。
project, relies_on(filepath)
表示您正在引用不在此项目中生成的文件,例如 PDF 或文本文件。您引用的这个文件不会影响代码或结果,您只是将它作为您正在使用的过程或数据的重要参考。在实践中,这与 project, original(filepath)
作用相同。project, uses(filepath)
意味着您正在使用在此项目中生成的文件。project, creates(filepath)
意味着您刚刚创建了这个文件。默认情况下,这些构建命令中的每一个都会自动清除内存中的数据集。通常这不会有问题,因为您的代码通常看起来像这样:
project, original("$root/data/raw_data/some data.dta")
use "$root/data/raw_data/some data.dta", clear
<do lots of things>
save "$root/data/derived_data/cleaned data.dta"
project, creates("$root/data/derived_data/cleaned data.dta")
注意 project, original
在 use
命令之前, project, creates
在 save
命令之后。所以清除加载的数据是无关紧要的。但有时您不希望加载的数据被清除,在这种情况下您需要将 preserve
选项添加到构建命令中。一些常见的情况是:
忽略 preserve
选项是首次运行代码时代码崩溃的最常见原因。在提交前构建项目有助于注意到这个问题。另一种方法是给每个构建命令都添加 preserve
选项,这会减慢构建速度。
构建命令与文件夹的对应关系
在本文介绍的文件夹构建方法下,构建命令与访问的文件夹存在直接关联:
project, original
命令加载project, uses
命令加载project, creates
命令生成实际上,在本文提到的健康不平等项目中,情况会稍微复杂一些,因为该项目中构建通过多个管线来构建。
但是即使您弄混了 original
或 uses
命令也无伤大雅, project
会报错并指出该错误。
记录对 ado 文件的依赖关系
如果某个 do 文件中运行了在 code/ado 文件夹中的 ado 文件里定义的代码,则称这个 do 文件依赖于这一 ado 文件。例如,当 do 文件中使用了 compute_racefracs
,且 code/ado/compute_racefracs.ado 文件发生更改时,do 文件需要重新运行。因此您需要使用 project, original(code/ado/compute_racefracs.ado)
记录这一依赖关系。
对于 do 文件对 code/ado_ssc 或 code/ado_maptile_geo 文件夹中 ado 文件的依赖关系,不要特别担心。由于该代码不是在项目中编写的,因此不太可能更改。
在实践中,记住为 ado 文件程序的依赖项添加构建命令比记住其他构建命令要困难得多。试着意识到这一点并记住它们。但根据我们的经验,它们有时会被遗忘是不可避免的。因此,如果您正在更新 code/ado 中的程序,通常值得仔细检查所有使用该程序的 do 文件是否声明了它们对 ado 文件的依赖关系。如果程序名称与众不同,您可以通过使用 Atom、Sublime Text 或 Notepad++ 等编辑器在所有 do 文件中搜索程序名称来轻松完成此操作。
除了构建命令以外的功能
大多数情况下,您只使用 project
的 project, build
命令。但是 project
也有其它有用的功能:
project, list(concordance)
将列出项目使用或创建的每个文件,并在每个文件下列出使用或创建它的 do 文件。这一列表会保存到 archive/list 路径下的文本文件中。这对于跟踪文件的创建方式以及更改的文件可能影响哪些代码和结果很有用。project, replicate
将构建项目,将项目创建的所有文件移动到名为 replicate 的文件夹中,然后再次构建项目并打印一份报告,列出两次构建之间不同的所有文件。
collapse
命令基于双精度 ( double ) 。这种情况是无需担心的,但你也可以使用 float()
或 recast float <varlist>, force
命令转换为浮点数 ( float ) 。project, share(<sharename>, alltime nocreated)
将在存档 ( archive ) 路径下创建一个文件夹,其中包含从头开始构建项目所需的所有文件。这在创建复制文件以在线发布时很有用。
project, setup
并指向存档路径下的主 do 文件,您应该可以通过运行 project, build
在存档路径下构建项目。但您也可能发现有一些依赖项没有使用构建命令声明,此时您需要在 do 文件中补充声明。再次建议阅读 help project
以了解 project
的全部功能。
Github 仓库地址:https://github.com/michaelstepner/healthinequality-code
数据下载。代码 与 数据下载地址如超链接所示。在本文的示例中,根目录为"D:/example",也即下载的代码与数据被放置于"D:/example"文件夹下,您可以选择任意路径。需要注意的是,请将下载的代码与数据文件夹中的文件而非整个文件夹放置进根目录,也即"D:/example"下包括 code、data 和 scratch 等文件夹,而非 healthinequality-code-main 和 health_ineq_replication_dataonly 文件夹。
命令行输入 sysdir
,查看您电脑中 Stata 的 PERSONAL 路径
运行 doedit "
c(sysdir_personal)'/profile.do"` ,这会弹出一个 do 文件编辑器
在 do 文件编辑器中写入 global mortality_root "<path_to_mortality_root>"
,其中 <path_to_mortality_root>
是您想指定的根目录。以根目录选取为"D:/example"为例,则该语句为 global mortality_root "D:/example"
保存 do 文件。如弹出设置文件存储路径选项,将其取名为 profile.do,存储至 PERSONAL 路径下。
重启 Stata,如果上述操作成功,结果窗口应该有一行字"running [...]/profile.do"
命令行输入 ls "$mortality_root/"
,您应该可以看到代码与数据文件,这说明配置成功
命令行输入 project, setup
,Stata 会弹出一个选项框。文件路径选择"D:/example/mortality.do"。如果您需要 text log 文件,而非 SMCL log 文件,勾选选项框。最后点击 OK。
再次运行 project, setup
,此次文件路径选择"D:/example/mortality_datagen.do"
运行 project mortality_datagen, build
。原文重复次数设置为 1000 次,如果您直接运行可能导致运行速度较慢。可以在"D:/example/code/set_bootstrap.do"中将 global reps 1000
改成更小的数,这可以使得运行速度加快。
《美国经济评论》The American Economic Review ( AER )
Angelini, P., & Generale, A. 2008. On the evolution of firm size distributions. American Economic Review, 98(1), 426-38. -Link-, -PDF-, -Github-replication-
Farber, H. S., Rothstein, J., & Valletta, R. G. 2015. The effect of extended unemployment insurance benefits: Evidence from the 2012-2013 phase-out. American Economic Review, 105(5), 171-76. -Link-, -PDF-, -Github-replication-
《经济学季刊》Quarterly Journal of Economics ( QJE )
Kogan, L., Papanikolaou, D., Seru, A. and Stoffman, N., 2017. Technological innovation, resource allocation, and growth. Quarterly Journal of Economics, 132(2), pp. 665-712. -Link-, -PDF-, -Github-replication-
Coppola A, Maggiori M, Neiman B, et al. 2021, Redrawing the map of global capital flows: The role of cross-border financing and tax havens[J]. The Quarterly Journal of Economics, 136(3): 1499-1556. -Link-, -PDF-, -Github-replication-
连享会代码复现仓库:
Note:产生如下推文列表的 Stata 命令为:
lianxh 重现 复现
安装最新版lianxh
命令:
ssc install lianxh, replace
免费公开课
最新课程-直播课
专题 | 嘉宾 | 直播/回看视频 |
---|---|---|
⭐ 最新专题 | 文本分析、机器学习、效率专题、生存分析等 | |
研究设计 | 连玉君 | 我的特斯拉-实证研究设计,-幻灯片- |
面板模型 | 连玉君 | 动态面板模型,-幻灯片- |
面板模型 | 连玉君 | 直击面板数据模型 [免费公开课,2小时] |
⛳ 课程主页
⛳ 课程主页
关于我们
课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法
等
连享会小程序:扫一扫,看推文,看视频……
扫码加入连享会微信群,提问交流更方便
✏ 连享会-常见问题解答:
✨ https://gitee.com/lianxh/Course/wikis
New!
lianxh
命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh