公司金融常用Stata代码

公司金融常用Stata代码

使用 Stata 做实证已经有一段时间了,分享一些比较常用的命令,应该大部分的公司金融或者家庭金融论文都能够用到。本人是做公司金融的,所以大部分代码都是和公司研究方面相关的。如有不足,还望补充。

写在前面

后文中采用的模型均为双向固定效应模型,固定了个体和年份
$$
Y = \alpha + \beta X + \eta’Controls + \delta_i + \lambda_t + \varepsilon_{it}
$$
$Controls$ 是一系列的控制变量

1 数据预处理部分

(1)剔除样本

通常我们在论文模型部分会看到剔除金融业企业、剔除单一观测值、剔除主要变量缺失的样本,有时还需要剔除部分年份的样本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
* 1 剔除金融业
drop if industry == "J"
// 有时候我们得到的是证监会分类的三位数代码,比如 J70,这时候就需要提取首字母
drop if substr(industry, 1, 1) == "J"


* 2 剔除样本中的 B 股
// 这个可能是经常被忽略的,尽管在正文部分已经提及了使用的是 A 股样本,但是有时候数据中却还包含 B 股样本
// stkcd 表示股票代码,注意处理成数值型变量而不是字符型
drop if (stkcd >= 200000 & stkcd < 300000)
drop if stkcd >= 900000


* 3 剔除样本中的 ST、SST等股票
// 这个应该是比较常用的操作了,因为股票如果变成 ST,它的名字前面会加上对应的字符
drop if substr(name, 1, 3) == "*ST"
drop if substr(name, 1, 3) == "SST"
drop if substr(name, 1, 2) == "ST"
drop if substr(name, 1, 2) == "PT"


* 4 剔除主要变量缺失的样本
// 这个建议在合并数据后再进行操作
// 主要思路是,把所有的变量假装用来回归,然后直接剔除掉没用上的样本就行了
global all_vars = "Y X $controls" // $controls 是一系列的控制变量,这里用了 Stata 的全局暂元方法
qui reg $all_vars
keep if e(sample)


* 5 剔除单一观测值
// 这个主要用在固定效应中
bysort stkcd: gen single = _N
drop if single <= 1
drop single

(2)合并数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* 1 1:1 匹配
// 如果主文件(master)和导入文件(using)是通过股票代码和年份一一对应的,那么就采用如下的合并数据方法
merge 1:1 stkcd year using "control1.dta"
keep if _m == 3
drop _m
// 需要注意的是,如果在后文中没有使用该导入的数据,那么不建议使用 "keep if _m == 3",因为这会导致样本损失
// 建议使用 "drop if _m == 2" 这样可以尽可能地保留原始样本


* 2 m:1 匹配
// 如果主文件(master)和导入文件(using)不是一一对应的。例如,导入文件是以城市和年份为唯一标识的。
// 由于一个城市有多家企业,所以主文件的多个样本对应了导入文件的一个样本,这个时候就需要 m:1 匹配
merge m:1 city year using "control2.dta"
keep if _m == 3
drop _m

(3)常见的公司金融变量生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 企业规模
gen Size = ln(assets)

// 资产负债率(杠杆率)
gen Leverage = debts / assets

// 资产回报率
gen ROA = 2 * net_return / (L.assets + assets)

// 企业现金流
gen Cashflow = cash / assets

// 企业年龄
// 有时候会采用成立时间而不是IPO时间
gen FirmAge = year - ipo_year

