资讯动态

税收调查数据与工商注册信息匹配:企业经营分析与风险识别实战

发布时间:2026/10/21 17:08:32 来源:尧图企业网站定制
简介针对企业微观数据实证研究需求这套整合包提供2007-2016年中国税收调查数据ATS与工商注册信息的匹配结果覆盖金融、贸易、经济等方向适合需要海量权威企业级变量的科研人员。压缩包内为1个docx文档仅50KB内含百度网盘链接与提取码指向135GB完整数据源包括近500项税调指标、匹配后的工商注册变量集、可清洗任意微观数据库的万能代码以及约100万观测值、近70万家企业的税调基本信息面板。已有116人学习下载。数据指标含纳税人识别号、注册资本、经营状态、经度纬度、行业代码等几十个字段所有统计结果未拟合、未插值显著性好可直接用于实证回归与稳健性检验大幅降低微观数据获取与清洗成本。1. 从经营分析的黑匣子说起税收调查数据与工商注册信息匹配能解决什么做企业客户授信审查时我遇到过不少这样的案例工商注册信息里注册资本写得很唬人经营范围列了一长串看起来是个像样的经营主体可真要判断这家公司过去几年到底赚不赚钱、交了多少税、人员规模是不是和营收匹配工商信息完全给不出答案。直到把 2007-2016 年税收调查数据与工商注册信息做了一次系统性匹配原本藏在黑匣子里的实际经营轨迹才浮出水面。所谓税收调查数据是税务系统按年度对企业在申报环节形成的税费、财务、人员等明细做抽样调查的汇总结果字段细到全年应缴实缴的增值税、企业所得税、从业人数和固定资产原值而工商注册信息提供的是企业的登记属性比如成立时间、资本、法人和行业归属。两者的匹配就是把“企业想干什么”和“企业实际干成什么样”接在一起组合出真正可用于经营分析的宽表。这套打法适用于三类人做信贷风控的数据分析师、做区域产业研究的机构从业者、以及需要对企业经营状况做交叉验证的税务与财务顾问。下面我按自己做过的方案把数据拆解、三轮匹配、特征构造和避坑要点完整讲一遍。2. 先把数据资源拆开2007-2016 税收调查与工商注册的字段、价值和清洗2.1 这十年税收调查数据为什么值得匹配2007 到 2016 这十年正好踩在中国税制变动和商事制度改革的交汇点上2008 年内外资企业所得税两法合并2012 年营改增开始在上海试点并在随后几年分行业扩围2014 年注册资本认缴制落地2015 年“三证合一”推行。这些政策变化都直接影响到企业报表上的收入确认口径、纳税编码和登记标识。如果只拿单一年的截面数据很多企业恰好落在政策切换节点上账表口径前后不一致根本没法做趋势分析而把十年数据拼接并匹配到工商注册信息后既能看清政策切换前后的口径差异也能还原一家企业从开业、扩张到收缩或注销的生命周期。更关键的是税收调查数据里包含了工商系统没有的实缴税费和财务报表字段匹配之后才能从登记信息推导不了的增长和税负特征。这类税收调查数据在企业级分析里属于高稀缺度资源。它不像工商注册信息那样能通过公开渠道批量抓取也不是每个年份、每个地区都按统一格式下发拿到手往往是一个包含多个子表的压缩包年份之间字段名还不完全一致。我第一次处理时光是把 2007 年的从业人数字段和 2016 年的同一字段对齐就花了大半天。所以开动分析前一定要先建立一份字段字典逐列确认每一年的含义而不是直接合并。2.2 工商注册信息里真正能用于分析的字段工商注册信息看似字段很多但真正能进匹配和特征工程的我一般只留下面这些字段说明在分析里的用途企业名称全称匹配主键之一归一化后做模糊匹配统一社会信用代码/旧注册号最早的强匹配键硬匹配第一轮法定代表人自然人姓名组合匹配兜底注册资本认缴或实缴单位为万元规模分层与校验成立日期精确到日计算开业年限登记状态存续、注销、吊销等样本过滤所属行业代码GB/T 4754 门类到小类分组统计与税负率参照如果手头数据还有经营范围字段也可以保留但经营范围的文本噪声非常大不适合直接进匹配最多用来做行业归类的补充校验。要注意的是2015 年三证合一之后部分企业同时拥有旧注册号和新统一社会信用代码两套号在建表时应横向并存而不是二选一否则后续匹配会白白丢掉一批命中。2.3 匹配前必做的清洗三步匹配失败的头号原因不是算法不够强而是脏数据。常见的脏点包括企业名称中间混入全角空格和非断行空格、括号一会儿全角一会儿半角、注册资本被 Excel 读成了浮点数导致小数位漂移、统一社会信用代码被当数字列读入后丢掉前导 0。我一般拿到原始文件后的第一件事就是修改列类型并做名称清洗。以下代码以 Python 为例假设税收调查文件命名为 tax_survey.csv工商注册文件命名为 ic_registry.csvimport re import pandas as pd def clean_ent_name(name): if not isinstance(name, str): return name name.upper() # 去掉普通空格、全角空格和不断行空格 name re.sub(r[\s\u3000\u00a0], , name) # 统一括号为半角 name re.sub(r[(], (, name) name re.sub(r[)], ), name) # 去掉名称里的“中国”前缀保留核心主体描述 name re.sub(r^中国, , name) return name.strip() tax pd.read_csv(tax_survey.csv, dtype{tax_no: str, ent_name: str}) ic pd.read_csv(ic_registry.csv, dtype{ credit_code: str, old_reg_no: str, ent_name: str }) tax[name_key] tax[ent_name].map(clean_ent_name) ic[name_key] ic[ent_name].map(clean_ent_name)这段代码里有几个细节值得留意。dtype 强制指定为 str这是最容易踩坑的地方注册号、信用代码如果按整数读前导 0 会消失后面无论怎么精确匹配都白搭。clean_ent_name 先转大写因为后续模糊匹配里的拼音或者字符串比较都受大小写影响去空格后再统一括号能避免同一家企业“中国”和“(中国)”的写法不一致。去掉“中国”前缀这一步要看你的样本结构如果分析对象全是本地公司这个操作能显著提升名称匹配的命中率如果包含全国性总部慎用。运行时建议把 name_key 中处理后为空的记录单独导出检查而不是直接丢弃。3. 三轮匹配实战税号、企业名称、法人组合的逐级衔接3.1 第一轮硬匹配统一社会信用代码与旧税号交替比对匹配不能上来就做模糊匹配计算量大且误报高。我习惯先做硬匹配把所有能用代码串解决的记录一次性解决掉。2007-2016 这段时期特殊点在于早年税收调查表里的纳税人识别号多为 15 位旧编码而工商侧在 2015 年后逐步换成 18 位统一社会信用代码两边都有 15 位旧注册号的历史残留。因此第一轮要做的是把两侧各自能拿出的代码都标准化成长字符串再逐个对齐。def normalize_tax_code(code): if not isinstance(code, str): return code code.strip().upper() # 18位统一社会信用代码去掉中划线后按标准字符集校验 if len(code) 18 and re.match(r^[0-9A-HJ-NPQRTUWXY]{18}$, code): return code # 15位旧税号/旧注册号纯数字且长度为15 if len(code) 15 and re.match(r^\d{15}$, code): return code return tax[code_key] tax[tax_no].map(normalize_tax_code) ic[code_key] ic[credit_code].map(normalize_tax_code).mask( lambda s: s , ic[old_reg_no].map(normalize_tax_code) ) match_round1 tax.merge( ic[[code_key, ic_id]], oncode_key, howinner ) print(round1 matched:, len(match_round1))代码逻辑分三层先尝试拿 18 位统一社会信用代码做匹配匹配不上再看 15 位旧注册号。normalize_tax_code 里的正则用了统一社会信用代码的标准字符集去掉了 I、O、Z、S、V 五个易混淆字母这样能挡掉一批录入错误导致的伪命中。mask 方法表示当 credit_code 标准化后仍为空时回退去取旧注册号。参数方面如果你的数据里有组织机构代码证号9 位且规模不小可以在 normalize 里再加一个 9 位纯数字分支但要注意 9 位组织机构代码存在一码多照的情况命中后必须用企业名称二次确认不能直接当唯一主键。3.2 第二轮名称匹配归一化并设置合理的相似度阈值第一轮结束以后通常会剩下两三成记录因为税号缺失或编码存疑而没匹配上。第二轮我改用企业名称模糊匹配。但直接对所有未匹配记录做两两相似度计算会爆炸比如一万条未匹配企业对上八十万条注册库就是八亿次比较。所以要先对未匹配样本做行业代码和注册地预筛把候选集缩小到同区县同行业再做编辑距离相似度计算。from difflib import SequenceMatcher def name_sim(a, b): if not a or not b: return 0.0 return SequenceMatcher(None, a, b).ratio() # 预先按城市代码与行业大类分组缩小配对范围 tax_pool tax_remain.groupby([district, ind_l]) ic_pool ic_remain.groupby([district, ind_l]) candidates [] for key, t_group in tax_pool: if key not in ic_pool.groups: continue i_group ic_pool.get_group(key) for _, t in t_group.iterrows(): best_score 0.0 best_ic None for _, i in i_group.iterrows(): s name_sim(t[name_key], i[name_key]) if s best_score: best_score s best_ic i[ic_id] if best_score 0.86: candidates.append((t[tax_id], best_ic, best_score)) match_round2 pd.DataFrame(candidates, columns[tax_id, ic_id, score])这段代码是用三层循环实现的只适合数据量在万级以内的情况。如果未匹配量更大建议改成用拼音首字母做前筛或者用向量化的相似度矩阵否则跑一遍要几个小时。阈值 0.86 是我试过多个项目后觉得平衡最好的数低于 0.86 误配就会成片出现比如“科技”和“技术”只差两个字相似度很容易落在 0.8 附近高于 0.9 又会漏掉那些名称里带括号、股份比例表述不一致但实际是同一家的记录。如果你的数据经过行政区划预筛后仍觉得噪声大把阈值提一点到 0.88 再观察命中分布也完全合理。3.3 第三轮组合兜底法定代表人加注册资本加成立日期前两轮结束后仍然会有一批名称差异比较大但在业务上能确认是同一主体的记录比如“某某有限公司”和“某某股份有限公司”由于行业小类不同没被预筛到同一组或者企业改名后税务和工商两侧冻结了不同名称。第三轮我用法定代表人、注册资本、成立日期三个字段组成联合条件三个字段完全一致就判定为同一主体。这个规则听起来粗暴但在这类企业级数据上很少误伤因为三个字段同时一致的概率极低。def combo_key(legal, cap, est): return |.join([ str(legal).strip().replace( , ), str(cap), str(est)[:10] ]) tax_rem2[combo] tax_rem2.apply( lambda r: combo_key(r[legal_name], r[reg_cap], r[est_date]), axis1 ) ic_rem2[combo] ic_rem2.apply( lambda r: combo_key(r[legal_name], r[reg_cap], r[est_date]), axis1 ) match_round3 tax_rem2.merge( ic_rem2[[combo, ic_id]], oncombo, howinner )这里有一个参数细节常被忽视注册资本金额在税收调查表和企业工商侧经常存在单位差异一边是万元一边是元。我在做 combo_key 前一定会先检查两侧资本字段的最大值和中位数确认单位统一再拼字符串。另外成立日期最好截断到天如果一边精确到天、一边精确到月用前 7 位做 key 会更稳妥。千万别直接对浮点资本做等值匹配,历史汇率折股等原因产生的分角差异会毁掉整轮匹配。3.4 匹配结果分级与交付前检查三轮匹配做完不应该直接进入分析而是先给每一条匹配结果打质量标签我按 A、B、C 三级管理A 级来自第一轮代码硬匹配且两侧企业名称归一化后的相似度不低于 0.9B 级来自第一轮但名称相似度较低或来自第二轮名称匹配且分数不低于 0.86C 级来自第三轮组合兜底。这个分级在后续特征工程里非常有用比如计算行业税负率中位数时可以先用 A 级样本做基准再逐步把 B、C 级加进去对比分布能直观看出低质量匹配是否引入了系统性偏差。交付前还要抽 50 条 C 级结果人工核验核验率低于 90% 就说明第三轮的组合 key 有问题需要回查字段单位。4. 匹配之后构建经营分析特征增速、税负率与异常识别4.1 基础经营特征表如何落地匹配完成的宽表才是分析开始的地方。我最常构造的特征可以分为四组规模类、增长类、税负类和效率类。规模类包括营业收入、资产总计、从业人数取对数增长类是相邻两年营业收入和利润总额的同比增速税负类是本年度实缴增值税除以营业收入、实缴企业所得税除以利润总额效率类是营业收入除以从业人数得到人均创收。下面这段 SQL 是构造年度特征的常见做法SELECT ic_id, tax_year, revenue, LAG(revenue) OVER (PARTITION BY ic_id ORDER BY tax_year) AS prev_revenue, (revenue - LAG(revenue) OVER (PARTITION BY ic_id ORDER BY tax_year)) / NULLIF(LAG(revenue) OVER (PARTITION BY ic_id ORDER BY tax_year), 0) AS rev_yoy, vat_paid / NULLIF(revenue, 0) AS vat_rate, income_tax_paid / NULLIF(profit_before_tax, 0) AS cit_rate, revenue / NULLIF(employee_cnt, 0) AS revenue_per_capita FROM matched_tax_ic ORDER BY ic_id, tax_year;用窗口函数 LAG 最大的好处是保留企业自身的时序结构避免跨年份错位。NULLIF 把分母为 0 的除法结果置成 NULL这样在统计聚合时这些无效值会自动被排除同时不会污染均值。rev_yoy 字段在样本只有单年数据时会出现大量 NULL分析时要注意按年区分。我一般在生成这张特征表后会跑一次分年分行业的描述统计确认每个行业每年的样本量不低于 30 家低于这个数就不往下拆分了避免让少数几家异常企业扭曲整个行业分布。4.2 经营异常识别规则与参数说明匹配后数据最有价值的应用之一是识别异常经营信号。我常用的规则是组合式阈值判断单一指标异常并不能说明问题但三四个同时异常就有很强的指向性连续两年增值税税负率为 0同时营业收入超过 5000 万元疑似申报与收入不匹配企业所得税贡献率应交所得税除以利润总额为 0 但利润总额连续为正需要回查是否享受减免营业成本增速与营业收入增速差超过 50 个百分点且绝对值都超过 30%疑似成本归集口径变动从业人数小于 5 人但营业收入进入行业前 20%属于典型的人均产出异常。规则里的阈值要不要改取决于你的分析目标如果做的是信贷风控要把阈值的召回率拉高宁可多筛出一批企业做人工核查也不要漏掉风险户如果做行业景气研究则更关注精确率用中位数加绝对偏差来替代固定阈值。2007-2016 年间还有一个政策因素要考虑到2012 年营改增试点后部分行业的税负口径从营业税切换为增值税跨 2012 年比较税负率时必须在特征表里加一个“是否营改增过渡期”的分组变量否则会得出税负骤降的错误结论。4.3 用工商字段做匹配后的最后一道自检不能以为匹配结束就是万事大吉匹配质量还会在特征层面露馅。我习惯用税务侧的实收资本字段和工商侧的注册资本交叉比对正常情况下两者不会相差太大如果出现税务侧实收资本是工商侧注册资本的 5 倍以上大概率是匹配到了错误的主体。另一个自检维度是行业代码税收调查的国民经济行业分类和工商注册的经营范围第一项行业分类应该大致对应出现跨门类不一致时要么是行业代码在十年间做过修订要么就是名称模糊匹配连错了公司。把这两类自检筛出的记录单独导出回原表看名称和代码几乎每批都能揪出几条 B 级匹配的误配案例。下面是行业代码一致性检查的示例ic_industry_map ic[[ic_id, industry_code]].drop_duplicates() coherent merged.merge(ic_industry_map, onic_id, howleft) coherent[ind_match] ( coherent[tax_industry].astype(str).str[:2] coherent[industry_code].astype(str).str[:2] ) bad coherent[~coherent[ind_match]] print(跨门类不一致占比: %.2f%% % (100 * len(bad) / len(coherent)))这里比较的是前两位也就是国民经济行业大类。如果两边大类都不一致基本可以断定匹配出错或企业历经过行业变更如果大类一致但小类不一致属于正常现象不用处理。这个自检步骤放最后能直接暴露第二三轮匹配引入的噪声比人工抽查高效得多。5. 匹配与分析路上的避坑记录五个翻车现场5.1 现象税号匹配出现多条连接CRUD 后的宽表主键重复第一次跑第一轮硬匹配时我惊讶地发现同一税务记录能匹配到三四条工商记录。查了半天原因是旧版税收调查数据里的纳税人识别号字段在 2015 年前后出现了“一码多主”的情况部分分公司沿用总公司的 15 位税号长达两年另外工商侧旧注册号也存在多头登记的历史异常。解决方式是在匹配前按“企业名称 法人姓名 行政区划”对税务侧重复的 code_key 做去重只保留成立日期最早的那一条作为主记录其他标记为分公司记录另行归档不做连接。5.2 现象名称模糊匹配连上了“科技”与“技术”相似度还挺高有一次抽查 B 级匹配结果看到一家“某信息技术服务有限公司”和一家“某信息科技服务有限公司”被连在一起两个名称的 SequenceMatcher 相似度高达 0.89但实际是两家独立公司。原因在于“技术”和“科技”两个字在编辑距离里只算一次替换而相同的部分又很多。解决方式是给模糊匹配加行业词黑名单筛选掉资本市场常见的易混词对比如“科技/技术”“贸易/商贸”“实业/实业发展”把这些词对在比较前先做标准化映射映射完仍然相同才算通过。5.3 现象匹配率只有六成一度以为是数据源不全做第二个年度数据时整体匹配率从前一年的 88% 掉到 61%直接怀疑是共享数据被裁剪过。后来排查发现掉匹配率的那一年税收调查抽样框变化很大新增了大批当年新成立企业这些企业没有历史税号记录同时工商库里包含大量长期不年报的非正常户。解决方法是分年度、分存续状态、分注册年份去统计匹配率发现存续超过 3 年的企业匹配率仍然在 90% 以上说明数据源本身没问题是样本结构变了不是匹配算法退化了。5.4 现象跨年纵向比较时税收特征整体跳变把 2011 到 2013 三年的营业税负率放在同一张折线图里发现某个行业在 2012 年突然下降一半。第一反应是数据质量出了问题回查后发现那年营改增试点令试点行业把营业额和应纳营业税额分别迁移到新科目旧字段出现大面积零值。解决方式是建口径映射表把营改增前后两年的科目做字段级重映射再生成一张“可同比指标清单”凡是没有完成映射的指标跨年对比直接禁用不做所谓的数字修正。5.5 现象匹配后的宽表出现大量空值下游模型结果漂移构建特征表后训练分类模型发现模型在同一批数据上重新跑一遍 AUC 波动超过 5 个百分点排查结果是利润总额字段在 2009 年以前缺失率高达 40%而这部分缺失集中在制造业小规模企业导致特征分布被人为分层。解决方式是不再把这些缺失直接填 0而是生成一列“是否存在利润字段”的哑变量并在分组特征里加入年份区间与是否为缺失样本的交叉项让模型自己学会区分不同口径而不是把缺失当成默认值。6. 用行业税负率区间给匹配质量验一次身匹配做完、特征表生成后我会多用一步用分行业分年度的增值税税负率四分位数来检验整张表的合理性。具体做法是把匹配后的宽表按国民经济行业大类分组分别计算每年增值税实缴额除以营业收入的 25% 分位、中位数和 75% 分位。如果某个年份某个行业的税负率中位数明显偏离正常区间比如制造业增值税税负率中位数在某一年突然跳到 15%那多半不是经营异常而是匹配串户或行业代码错位。下面是常用参考区间表具体数值应以你的样本里 A 级匹配形成的内部分布为准行业大类增值税税负率常见中位区间操作提示制造业2%-5%偏离超过两倍中位数需抽查批发零售业0.5%-2%偏低是常态不直接判定异常建筑业1.5%-3.5%注意跨 2012 年口径切换信息技术服务业2%-6%与增值税进项结构相关把这个检验写成脚本后输出的是一张行业年度分布表同时把落在 P5-P95 之外的企业标记为“待复核”不直接入模。每次跑完匹配流程我都会保留一份匹配质量摘要记录第 1、2、3 轮各自命中条数、A/B/C 级占比、交叉校验不一致率这份摘要只有十几行但能让我在三个月后重新回来处理同一个数据集时不用重新摸一遍字段口径。这套流程是我个人做企业级数据匹配的固定动作先清洗字段再逐级匹配分级管理最后用行业分布验证。走到今天我仍然会在每一轮新的数据上翻车但每一次翻车都会把其中一条经验补充到检查清单里。希望这篇笔记能帮到你少走一段弯路尤其是第三轮组合匹配里注册资本单位不一致这类不亲身踩一遍很难意识到的坑。祝你匹配顺利、分析稳定。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