如何去重上市公司前5大供应商与税调数据匹配结果?

在上市公司供应链研究中,我们通常需要将上市公司前5大供应商的名称与税调企业数据进行匹配。由于匹配时采用的是去除干扰词匹配方法,同一个供应商 ID(gysid)往往会得到多条候选匹配结果。如何从中保留”最合适”的一条,是数据清理中的关键一步。

其实之所以保留是担心误删了有用的结果,所以希望这个操作由大家自己完成。

本文介绍一套系统性的去重方法,按优先级从高到低依次筛选,最终确保每个供应商 ID 只对应一条税调企业记录。

数据来源

本教程涉及以下两份数据:

  1. 上市公司前5大供应商与税调匹配结果:2007~2020年上市公司前5大供应商与税调数据匹配结果.dta
  2. 税调基本信息:税调基本信息07_20.dta

两份数据以税调企业 ID(sdid)为连接键进行联结,得到包含供应商名称和税调企业名称的合并数据集。

去重思路

去重的核心逻辑是:在存在多条候选匹配的情况下,按照一套优先级规则,逐步淘汰”较差”的匹配,保留”更好”的匹配。

每一步的操作结构如下:

*- 生成标识变量:满足当前优先级条件时取 1,否则取 0
gen temp1 = (条件)

*- 在同一 gysid 组内计算 temp1 的最大值
bysort gysid: egen maxtemp1 = max(temp1)

*- 若组内有"更好"的匹配(maxtemp1 > 0),则删除该组内"较差"的匹配(temp1 == 0)
drop if temp1 == 0 & maxtemp1 > 0

这种方式的好处是:只在有更好选择时才删除,不会误删”唯一匹配”的记录。

第0步:读入数据并合并

*- 读入供应商-税调匹配结果,将工商企业名称重命名以与供应商名称区分
use "2007~2020年上市公司前5大供应商与税调数据匹配结果.dta", clear
ren 企业名称 企业名称_工商

*- 以 sdid(税调企业 ID)为键,联结税调基本信息(m:m 联结,保留所有匹配)
joinby sdid using 税调基本信息07_20.dta

*- 调整变量顺序,便于核查
order 供应商名称 企业名称

*- 保存合并结果
save rawdata, replace

去重步骤详解

第1步:优先保留名称完全一致的匹配

最高优先级:若供应商名称与税调企业名称 完全相同,则优先保留这类精确匹配。

*- 名称完全相同时 temp1 取 1
gen temp1 = (供应商名称 == 企业名称)

bysort gysid: egen maxtemp1 = max(temp1)
order temp1 maxtemp1

*- 有精确匹配存在时,删除模糊匹配记录
drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

第2步:优先保留”集团对集团”的匹配

供应商名称与税调名称 都含”集团” 的匹配,优于一方含集团、另一方不含集团的情况。这避免了将集团总部与其下属子公司混淆匹配。

cap drop temp1 maxtemp1
gen temp1 = (index(供应商名称, "集团") & index(企业名称, "集团"))

bysort gysid: egen maxtemp1 = max(temp1)
order temp1 maxtemp1

drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

第3步:优先保留”双方均不含集团”的匹配

若双方都不是集团级企业,则直接匹配更合理,排除一方是集团、另一方不是集团的错误匹配。

cap drop temp1 maxtemp1
gen temp1 = (!index(供应商名称, "集团") & !index(企业名称, "集团"))

bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

第4、5步:排除银行支行、分行层级匹配

银行的支行和分行是上级机构的下设分支,非独立法人主体。若同一 gysid 存在非支行/分行层级的匹配,则优先保留后者。

*- 第4步:优先保留双方均不含"支行"的匹配
cap drop temp1 maxtemp1
gen temp1 = (!index(供应商名称, "支行") & !index(企业名称, "支行"))
bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0

*- 第5步:优先保留双方均不含"分行"的匹配
cap drop temp1 maxtemp1
gen temp1 = (!index(供应商名称, "分行") & !index(企业名称, "分行"))
bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0

第6步:排除加油站等末端机构匹配

针对中国石化等大型能源企业,其旗下有大量加油站、服务站等末端机构。通过排除含”站”字的匹配,将结果聚焦于总部或分公司层级。

cap drop temp1 maxtemp1
gen temp1 = (!index(供应商名称, "站") & !index(企业名称, "站"))
bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

第7步:排除企业内部部门匹配

名称中含”部”字的通常是企业内部部门(如”销售部”),而非独立注册的法人主体,优先排除此类匹配。

cap drop temp1 maxtemp1
gen temp1 = (!index(供应商名称, "部") & !index(企业名称, "部"))
bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

第8步:优先保留标准”公司”结构

名称形如”XX公司YY公司”通常是多级嵌套或异常命名格式,优先保留正常的单层”公司”结构。

cap drop temp1 maxtemp1
gen temp1 = (!ustrregexm(供应商名称, "公司(.*)公司") & !ustrregexm(企业名称, "公司(.*)公司"))
bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

