☰
上市公司董监高特征数据1990-2024:从字段编码到面板回归的实操指南
2026/10/21 8:05:55 网站建设 项目流程

简介:本资源为1990至2024年沪深北证A股上市公司董监高个人特征信息表,面向公司治理、金融实证与会计学研究者,以及需要构建高管背景数据库的量化分析人员。数据基于上市公司年报与公告整理,覆盖主板、中小企业板、创业板、科创板及北交所,字段包含证券代码、统计截止日期、人员ID、姓名、性别、年龄、专业、学历、毕业院校、具体职务、任职起止日期、是否领取薪酬、报告期报酬总额、津贴、年末持股数、持股方式、海外背景、金融背景及个人简历等,其中海外背景与金融背景自2008年起有记录,学历与金融背景均附有明确编码说明。资源包共1个docx文件,约52KB,结构紧凑,便于直接查阅与二次整理。目前已有98人学习下载,适合用于高管特征刻画、薪酬与持股分析、海外及金融经历研究等场景,可显著减少手工搜集与清洗成本。

1. 董监高特征表到底能拿来做什么:从一份1990-2024的治理数据说起

如果你手头正在做公司治理、高管薪酬或者企业创新方向的实证研究,大概率绕不开一个基础问题:怎么把“人”的维度量化进模型。上市公司董监高个人特征信息表1990-2024,本质上是把董事、监事、高级管理人员的性别、年龄、专业、学历、毕业院校、具体职务、海外背景、金融背景这些字段,按公司-年度-个人三层结构整理成可回归的面板数据。它解决的不是“有没有数据”的问题,而是“能不能把人的异质性拆成可检验变量”的问题。适合谁用?做CSR、研发投入、并购绩效、股价崩盘风险这类题目,需要控制或解释管理层背景的研究者。我见过太多人卡在第一步:从年报PDF里扒出来的高管简历是一坨非结构化文本,根本没法直接进Stata。

2. 字段拆解与编码逻辑:八个维度怎么变成可回归变量

2.1 性别、年龄、学历:三个最容易被低估的编码坑

性别字段看起来最简单,但实操里最容易被“是否女性”这个虚拟变量带偏。我一般会保留原始性别字段,再生成female=1/0,同时留一个gender_missing标记。为什么?因为1990年代早期年报里高管性别披露率极低,直接drop缺失值会让样本量断崖式下跌。年龄字段的坑在于“出生年份”和“报告年份”的匹配——很多表格只给年龄,你需要用报告年度减去年龄反推出生年,但要注意年报披露的是“截至报告期末”还是“截至年报披露日”,两者差几个月,对年龄分组(比如是否接近退休)会有影响。学历字段更麻烦,专科、本科、硕士、博士、博士后、EMBA、MBA混在一起,我的做法是先做一张映射表,把非标准表述归一到5档:中专及以下、大专、本科、硕士、博士。EMBA和MBA统一归硕士,但单独留一个emba_flag,因为这类学位的信号效应和学术型硕士不一样。

import pandas as pd # 学历归一映射表 edu_map = { '中专': 1, '高中': 1, '大专': 2, '专科': 2, '本科': 3, '学士': 3, '硕士': 4, 'MBA': 4, 'EMBA': 4, '博士': 5, '博士后': 5 } def normalize_education(raw): if pd.isna(raw): return None raw = str(raw).strip() for key, val in edu_map.items(): if key in raw: return val return None # 未匹配的留空,后续人工核查 df['edu_level'] = df['学历'].apply(normalize_education) df['emba_flag'] = df['学历'].str.contains('EMBA', na=False).astype(int)

这段代码的逻辑是先做关键词匹配,再做档位映射。参数说明:edu_map的键是年报里可能出现的各种写法,值是1-5的序数。注意不要用精确匹配,因为年报里经常写“硕士研究生”“博士研究生”,用in做子串匹配更稳。emba_flag单独拎出来,是因为后续做异质性分析时,EMBA背景的高管在战略决策上可能和普通硕士有显著差异。

2.2 毕业院校与海外背景:从文本到虚拟变量的三种策略

毕业院校字段的编码策略取决于你的研究问题。如果只是控制“是否名校”,可以按985/211/双一流做虚拟变量;如果想研究校友网络,就需要保留院校名称做共现分析。我的经验是:先保留原始院校文本,再生成三个变量——school_985、school_211、school_overseas。海外背景字段通常包含“海外留学”“海外工作”“海外经历”等表述,需要区分“海外学习背景”和“海外工作背景”,因为前者影响认知,后者影响资源。金融背景同理,要区分“金融行业从业经历”和“金融学教育背景”,前者是实务资源,后者是知识储备。

