名师讲堂|使用 Stata 测算上市公司人力资本流动、创新产出及流动方向

今天给大家分享使用 Stata 测算上市公司人力资本流动、创新产出及流动方向的方法。该指标来源于「空气污染、人力资本流动与创新活力 —— 基于个体专利发明的经验证据」,附件中也提供了该文献的 pdf 文件。

在下面的代码中,上市公司专利数据使用的是之前分享的:

1985~2024 年上市公司与专利数据匹配结果(版本3, 含申请、授权信息):https://rstata.duanshu.com/#/brief/course/04100321f88b411f90429be934934bff

论文中提到使用 AQI 作为空气质量的测度,不过这个指标通常只有 2014 年之后的,平台上也有分享:

2014~2024 年中国各省市区县分年、分月、逐日 AQI 面板数据:https://rstata.duanshu.com/#/brief/course/aa737515f4234c89bcc593a85ad50bc2

如果想进行更长年度的研究,也可以考虑使用 PM2.5 代替,例如这里我使用的就是:

1980 年 1 月~2024 年 12 月各省市区县地表 PM2.5 质量浓度:https://rstata.duanshu.com/#/brief/course/76698bc838ef49c5ab57633aa64b4e06

首先我们读取专利数据进行初步处理:

  1. 仅保留授权专利;
  2. 根据公开公告号和申请号去除重复专利(专利数据里面同时包含了很多专利的申请和授权公告);
  3. 去除外观设计专利;
  4. 只保留需要的变量。

实际上原论文也保留了外观设计专利。如果想把外观设计专利纳入分析中,可以保留该部分专利,然后外观设计专利分类号采用 LOC 分类,结构为大类(两位数字)-小类(两位数字)。可以使用前两位数字作为下面的 class 变量。另外也有一些论文认为发明专利的创新性更强,仅选择发明专利进行研究,这样也是不错的,更有说服力,也可以节省一些计算量。

cd "~/Desktop/使用 Stata 测算上市公司人力资本流动、创新产出及流动方向"

clear all
*- 1. 读取并清洗专利数据
import delimited "2010~2014年上市公司与专利数据匹配结果.csv", clear bindquotes(strict) maxquotedrows(unlimited) stringcols(_all)
foreach i of varlist _all {
replace `i' = "" if `i' == "NA"
}
destring 年份, replace

*- 保留授权专利
drop if missing(授权公告号) | 授权公告号 == "" | 授权公告号 == "NA"

*- 选择变量
keep newipzlid 股票代码 公开公告号 申请号 ipc 年份 专利类型 市 发明人

*- 保留发明和实用新型专利
keep if index(专利类型, "发明") | index(专利类型, "实用新型")

*- 清理公开公告号 - 移除末尾字母
replace 公开公告号 = ustrregexra(公开公告号, "[A-Z]$", "")

*- 去重
duplicates drop 股票代码 公开公告号, force
duplicates drop 申请号, force

*- 删除不需要的变量
drop 公开公告号 申请号 专利类型
save dfall, replace

每个专利都可能包含多个 IPC 以及多个申请人,下面使用 split + reshape 进行拆分:

use dfall, clear

*- 拆分 IPC(多个分类号)
split ipc, parse(";") gen(ipc_)
drop ipc
gen long_id = _n
reshape long ipc_, i(long_id) j(ipc_no)
*- 这个比 gahter ipc_* 更快,所以这里还是用 reshape long
drop if missing(ipc_)
ren ipc_ ipc
replace ipc = subinstr(ipc, " ", "", .)
replace ipc = subinstr(ipc, ".", "", .)
gen class = substr(ipc, 1, 3)
drop ipc_no long_id

*- 拆分发明人(多个发明人)
split 发明人, parse(";") gen(inventor_)
drop 发明人
gen long_id = _n
reshape long inventor_, i(long_id) j(inventor_no)
drop if missing(inventor_)
rename inventor_ 发明人
replace 发明人 = subinstr(发明人, " ", "", .)
drop inventor_no long_id

*- 去重
drop ipc
duplicates drop _all, force
save patent_data, replace

再计算创新产出前,我们需要先去除发明人重名的。

根据原文逻辑:同一年、同一发明人、不同城市、不同IPC大类 -> 视为重名:

*- 3. 剔除发明人重名问题
use patent_data, clear