第9步:清理不对称的嵌套公司结构

当供应商名称与税调名称在嵌套结构上不对称时,强制删除单侧含异常后缀的记录。涵盖的情形包括:公司、局、煤矿、中心、厂、部等。

cap drop tags
duplicates tag gysid, gen(tags)
gsort -tags gysid

*- 供应商含"公司...公司" vs 企业不含(删除不对称项)
drop if ustrregexm(供应商名称, "公司(.*)公司") & !ustrregexm(企业名称, "公司(.*)公司")
drop if !ustrregexm(供应商名称, "公司(.*)公司") & ustrregexm(企业名称, "公司(.*)公司")

*- 其他不对称情形:公司与局、煤矿、中心、厂、部
drop if ustrregexm(供应商名称, "公司(.*)局") & !ustrregexm(企业名称, "公司(.*)公司")
drop if !ustrregexm(供应商名称, "公司(.*)局") & ustrregexm(企业名称, "公司(.*)公司")
*- ... 以此类推,共覆盖 12 种不对称组合 ...

*- 供应商以"公司"结尾,但企业名称不以"公司"结尾
drop if ustrregexm(供应商名称, "公司$") & !ustrregexm(企业名称, "公司$")

第10步:去掉”股份”后比较名称

“XX有限责任公司”与”XX股份有限公司”在工商注册中属于不同名称,但实为同一企业。去掉”股份”后若名称相同,优先保留这类匹配。

cap drop temp1 maxtemp1
gen 企业名称2 = subinstr(企业名称, "股份", "", .)
gen temp1 = (供应商名称 == 企业名称2)

bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
duplicates report gysid

cap drop 企业名称2

第11步:强制去重(gysid + 企业名称组合重复项)

同一供应商 ID 对应完全相同企业名称的记录超出1条时,直接强制保留1条。

duplicates drop gysid 企业名称, force
duplicates report gysid

第12步:去掉”责任”后比较名称

类似第10步,”有限公司”与”有限责任公司”是同一企业的不同写法,去掉”责任”后若名称相同,优先保留这类匹配。

cap drop temp1 maxtemp1
gen 企业名称2 = subinstr(企业名称, "责任", "", .)
gen temp1 = (供应商名称 == 企业名称2)

bysort gysid: egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0

cap drop 企业名称2

第13步:字符串编辑距离兜底

经过前述所有规则后仍存在重复的 gysid,使用 strdist 命令计算字符串相似度,保留每组中相似度最高的匹配。

注意:需提前安装 strdist 包:ssc install strdist

*- 重新标记仍有重复的记录
cap drop tags
duplicates tag gysid, gen(tags)
tab tags

*- 仅对仍有重复的记录计算字符串距离
strdist 供应商名称 企业名称 if tags > 0, gen(dist)
bysort gysid: egen maxdist = max(dist)

*- 保留相似度最高的记录(或无重复的记录)
keep if dist == maxdist | mi(dist)
duplicates report gysid

第14步:最终去重与输出

*- 每个 gysid 只保留1条记录
duplicates drop gysid, force

*- 删除所有辅助变量
drop dist maxdist temp1 maxtemp1 tags

*- 保存最终结果
save 2007~2020年上市公司前5大供应商与税调数据匹配结果(去除gysid重复).dta, replace

去重规则总结

下表汇总了本方法的 14 个去重步骤及其背后的逻辑:

步骤 优先保留的匹配类型 核心逻辑
1 名称完全一致 精确匹配优于模糊匹配
2 集团对集团 层级一致优于层级错位
3 双方均非集团 层级一致优于层级错位
4 双方均非支行 法人主体优于下设分支
5 双方均非分行 法人主体优于下设分支
6 双方均非”站” 总部/分公司优于末端机构
7 双方均非”部” 法人主体优于内部部门
8 双方均为标准公司 正常命名优于嵌套格式
9 结构对称 后缀类型需两侧一致
10 去”股份”后名称一致 同企业不同注册形式
11 强制去重(同名) 消除完全重复记录
12 去”责任”后名称一致 同企业不同注册形式
13 字符串相似度最高 兜底规则,量化相似度
14 最终强制去重 确保结果唯一

对于其他数据可能还存在其他类型的情况,可以再采用类似的方法去重。

关联课程

数据匹配与去重方法在供应链、企业关系等研究中非常常见,以下课程介绍了相关的数据匹配方法:

如何匹配中国工业企业数据库和海关数据库?以 2013 年为例:https://rstata.duanshu.com/#/course/5463b8d7afcb438ca1e537fa76c1a45d

工企和污染数据是如何匹配的?使用 Stata 完成整个过程:https://rstata.duanshu.com/#/course/7325758d0ad9457790215455dbcbcb1a

点击这里跳转到 RStata 短书平台获取附件:如何去重上市公司前5大供应商与税调数据匹配结果?

评论