当然,如果做的是 DID 的话,可以采用如下的生成方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
* 1 单期 DID
local treat_province2010 = "广东省, 广西壮族自治区, 上海市, 北京市, 山西省" // 随便打打
inlist2 province, values(`treat_province2010') name(Treat) // inlist2 需要外部安装
replace Treat = 0 if Treat == .
gen Post = (year >= 2010)
gen DID = Treat * Post


* 2 多期 DID
// 假设有三期,分别是
local treat_province2010 = "广东省, 广西壮族自治区, 上海市, 北京市, 山西省" // 随便打打
local treat_province2012 = "重庆市, 山东省, 西藏自治区" // 随便打打
local treat_province2015 = "江西省, 福建省, 湖南省, 河北省" // 随便打打
inlist2 province, values(`treat_province2010') name(Treat2010) // inlist2 需要外部安装
inlist2 province, values(`treat_province2012') name(Treat2012)
inlist2 province, values(`treat_province2015') name(Treat2015)
replace Treat2010 = 0 if Treat2010 == .
replace Treat2012 = 0 if Treat2012 == .
replace Treat2015 = 0 if Treat2015 == .
gen DID = ((Treat2010 == 1) & (year >= 2010)) | ((Treat2012 == 1) & (year >= 2012)) | ((Treat2015 == 1) & (year >= 2015))

(4) 缩尾处理

1
2
3
// 通常需要对连续变量进行 1% 和 99% 处的缩尾处理
// 建议直接 replace,不要生成新的变量,容易选错。。。
winsor2 $continuous_variables, cut(1, 99) replace

(5)在基准回归前

在基准回归前,最好是对一些变量设定暂元,方便后续使用。然后把处理好的数据稳健保存好,以免后续处理不小心剔除了某些样本又要重来。

1
2
3
4
5
6
* 定义全局暂元
global controls = "control1 control2 control3 control4 control5 control6"
global independents = "X $controls"

* 保存数据文件
save "main.dta", replace

2 描述性统计

这部分内容其实没啥好说的。。。代码照抄就是了,/// 表示续行符

1
2
3
sum2docx Y $independents ///
using "描述性统计.docx", replace ///
stats(N mean(%12.3f) sd(%12.3f) min(%12.3f) median(%12.3f) max(%12.3f))

3 基准回归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use "main.dta", replace // 这里开始就可以体现前面保存数据的好处了

// 不放控制变量和固定效应
reghdfe Y X , vce(cluster stkcd) // 个体层面的聚类稳健标准误
est store m1
// 加入控制变量
reghdfe Y $independents ,vce(cluster stkcd)
est store m2
// 加入固定效应
reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd)
est store m3

// 表格输出
reg2docx m1 m2 m3 using "基准回归.docx", replace ///
scalars(N(%20.0fc) r2_a(%9.3f)) b(%9.4f) t(%7.3f) ///
addfe("Firm=YES YES YES" "Year=YES YES YES") ///
mtitles("OLS" "OLS" "OLS") ///
font("Times New Roman", 9)

如果采用的是交乘的方法可以使用 # 符号,c. 声明变量是连续的

1
2
3
4
// 只有交乘项
reghdfe Y c.X#c.D $controls , absorb(stkcd year) vce(cluster stkcd)
// 包含单独项
reghdfe Y c.X##c.D $controls , absorb(stkcd year) vce(cluster stkcd)

4 分组回归和系数差异检验

分组回归的方法常用于异质性分析,但由于近年来中介效应模型备受批评,所以也有越来越多的学者采用分组或交乘的方式来做机制检验,因此掌握分组回归和系数差异检验的方法非常重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
* 1 按照已有的 0-1 虚拟变量分组,例如“是否国企”
// 非国企
reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd), if SOE == 0
est store m1
// 国企
reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd), if SOE == 1
est store m2

// 表格输出
reg2docx m1 m2 using "国企分组回归.docx", replace ///
scalars(N(%20.0fc) r2_a(%9.3f)) b(%9.4f) t(%7.3f) ///
addfe("Firm=YES YES" "Year=YES YES") ///
mtitles("Non-SOEs" "SOEs") ///
font("Times New Roman", 9)

// 系数差异检验
// (1)chowtest,其实就是引入交乘项,看交乘项的系数方向对不对,显不显著就行了
reghdfe Y c.X##c.SOE $controls , absorb(stkcd year) vce(cluster stkcd)
// (2)似无相关模型SUR的检验
// 这个貌似不能用 reghdfe,所以还是算了。。。
// (3)bdiff
// 这个是我常用的命令,详细使用方法可以看连玉君老师的推文
bdiff, group(SOE) model(reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd)) ///
reps(1000) bdec(4) pdec(4) bsample seed(123456)


* 2 按照中位数分组
// 如果是需要按照某个连续变量的中位数进行分组,那么就需要采用一些小小的处理方法