*- 计算每个发明人每年的城市数和大类数
bysort 年份 发明人: egen city_count = nvals(市)
bysort 年份 发明人: egen class_count = nvals(class)

*- 重名条件:同一年同一发明人,且城市数 > 1,且大类数 > 1
drop if city_count > 1 & class_count > 1

drop city_count class_count

save patent_clean, replace

*- 查看剔除情况
use patent_data, clear
count
*> 1,408,696
local original_count = r(N)

use patent_clean, clear
count
*> 1,038,381
local clean_count = r(N)

disp "原始数据量:`original_count'"
*> 原始数据量:1408696
disp "剔除重名后数据量:`clean_count'"
*> 剔除重名后数据量:1038381

*- 总发明人数
use patent_data, clear
duplicates drop 发明人, force
count
*> 200,691
local original_inventors = r(N)

use patent_clean, clear
duplicates drop 发明人, force
count
*> 197,041
local clean_inventors = r(N)

local removed_ratio = (`original_inventors' - `clean_inventors') / `original_inventors'
display "重名的人占:" `removed_ratio'
*> 重名的人占:.01818716

然后分组统计每年每位发明人申请的专利总数即为创新产出:

*- 此处代码需下载讲义材料查看~

list in 1/10

*> +------------------------------+
*> | 发明人 年份 patent |
*> |------------------------------|
*> 1. | A·R·纳戈帕尔 2012 1 |
*> 2. | A·仁加萨米 2010 2 |
*> 3. | A·德特默斯 2013 1 |
*> 4. | A·德特默斯 2014 3 |
*> 5. | A·查克拉 2014 3 |
*> |------------------------------|
*> 6. | A·米勒 2013 1 |
*> 7. | A·维斯戈尔 2013 1 |
*> 8. | A·维斯戈尔 2014 3 |
*> 9. | A·马赛拉 2011 2 |
*> 10. | B.A.彼得斯 2014 1 |
*> +------------------------------+

save innovation_output, replace

如果同一个发明人在不同的年份(如 2005 年和 2006 年)出现在两个不同的上市公司(A 和 B),如果 2006 年后该发明人不再出现 在 A 企业,同时 2006 年前 B 企业也没有该发明人,则认为发明人在 A 和 B 之间发生了流动:

*- 5. 识别人力资本流动(Flow)
use patent_clean, clear

*- 过滤不公开的发明人
drop if regexm(发明人, "不公开") | regexm(发明人, "不公布")

*- 构建发明人-年份-企业面板
keep 发明人 年份 股票代码 市
duplicates drop

*- 按发明人和年份排序
gsort 发明人 年份

*- 标记流动
bysort 发明人 (年份): gen prev_firm = 股票代码[_n-1]
bysort 发明人 (年份): gen next_firm = 股票代码[_n+1]
bysort 发明人 (年份): gen prev_city = 市[_n-1]
bysort 发明人 (年份): gen next_city = 市[_n+1]

gen flow = 0
replace flow = 1 if 股票代码 != prev_firm & 股票代码 != next_firm & !missing(prev_firm) & !missing(next_firm)

save flow_data, replace

结合空气质量数据就可以构造流动方向了。Flow_up 表示发明人 流向空气质量较好的城市,此时 Flow_up 取值为 1,否则为 0:

*- 6. 构建流动方向变量(Flow_up, Flow_down)
*- 读取城市空气质量数据
import excel "1980年1月~2024年12月各城市地表PM2.5质量浓度(微克每立方米).xlsx", firstrow clear

*- 提取年份
gen 年份 = year(month)
*- 计算年度平均
collapse (mean) pm25 = 地表PM25质量浓度, by(市 年份)
ren 市 merge_city
*- 只保留研究期间
keep if 年份 >= 2010 & 年份 <= 2014
save city_air_quality, replace

*- 合并空气质量数据到流动数据
use flow_data, clear

*- 合并前一个城市的空气质量
rename prev_city merge_city
merge m:1 merge_city 年份 using "city_air_quality.dta", keepusing(pm25) keep(match master)
rename pm25 prev_pm25
rename merge_city prev_city

drop _m

*- 合并当前城市的空气质量
rename 市 merge_city
merge m:1 merge_city 年份 using "city_air_quality.dta", keepusing(pm25) keep(match master)
rename pm25 curr_pm25
rename merge_city 市
drop _m

*- 计算流动方向
gen flow_up = 0
gen flow_down = 0
replace flow_up = 1 if flow == 1 & curr_pm25 < prev_pm25 & !missing(curr_pm25, prev_pm25)
replace flow_down = 1 if flow == 1 & curr_pm25 > prev_pm25 & !missing(curr_pm25, prev_pm25)

replace flow_up = 0 if missing(flow_up)
replace flow_down = 0 if missing(flow_down)

save flow_direction, replace

再添加公司信息,方便下面的分公司汇总:

*- 7. 构建最终数据集
use patent_clean, clear
*- 合并创新产出数据
merge m:1 发明人 年份 using innovation_output, keep(match master) nogen

*- 此处代码需下载讲义材料查看~

分公司、年份统计流入流出人员数量。继续使用前面的 flow_data 或 final_data,这里可以根据需要分三种方法汇总:

  1. 基于流动数据直接统计;
  2. 根据发明人进行精确的流动识别;
  3. 按流动方向进一步细分。

方法一:基于流动数据直接统计

*- 统计流入企业的发明人数量
use flow_data, clear
keep if flow == 1
bysort 股票代码 年份: egen inflow_count = count(发明人)
keep 股票代码 年份 inflow_count
duplicates drop
save inflow_by_firm, replace

*- 统计流出企业的发明人数量
use flow_data, clear
keep if flow == 1
rename 股票代码 target_firm
rename prev_firm 股票代码
bysort 股票代码 年份: egen outflow_count = count(发明人)
keep 股票代码 年份 outflow_count
duplicates drop
save outflow_by_firm, replace

*- 此处代码需下载讲义材料查看~

方法二:使用更精确的流动识别

*- 方法二:使用更精确的流动识别
use flow_data, clear
keep if flow == 1
gen flow_year = 年份
gen source_firm = prev_firm
gen target_firm = 股票代码
keep 发明人 flow_year source_firm target_firm 市 prev_city
save precise_flow_records, replace

*- 此处代码需下载讲义材料查看~

方法三:按流动方向进一步细分

*- 方法三:按流动方向进一步细分
*- 按流动方向统计流入
use flow_direction, clear
keep if flow == 1
bysort 股票代码 年份: egen inflow_total = count(发明人)
bysort 股票代码 年份: egen inflow_up = sum(flow_up)
bysort 股票代码 年份: egen inflow_down = sum(flow_down)
keep 股票代码 年份 inflow_total inflow_up inflow_down
duplicates drop
save directional_inflow, replace

*- 按流动方向统计流出
use flow_direction, clear
keep if flow == 1
rename 股票代码 target_firm
rename prev_firm 股票代码
bysort 股票代码 年份: egen outflow_total = count(发明人)
bysort 股票代码 年份: egen outflow_up = sum(flow_up)
bysort 股票代码 年份: egen outflow_down = sum(flow_down)
keep 股票代码 年份 outflow_total outflow_up outflow_down
duplicates drop
save directional_outflow, replace

*- 此处代码需下载讲义材料查看~

最后还可以计算一些统计信息:

*- 9. 输出统计摘要
use firm_mobility_detailed, clear

summarize inflow_count
local avg_inflow = r(mean)
summarize outflow_count
local avg_outflow = r(mean)

count if net_flow > 0
local positive_net_ratio = r(N)/_N * 100

display "企业流动统计摘要:"
display "平均每年流入发明人数量:`avg_inflow'"
display "平均每年流出发明人数量:`avg_outflow'"
display "净流动为正的企业比例:`positive_net_ratio'%"

*- 按年份查看流动趋势
collapse (mean) avg_inflow = inflow_count avg_outflow = outflow_count ///
(sum) total_inflow = inflow_count total_outflow = outflow_count, by(年份)

list

*> +--------------------------------------------------+
*> | 年份 avg_in~w avg_ou~w to~nflow to~tflow |
*> |--------------------------------------------------|
*> 1. | 2010 .094175 .094175 367 367 |
*> 2. | 2011 .3684886 .3684886 1436 1436 |
*> 3. | 2012 .5545291 .5545291 2161 2161 |
*> 4. | 2013 .5332307 .5332307 2078 2078 |
*> 5. | 2014 .2083654 .2083654 812 812 |
*> +--------------------------------------------------+

点击这里跳转到 RStata 短书平台获取附件:名师讲堂|使用 Stata 测算上市公司人力资本流动、创新产出及流动方向

评论