# 院校层级标记 df['school_985'] = df['毕业院校'].str.contains('985', na=False).astype(int) df['school_211'] = df['毕业院校'].str.contains('211', na=False).astype(int) # 海外背景细分 df['overseas_edu'] = df['海外背景'].str.contains('留学|学习|学位', na=False).astype(int) df['overseas_work'] = df['海外背景'].str.contains('工作|任职|经历', na=False).astype(int) # 金融背景细分 df['finance_edu'] = df['金融背景'].str.contains('金融学|金融专业|经济学', na=False).astype(int) df['finance_work'] = df['金融背景'].str.contains('银行|证券|基金|保险|信托', na=False).astype(int)

参数说明:str.contains里的正则用|连接多个关键词,na=False保证缺失值不报错。注意985/211的标记不能只看院校名称里有没有数字,因为有些院校名称本身不含这些标签,需要额外维护一张院校层级对照表。海外背景的细分逻辑是:如果描述里出现“留学”“学位”等词,归为教育背景;出现“工作”“任职”归为工作背景。两者可以同时为1,不互斥。

2.3 具体职务与董监高分层:为什么不能只用一个dummy

具体职务字段是这份表里信息密度最高的部分。董事长、副董事长、总经理、副总经理、财务总监、董事会秘书、独立董事、监事长、监事……如果只用一个“是否高管”的dummy,会丢掉大量信息。我的做法是生成三个层级变量:board_level(董事层)、supervisor_level(监事层)、executive_level(经理层),再在每个层级内部生成具体职务的虚拟变量。比如董事长和独立董事都是董事,但权力和激励完全不同。另外要注意“兼任”情况——很多高管同时是董事,这时候需要在两个层级都标记为1,而不是二选一。

# 职务分层 df['is_director'] = df['具体职务'].str.contains('董事', na=False).astype(int) df['is_supervisor'] = df['具体职务'].str.contains('监事', na=False).astype(int) df['is_executive'] = df['具体职务'].str.contains('总经理|总裁|财务总监|董秘|经理', na=False).astype(int) # 关键职务单独标记 df['is_chairman'] = df['具体职务'].str.contains('董事长', na=False).astype(int) df['is_independent'] = df['具体职务'].str.contains('独立董事', na=False).astype(int) df['is_cfo'] = df['具体职务'].str.contains('财务总监|财务负责人', na=False).astype(int)

逻辑说明:is_director和is_executive可以同时为1,因为现实中大量高管兼任董事。is_chairman单独标记是因为董事长在治理研究里通常需要单独控制。is_independent用于识别独立董事,这类角色在关联交易、信息披露等题目里是关键调节变量。参数上,str.contains的关键词列表要根据你手头数据的实际表述调整,比如有些年报写“总会计师”而不是“财务总监”。

3. 从原始表到面板数据:合并、去重与年度匹配的完整流程

3.1 与公司财务数据合并:股票代码和年度两个键都不能少

拿到董监高特征表后,下一步通常是和CSMAR或Wind的财务数据合并。这里最大的坑是股票代码格式不统一——有的表用“000001”,有的用“000001.SZ”,有的用“平安银行”。我一般先做代码标准化:提取6位数字,再根据交易所补后缀。年度匹配要注意,董监高特征表里的“年度”通常指报告年度,而财务数据的“年度”可能指会计年度,两者在绝大多数情况下一致,但遇到年报更正或追溯调整时会错位。合并时用inner join还是left join取决于你的研究设计:如果研究高管背景对绩效的影响,用left join保留所有公司-年度;如果研究特定背景的子样本,可以用inner join。

# 股票代码标准化 def standardize_code(code): code = str(code).strip() digits = ''.join(filter(str.isdigit, code))[:6] if digits.startswith('6'): return digits + '.SH' elif digits.startswith('0') or digits.startswith('3'): return digits + '.SZ' else: return digits df['stock_code'] = df['股票代码'].apply(standardize_code) # 合并财务数据 merged = pd.merge( df, financial_df, on=['stock_code', 'year'], how='left', validate='many_to_one' # 财务数据每个公司-年度只有一条 )

参数说明:validate='many_to_one'用于检查合并键的唯一性,如果财务数据里同一个公司-年度有多条记录,会直接报错而不是静默产生笛卡尔积。这个参数在实操里能帮你省下大量排查时间。合并后要检查merged的行数是否和原表一致,如果变多了,说明财务数据有重复键。

