资讯动态

上市公司董监高特征数据1990-2024:从字段编码到面板回归的实操指南

发布时间:2026/10/21 8:06:01 来源:尧图企业网站定制
简介本资源为1990至2024年沪深北证A股上市公司董监高个人特征信息表面向公司治理、金融实证与会计学研究者以及需要构建高管背景数据库的量化分析人员。数据基于上市公司年报与公告整理覆盖主板、中小企业板、创业板、科创板及北交所字段包含证券代码、统计截止日期、人员ID、姓名、性别、年龄、专业、学历、毕业院校、具体职务、任职起止日期、是否领取薪酬、报告期报酬总额、津贴、年末持股数、持股方式、海外背景、金融背景及个人简历等其中海外背景与金融背景自2008年起有记录学历与金融背景均附有明确编码说明。资源包共1个docx文件约52KB结构紧凑便于直接查阅与二次整理。目前已有98人学习下载适合用于高管特征刻画、薪酬与持股分析、海外及金融经历研究等场景可显著减少手工搜集与清洗成本。1. 董监高特征表到底能拿来做什么从一份1990-2024的治理数据说起如果你手头正在做公司治理、高管薪酬或者企业创新方向的实证研究大概率绕不开一个基础问题怎么把“人”的维度量化进模型。上市公司董监高个人特征信息表1990-2024本质上是把董事、监事、高级管理人员的性别、年龄、专业、学历、毕业院校、具体职务、海外背景、金融背景这些字段按公司-年度-个人三层结构整理成可回归的面板数据。它解决的不是“有没有数据”的问题而是“能不能把人的异质性拆成可检验变量”的问题。适合谁用做CSR、研发投入、并购绩效、股价崩盘风险这类题目需要控制或解释管理层背景的研究者。我见过太多人卡在第一步从年报PDF里扒出来的高管简历是一坨非结构化文本根本没法直接进Stata。2. 字段拆解与编码逻辑八个维度怎么变成可回归变量2.1 性别、年龄、学历三个最容易被低估的编码坑性别字段看起来最简单但实操里最容易被“是否女性”这个虚拟变量带偏。我一般会保留原始性别字段再生成female1/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, naFalse).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, naFalse).astype(int) df[school_211] df[毕业院校].str.contains(211, naFalse).astype(int) # 海外背景细分 df[overseas_edu] df[海外背景].str.contains(留学|学习|学位, naFalse).astype(int) df[overseas_work] df[海外背景].str.contains(工作|任职|经历, naFalse).astype(int) # 金融背景细分 df[finance_edu] df[金融背景].str.contains(金融学|金融专业|经济学, naFalse).astype(int) df[finance_work] df[金融背景].str.contains(银行|证券|基金|保险|信托, naFalse).astype(int)参数说明str.contains里的正则用|连接多个关键词naFalse保证缺失值不报错。注意985/211的标记不能只看院校名称里有没有数字因为有些院校名称本身不含这些标签需要额外维护一张院校层级对照表。海外背景的细分逻辑是如果描述里出现“留学”“学位”等词归为教育背景出现“工作”“任职”归为工作背景。两者可以同时为1不互斥。2.3 具体职务与董监高分层为什么不能只用一个dummy具体职务字段是这份表里信息密度最高的部分。董事长、副董事长、总经理、副总经理、财务总监、董事会秘书、独立董事、监事长、监事……如果只用一个“是否高管”的dummy会丢掉大量信息。我的做法是生成三个层级变量board_level董事层、supervisor_level监事层、executive_level经理层再在每个层级内部生成具体职务的虚拟变量。比如董事长和独立董事都是董事但权力和激励完全不同。另外要注意“兼任”情况——很多高管同时是董事这时候需要在两个层级都标记为1而不是二选一。# 职务分层 df[is_director] df[具体职务].str.contains(董事, naFalse).astype(int) df[is_supervisor] df[具体职务].str.contains(监事, naFalse).astype(int) df[is_executive] df[具体职务].str.contains(总经理|总裁|财务总监|董秘|经理, naFalse).astype(int) # 关键职务单独标记 df[is_chairman] df[具体职务].str.contains(董事长, naFalse).astype(int) df[is_independent] df[具体职务].str.contains(独立董事, naFalse).astype(int) df[is_cfo] df[具体职务].str.contains(财务总监|财务负责人, naFalse).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], howleft, validatemany_to_one # 财务数据每个公司-年度只有一条 )参数说明validatemany_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聚合很多原始表把“董事”和“副总经理”拆成两行导致个人层面出现重复。如果不聚合做个人层面回归时这个人会被加权两次。解决方法是用groupbyagg合并职务字段用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), datafirm_year ).fit(cov_typecluster, cov_kwds{groups: firm_year[stock_code]}) print(model.summary())参数说明C(year)和C(industry)是固定效应cov_typecluster按公司聚类标准误。交互项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), datafirm_year ).fit(cov_typeclustered, clustersfirm_year[stock_code]) print(iv_model.summary)参数说明IV2SLS.from_formula里用[overseas_ratio ~ iv_overseas]指定内生变量和工具变量。cov_typeclustered按公司聚类。注意工具变量的排他性检验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%的变量要么找替代数据要么在论文里讨论局限性。这套表的价值不在于字段多而在于你能不能把字段背后的治理逻辑翻译成可检验的假设。我自己的习惯是每加一个变量先问它能不能在理论上影响因变量再问它在数据里有没有足够的变异。两个问题有一个答不上来这个变量就不该进模型。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