qui sum C, detail
gen dC = (C >= r(p50)) if !missing(C)
// 这里的 dC 就是计算出来的基于中位数的分组变量

// 低于中位数的组别
reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd), if dC == 0
est store m1
// 高于中位数的组别
reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd), if dC == 1
est store m2

// 表格输出
reg2docx m1 m2 using "国企分组回归.docx", replace ///
scalars(N(%20.0fc) r2_a(%9.3f)) b(%9.4f) t(%7.3f) ///
addfe("Firm=YES YES" "Year=YES YES") ///
mtitles("Low" "High") ///
font("Times New Roman", 9)

// 系数差异检验
bdiff, group(dC) model(reghdfe Y $independents , absorb(stkcd year) vce(cluster stkcd)) ///
reps(1000) bdec(4) pdec(4) bsample seed(123456)


* 3 按照分位数分组
// 有时候中位数分组不显著(就是这么直接),一些作者会采用极端的分组方法。
// 例如,最低的 1/3 定为 0,最高的 1/3 定为 1,中间的 1/3 剔除
xtile dC = C, nq(3)
drop if dC == 2
replace dC = 0 if dC == 1
replace dC = 1 if dC == 3
// 后面就和前面类似了


* 4 按照某个连续变量在某一年的中位数进行分组
// 这种分类方式其实还是比较少见的。。。大家为了显著都不容易啊。。。
// 例如现在有 2008-2020 的上市公司数据,按照变量 C 在 2015 年的中位数分组
// 按照哪一年其实也是有讲究的,如果做的是 DID,一般选取事件的前一年
// 思路就是把 2015 年的数据填充到其他年份,然后再找中位数分组
gen dc = C
gen temp = dc if year == 2015
bys code: egen gvar_in_year = min(temp)
egen gvar_in_year_median = median(gvar_in_year)
gen dC = (gvar_in_year > gvar_in_year_median) if !mi(gvar_in_year)
drop dc temp gvar_in_year gvar_in_year_median
* 更建议先在原始文件里先处理成指定年份再 merge,然后直接用全样本中位数即可
* 不建议先在原始文件里分组再 merge,可能会导致两组样本差异很大


* 5 按照是否高于同年/行业/城市/省份中位数进行分组
// 这个相对前一个来说简单一些,按行业分别找中位数然后再用变量跟它对比就好
bysort industry: egen temp = median(C) // 行业中位数
gen dC = (C >= temp) if !missing(temp)
drop temp

bysort year: egen temp = median(C) // 年份中位数
gen dC = (C >= temp) if !missing(temp)
drop temp


* 6 按照是否高于同年行业/城市/省份中位数进行分组
// 这种分组方式感觉怪怪的其实。。。不建议使用
bysort industry year: egen temp = median(C)
gen dC = (C >= r(p50)) if !missing(temp)
drop temp

5 稳健性检验

这部分内容建议去看下知乎上专门的文章,我这里就抛砖引玉,主要是介绍下有哪些稳健性检验方法。。。

(1)平行趋势检验

DID 必备检验之一,继续沿用前面的 DID 例子。如果大家感兴趣的话,之后可能会专门出一期怎么调平行趋势的文章。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 假设有三期,分别是
local treat_province2010 = "广东省, 广西壮族自治区, 上海市, 北京市, 山西省" // 随便打打
local treat_province2012 = "重庆市, 山东省, 西藏自治区" // 随便打打
local treat_province2015 = "江西省, 福建省, 湖南省, 河北省" // 随便打打
inlist2 province, values(`treat_province2010') name(Treat2010) // inlist2 需要外部安装
inlist2 province, values(`treat_province2012') name(Treat2012)
inlist2 province, values(`treat_province2015') name(Treat2015)

gen current = ((Treat2010 == 1) & (year == 2010)) | ((Treat2012 == 1) & (year == 2012)) | ((Treat2015 == 1) & (year == 2015))
gen Treat_Year = year if current == 1
bysort stkcd: egen Treat_Year2 = sum(Treat_Year)
drop Treat_Year
rename Treat_Year2 Treat_Year

forvalue i = 4(-1)2{
gen Before`i'_ = cond(year - Treat_Year <= -`i' & Treat_Year != ., 1, 0)
}