3.2 个人-公司-年度三层去重:同一个人的多条记录怎么处理

董监高特征表最常见的结构是“公司-年度-个人”三层,但同一个人在同一年可能有多条记录(比如同时担任董事和副总经理),也可能同一个人在不同公司任职。去重策略取决于分析层级:如果做公司层面回归,需要把个人特征聚合到公司-年度层面,比如“高管团队中女性比例”“高管平均年龄”“是否有海外背景的高管”;如果做个人层面回归,则需要保留个人-公司-年度唯一记录,把兼任职务合并成一个字段。

# 公司-年度层面聚合 firm_year = df.groupby(['stock_code', 'year']).agg( female_ratio=('female', 'mean'), avg_age=('age', 'mean'), overseas_ratio=('overseas_edu', 'mean'), finance_ratio=('finance_work', 'mean'), team_size=('person_id', 'nunique') ).reset_index() # 个人层面去重:同一人同公司同年合并职务 person_level = df.groupby(['stock_code', 'year', 'person_id']).agg( gender=('female', 'first'), age=('age', 'first'), edu_level=('edu_level', 'max'), positions=('具体职务', lambda x: '|'.join(x.unique())) ).reset_index()

逻辑说明:公司-年度聚合用mean生成比例变量,用nunique计算团队规模。个人层面用max取最高学历,因为同一个人可能同时有硕士和EMBA记录,取最高档更合理。positions字段用|连接所有职务,后续可以用str.contains拆开。注意person_id需要你自己构造,通常用“姓名+出生年份”做唯一标识,因为姓名会重复。

3.3 缺失值处理:什么时候该插补,什么时候该放弃

这份表1990-2024的时间跨度里,早期年份的缺失率极高。我的原则是:核心变量(性别、年龄、职务)缺失超过30%的年份,直接截断样本;辅助变量(海外背景、金融背景)缺失,用0填充并加一个missing_flag。不要用均值插补年龄,因为年龄的缺失往往和公司规模、上市板块相关,均值插补会引入偏误。学历缺失可以用“本科”做众数插补,但同样要加flag。

# 缺失标记 for col in ['overseas_edu', 'finance_work', 'edu_level']: df[col + '_missing'] = df[col].isna().astype(int) # 辅助变量缺失填0 df['overseas_edu'] = df['overseas_edu'].fillna(0) df['finance_work'] = df['finance_work'].fillna(0) # 学历缺失用众数填充 mode_edu = df['edu_level'].mode()[0] df['edu_level'] = df['edu_level'].fillna(mode_edu)

参数说明:missing_flag的作用是在回归里控制缺失值带来的选择性偏误。如果missing_flag显著,说明缺失不是随机的,需要在论文里讨论。学历众数填充只适用于缺失率低于10%的情况,否则建议直接drop。

4. 避坑与排查:这份表在实操中最容易翻车的五个地方

4.1 现象:合并后样本量暴涨,原因:股票代码没标准化,解决:先做代码清洗再merge

这个坑我踩过不止一次。两张表看着都是“000001”,但一张是字符串,一张是数值,pandas默认不相等。更隐蔽的是带后缀和不带后缀的混用。解决方法是merge之前先打印两张表的代码样例,确认格式一致。如果代码里有空格或不可见字符,用str.strip()清掉。

4.2 现象:同一个人在同一年出现两次,原因:兼任职务被拆成多条记录,解决:按person_id聚合

很多原始表把“董事”和“副总经理”拆成两行,导致个人层面出现重复。如果不聚合,做个人层面回归时这个人会被加权两次。解决方法是用groupby+agg合并,职务字段用join连接。注意聚合时要确认哪些字段取first、哪些取max、哪些取mean。

4.3 现象:海外背景变量全为0,原因:关键词匹配没覆盖“海归”“境外”等表述,解决:先做词频统计再定正则

str.contains的关键词列表不能拍脑袋定。我一般先对原始字段做value_counts,看前50个高频表述,再从中提取关键词。比如“海归”“境外学习”“国外经历”这些词如果不加进去,会漏掉大量样本。另外要注意大小写和全半角,英文关键词统一转小写再匹配。

4.4 现象:年龄出现负数或超过100,原因:报告年度和出生年份错位,解决:加合理性校验

年龄字段的异常值通常来自两个地方:一是报告年度填错,二是出生年份反推时用了错误的基准日。我一般会加一个校验:age在25到75之间保留,超出范围的标记为异常并人工核查。如果异常比例超过5%,说明数据源本身有问题,需要考虑换数据源或截断年份。

