简介这份资源是CSMAR中国上市公司财务报表数据库的说明文档面向会计、金融、经济管理领域的研究者、高校师生及数据分析从业者用于了解该数据库的结构设计与字段规范辅助开展上市公司财务数据的实证研究与建模分析。包内仅含1个doc文件大小约856KB以文字与表格形式系统梳理数据库的报表分类、字段提取方式及数据范围便于快速查阅与引用。文档详细介绍了以2006年企业会计准则为框架的报表体系涵盖资产负债表、利润表、现金流量表直接法与间接法文件并说明如何用「会计期间」区分年、中、季报及年初年末数据同时讲解新旧准则下股东权益、非流动资产等科目的对应处理以及交易性金融资产、商誉等新增科目的列示方式。此外还涉及财务附注数据库的29张表、430个字段及应收款项账龄明细等内容。目前已有211人学习适合需要理解CSMAR数据结构、规范财务指标口径的研究人员参考使用。1. 从一份“能改的 doc”说起CSMAR 财务库到底装了什么如果你正在做上市公司财务方向的实证研究大概率绕不开一个名字——CSMAR。但很多人第一次拿到手的不是数据库客户端而是一份CSMAR中国上市公司财务报表数据库.doc。别小看这份文档它其实是整套数据库的“说明书字段字典结构设计稿”把财务年报、中报、季报、金融财务、B股财务五大库的字段逻辑、报表分类、会计期间规则全写进去了。它解决的核心问题是让你在正式跑回归之前先搞清楚每个字段从哪来、怎么对齐、跨年怎么比。适合谁适合正在做面板数据清洗、财务指标构建、审计意见文本分析的研究生和量化从业者。我见过太多人跳过这份文档直接开跑结果在“股东权益合计”这种科目上翻车——2007 年前后口径完全不同不读文档根本发现不了。2. 会计期间与报表类型两个字段决定你能不能对齐面板2.1 会计期间不是日期是“报表身份”很多人把“会计期间”当成普通日期字段直接pd.to_datetime一转就完事。这是第一个大坑。在这套库里会计期间承担的是“报表身份标识”的功能它同时区分三件事年/中/季报、年初/年末、以及是否属于同一财政年度。文档里写得很清楚2007-12-31表示 2007 年年报2007-03-31表示 2007 年第一季季报而2007-01-01表示的是 2007 年年初数据——注意年初数据不是上年年末它是资产负债表期初列报值。如果你把年初和年末混在一起做时间序列整个面板的滞后项全错。常见做法是先把会计期间拆成三个辅助列import pandas as pd # 假设原始表 df 中有 会计期间 和 报表类型 两列 df[会计期间] pd.to_datetime(df[会计期间]) # 1. 报表频率根据月日判断 def freq_flag(dt): md (dt.month, dt.day) if md (12, 31): return 年报 elif md (6, 30): return 中报 elif md in [(3, 31), (9, 30)]: return 季报 elif md (1, 1): return 年初 else: return 其他 df[报表频率] df[会计期间].apply(freq_flag) # 2. 财政年度直接取年份 df[财政年度] df[会计期间].dt.year # 3. 时点标记年初 or 年末 df[时点] df[会计期间].apply( lambda x: 年初 if (x.month, x.day) (1, 1) else 期末 )逻辑说明freq_flag把 12-31、6-30、3-31/9-30、1-1 分别映射到年报、中报、季报、年初。参数上唯一需要留意的是部分早期数据可能存在非标准日期比如 2001 年之前的季报披露不规范跑完value_counts()后如果出现“其他”要单独拉出来核对别直接 drop。2.2 报表类型 A/B合并与母公司的分叉口报表类型只有两个值A合并报表B母公司报表。看起来简单但选错类型是高频翻车点。合并报表包含子公司母公司报表只反映母公司自身。做偿债能力分析通常用合并报表做母公司现金流归集可能用 B。文档里所有示例默认给的是 A但你的研究设计如果涉及“母公司对子公司的资金占用”就必须切到 B。我一般会在数据加载阶段就按报表类型拆成两张宽表避免后续 merge 时把 A 和 B 混在一起# 按报表类型拆分 df_a df[df[报表类型] A].copy() df_b df[df[报表类型] B].copy() # 检查每个证券代码在两种类型下的记录数 check df.groupby([证券代码, 报表类型]).size().unstack(fill_value0) print(check.describe())如果发现某些证券代码只有 A 没有 B或者 B 的记录数远少于 A这是正常的——早期很多公司不披露母公司报表。但如果你做的是配对样本研究就要在样本筛选阶段把不配对的公司剔除而不是在回归阶段才发现样本量对不上。3. 科目口径变迁2007 年前后怎么接、怎么断3.1 “股东权益合计”的口径断裂这是文档里花最大篇幅讲的一件事也是实际清洗中最容易出错的地方。2007 年新准则之前“股东权益合计”只包含归属于母公司所有者的权益少数股东权益是单独列示的不属于“股东权益合计”。2007 年之后新准则把少数股东权益并入了“股东权益合计”同时新增了“归属于母公司所有者权益合计”这个字段。这意味着什么如果你直接拿 2005 年和 2008 年的“股东权益合计”做纵向对比2005 年的值偏小2008 年的值偏大差异不是公司经营变化而是口径变了。处理方式有两种。第一种是统一到旧口径2007 年之后的数据用“归属于母公司所有者权益合计”替代“股东权益合计”。第二种是统一到新口径2007 年之前的数据把“股东权益合计”加上“少数股东权益”。我一般用第一种因为“归属于母公司”这个口径在后续的 ROE 计算中更常用。# 统一股东权益口径统一到归属于母公司口径 df[股东权益_统一] df.apply( lambda row: row[归属于母公司所有者权益合计] if pd.notna(row.get(归属于母公司所有者权益合计)) else row[股东权益合计], axis1 ) # 标记口径来源方便回溯 df[权益口径] df[归属于母公司所有者权益合计].apply( lambda x: 新准则 if pd.notna(x) else 旧准则 )参数说明归属于母公司所有者权益合计这个字段在 2007 年之前的记录中为空所以pd.notna判断是安全的。但要注意部分 2007 年之后的记录也可能因为披露缺失而为空这时候 fallback 到“股东权益合计”会引入口径不一致建议对这部分记录打标后单独检查。3.2 取消科目的保留与映射文档提到“短期投资”等旧准则科目在新准则中取消了但数据库采取了保留策略。这对做长面板的人是好事——你不用去别的地方找 2006 年之前的短期投资数据。但问题是新准则下的“交易性金融资产”和旧准则的“短期投资”并不是一一对应关系。常见做法是建一张科目映射表把旧科目映射到新科目体系下最接近的类别同时在论文里说明映射规则。比如旧准则科目新准则对应科目映射说明短期投资交易性金融资产仅当持有目的为交易性时长期投资合计非流动资产合计文档明确归入非流动资产固定资产合计固定资产净额口径基本一致无形资产合计无形资产净额需扣除累计摊销差异这张表不是让你直接替换字段而是让你在构建跨年指标时知道哪些科目可以合并、哪些必须分开。我见过有人把“长期投资合计”和“持有至到期投资”加在一起算结果 2007 年前后重复计算指标直接爆炸。4. 财务指标与附注库208 个指标怎么选、29 张表怎么连4.1 同一指标多种算法A 版和 B 版的区别财务指标分析库里同一个指标名可能对应多个算法。文档以“应收账款周转率”和“存货周转率”为例A 版用期末余额做分母B 版用平均占用额做分母。这不是可有可无的选项而是直接影响你结论的变量定义。# 应收账款周转率 A 版营业收入 / 应收账款期末余额 df[应收账款周转率_A] df[营业收入] / df[应收账款净额] # 应收账款周转率 B 版营业收入 / 应收账款平均占用额 df[应收账款平均占用额] (df[应收账款净额] df[应收账款净额].shift(1)) / 2 df[应收账款周转率_B] df[营业收入] / df[应收账款平均占用额]逻辑说明B 版需要按证券代码分组后做 shift否则会把不同公司的数据混在一起。正确写法是先sort_values([证券代码, 会计期间])再groupby(证券代码)[应收账款净额].shift(1)。参数上如果某公司中间有年度缺失shift 会跨年错位建议在 shift 之前先补齐年度索引或标记缺失。选 A 还是 B如果样本期内公司规模波动大用 B 更稳健如果只是做截面分析A 更直接。关键是全文统一别在描述性统计用 A、回归用 B。4.2 附注库的 29 张表常规与特殊分开处理财务附注库有 29 张表、430 个字段文档特别强调“常规性和特殊性数据分开”。比如应收款项表里常规明细按账龄划分特殊明细单独成文件记录特殊计提。这个设计的好处是你做建模分析时直接用常规表批量提取特殊表只在做案例研究或异常检测时翻出来看。我一般会先把 29 张表的表名和主键列出来建一张元数据表# 伪代码遍历附注库所有表记录表名、行数、主键候选 import sqlite3 conn sqlite3.connect(csmar_notes.db) tables pd.read_sql( SELECT name FROM sqlite_master WHERE typetable, conn ) meta [] for t in tables[name]: cnt pd.read_sql(fSELECT COUNT(*) as c FROM {t}, conn)[c][0] cols pd.read_sql(fPRAGMA table_info({t}), conn)[name].tolist() meta.append({表名: t, 行数: cnt, 字段数: len(cols), 字段列表: cols}) meta_df pd.DataFrame(meta) print(meta_df.sort_values(行数, ascendingFalse).head(10))这样你能快速知道哪张表记录最多文档提到主营业务收入有 84 万条、哪张表字段最宽从而决定哪些表进主分析、哪些表做补充验证。5. 避坑与排查那些文档没写但一定会遇到的问题5.1 现象合并报表和母公司报表的证券代码相同merge 后行数翻倍原因直接按证券代码 merge 两张表没有加报表类型约束。解决所有 merge 操作必须带上on[证券代码, 会计期间, 报表类型]或者提前把 A/B 拆成独立数据集。5.2 现象2007 年之前的“交易性金融资产”全为空原因该科目是新准则新增旧准则下不存在。解决不要用fillna(0)直接填零零和缺失在财务上含义完全不同。正确做法是保留 NaN在构建指标时用条件判断处理比如“交易性金融资产占比”只在 2007 年之后计算。5.3 现象审计意见库中同一公司同一年有两条记录原因境内审计机构和境外审计机构分别记录审计费用也分境内和境外。解决如果研究的是审计意见类型去重时按证券代码审计日期保留一条如果研究的是审计费用要把境内和境外费用加总注意货币单位可能不同。5.4 现象分析师预测库中预测终止日相同但分析师不同直接平均后偏差大原因不同分析师的预测精度和覆盖范围差异很大简单平均会引入噪声。解决常见做法是按研究机构分组取中位数或者只保留预测发布日在报告公布日之前一定窗口内的记录。文档里提供了实际指标文件可以用来计算预测误差反过来筛选分析师样本。5.5 现象银行财务库和一般行业财务库的资产负债表科目名不同合并后大量缺失原因银行报表结构特殊科目设置与一般行业不一致。解决不要试图把银行样本和一般行业样本混在一张宽表里跑回归。要么分开建模要么只保留两套体系共有的科目如总资产、净利润并在论文中说明样本划分依据。6. 从 doc 到可复现流程我习惯先跑一遍字段审计拿到任何一份 CSMAR 的 doc 或数据导出文件我第一件事不是跑回归而是做字段审计。具体做法是把 doc 里列出的字段清单和实际数据文件的列名做一次 diff找出三类问题——doc 有但数据没有的、数据有但 doc 没提的、名称不一致的。# 字段审计doc 字段清单 vs 实际数据列名 doc_fields [ 证券代码, 会计期间, 报表类型, 货币资金, 流动资产合计, 固定资产净额, 无形资产净额, 资产总计 ] # 这里只列了资产负债表的一部分实际使用时从 doc 全文提取 actual_cols df.columns.tolist() missing_in_data set(doc_fields) - set(actual_cols) missing_in_doc set(actual_cols) - set(doc_fields) print(doc 有但数据没有, missing_in_data) print(数据有但 doc 没提, missing_in_doc)这一步能帮你提前发现版本差异。比如某些年份的导出文件可能把“固定资产净额”写成“固定资产净值”如果不做审计跑到后面 merge 时才发现对不上返工成本很高。另一个习惯是对每个关键字段跑一遍describe()和缺失率统计尤其是跨年字段。2007 年前后的缺失模式往往能告诉你口径断裂发生在哪一年、哪些科目受影响最大。# 关键字段缺失率按年统计 key_fields [股东权益合计, 归属于母公司所有者权益合计, 少数股东权益] missing_by_year df.groupby(财政年度)[key_fields].apply( lambda g: g.isna().mean() ) print(missing_by_year.round(3))如果“归属于母公司所有者权益合计”在 2006 年缺失率 100%、2007 年骤降到 5% 以下那就确认了口径切换点。这时候再回头调整你的样本期间或变量定义比跑完回归再补救要省事得多。从那以后我每次拿到新的 CSMAR 数据导出都强制先走一遍字段审计和缺失率按年扫描再动任何模型。希望帮到你。本文还有配套的精品资源点击获取