forvalue i = 4(-1)1{
gen Before`i' = cond(year - Treat_Year == -`i', 1, 0)
}

forvalue i = 0(1)5{
gen After`i' = cond(year - Treat_Year == `i', 1, 0)
}

forvalue i = 2(1)5{
gen After`i'_ = cond(year - Treat_Year >= `i' & Treat_Year != ., 1, 0)
}

reghdfe Y Before3_ Before2 Before1 After0 After1 After2 After3_ $controls , absorb(stkcd year) vce(cluster stkcd)
est store m1
coefplot m1, keep(Before3_ Before2 Before1 After0 After1 After2 After3_) ///
levels(95) ///
vertical yline(0) xline(4, lp(dash)) ///
xtitle("period") ytitle("coefficient") ///
addplot(line @b @at) ciopts(lpattern(dash) ///
recast(rcap) msize(medium)) ///
msymbol(circle_hollow) ///
scheme(s1mono)

(2)安慰剂检验

这个也是 DID 必备的检验之一,通常可以分为对 $Treat$ 随机、对 $Post$ 随机和对 $DID$ 随机。这里我以单期 $DID$ 对 $Treat$ 随机为例子。(多期 DID 好像只能对 DID 随机)

1
2
3
4
5
6
7
8
local treat_province2010 = "广东省, 广西壮族自治区, 上海市, 北京市, 山西省" // 随便打打
inlist2 province, values(`treat_province2010') name(Treat) // inlist2 需要外部安装
replace Treat = 0 if Treat == .
gen Post = (year >= 2010)

permute Treat beta = _b[c.Treat#c.Post] t = (_b[c.Treat#c.Post] / _se[c.Treat#c.Post]), ///
reps(1000) rseed(123456) saving("simulations.dta", replace): ///
reghdfe Y c.Treat#c.Post $controls , absorb(stkcd year) vce(cluster stkcd)

“beta = _b[c.Treat#c.Post] t = (_b[c.Treat#c.Post] / _se[c.Treat#c.Post])” 表示分别将交互项的系数和 t 值记录下来,然后再对输出文件 simulations.dta 做描述性统计就可以得到安慰剂检验的结果。当然,也可以直接查看 permute 的输出结果,可以参考我的另一篇文章

(3)工具变量法

工具变量法采用 ivreghdfe 命令,该命令还可以输出一系列的弱工具变量检验结果。当然要注意的是这个命令没办法使用稳健标准误。

1
ivreghdfe Y (X = IV) $controls , absorb(stkcd year)

还有另一种方法是通过手动 2SLS 回归,这种方法还能够分别输出两阶段的回归结果,所以更加推荐使用。

1
2
3
reghdfe X IV $controls , absorb(stkcd year) vce(cluster stkcd)
predict X_hat
reghdfe Y X_hat $controls , absorb(stkcd year) vce(cluster stkcd)

如果一阶段 F 值大于经验值 10,通常就可以认为不存在弱工具变量问题了。但是其他的弱工具变量检验就需要自己去操作了。

(4)遗漏变量问题

遗漏变量也是内生性的一种来源,而且近几年的文献越来越关注遗漏变量可能导致的后门路径问题。一般分为两种解决方法。

第一种是增加更多的控制变量以控制遗漏混淆变量影响。比如加入更多的固定效应,其他层面的控制变量等等。

第二种是证明遗漏变量对原文结论没有很大影响,常见的方法有 Oster 检验(2019)和 Frank(2000)提出的 Konfound 检验。Oster检验可以见我前面提到的那篇文章,Frank(2000)的方法可以看下连玉君老师的推文。

https://www.lianxh.cn/news/576be9d47ceeb.html

https://zhuanlan.zhihu.com/p/513830106

https://www.lianxh.cn/news/4832e3735dc81.html

6 总结

以上便是一些常见的实证论文中的 Stata 操作,当然难免会有遗漏。如有问题还希望大家指出,一起进步。

作者

CodeFox

发布于

2023-01-06

更新于

2024-06-08

许可协议

评论