4.5 现象:学历序数回归不显著,原因:把EMBA和学术硕士混在一起,解决:拆分学位类型

EMBA、MBA、学术硕士在认知模式和社会网络上有本质差异。如果研究的是战略决策或创新投入,EMBA背景的高管可能更偏向实务导向,而学术硕士更偏向技术导向。把两者混在一个“硕士”档里,会稀释效应。解决方法是在学历序数之外,单独加emba_flag和mba_flag。

5. 进阶用法:用这套表做异质性分析和工具变量

5.1 按行业和产权性质分组回归:怎么切样本才有意义

拿到董监高特征表后,最直接的进阶用法是做分组回归。比如按制造业vs非制造业、国企vs民企分组,看高管海外背景对研发投入的影响是否不同。切样本时要注意:分组变量本身不能缺失,否则样本量会骤减。我一般先做全样本回归,再用交互项做分组检验,而不是直接切样本,因为交互项能保留全部样本并直接检验组间差异的显著性。

import statsmodels.formula.api as smf # 全样本回归加交互项 model = smf.ols( 'rd_intensity ~ overseas_ratio + overseas_ratio:soe + controls + C(year) + C(industry)', data=firm_year ).fit(cov_type='cluster', cov_kwds={'groups': firm_year['stock_code']}) print(model.summary())

参数说明:C(year)和C(industry)是固定效应,cov_type='cluster'按公司聚类标准误。交互项overseas_ratio:soe的系数就是组间差异的估计。注意聚类标准误的groups参数要传公司代码,不是公司-年度。

5.2 用海外背景做工具变量:外生性从哪来

海外背景作为工具变量的逻辑是:海外经历影响高管认知,进而影响企业决策,但海外经历本身不直接受企业绩效影响。这个外生性假设是否成立,取决于你的研究问题。如果研究的是企业创新,海外背景可能通过“国际视野”影响创新战略,但海外背景也可能和家庭背景、个人能力相关,存在遗漏变量。我一般会做两个检验:一是用“同行业同年度其他公司海外背景均值”作为工具变量,二是用“高管出生年份的留学政策变化”作为外生冲击。前者需要论证行业层面的海外背景不影响本公司绩效,后者需要政策变化的外生性。

# 构造行业-年度海外背景均值作为IV industry_year_overseas = firm_year.groupby(['industry', 'year'])['overseas_ratio'].transform('mean') firm_year['iv_overseas'] = industry_year_overseas # 两阶段最小二乘 from linearmodels.iv import IV2SLS iv_model = IV2SLS.from_formula( 'rd_intensity ~ 1 + [overseas_ratio ~ iv_overseas] + controls + C(year) + C(industry)', data=firm_year ).fit(cov_type='clustered', clusters=firm_year['stock_code']) print(iv_model.summary)

参数说明:IV2SLS.from_formula里用[overseas_ratio ~ iv_overseas]指定内生变量和工具变量。cov_type='clustered'按公司聚类。注意工具变量的排他性检验:iv_overseas只通过overseas_ratio影响rd_intensity,不直接进入第二阶段。如果行业层面的海外背景均值直接影响企业研发(比如行业竞争压力),排他性就不成立。

5.3 一个我常用的验证习惯:先跑描述性统计再跑回归

不管多急,我都会先跑一遍描述性统计:均值、标准差、分位数、缺失率。这一步能发现80%的数据问题。比如海外背景的均值如果是0.02,说明这个变量几乎没有变异,回归系数再显著也不可信。再比如年龄的标准差如果只有3岁,说明样本被截断得太厉害。描述性统计还能帮你判断要不要缩尾——财务变量通常1%和99%缩尾,但高管特征变量一般不缩尾,因为极端值本身就是信息。

# 描述性统计 desc = firm_year[['female_ratio', 'avg_age', 'overseas_ratio', 'finance_ratio', 'rd_intensity']].describe() print(desc) print(firm_year[['female_ratio', 'overseas_ratio']].isna().mean())

这段代码输出均值、标准差、分位数和缺失率。我一般会把describe()的结果和论文里的样本描述对比,如果差异超过10%,说明样本构造有问题。缺失率超过20%的变量,要么找替代数据,要么在论文里讨论局限性。

这套表的价值不在于字段多,而在于你能不能把字段背后的治理逻辑翻译成可检验的假设。我自己的习惯是:每加一个变量,先问它能不能在理论上影响因变量,再问它在数据里有没有足够的变异。两个问题有一个答不上来,这个变量就不该进模型。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询