Workbuddy:构建一个使用 Stata 进行数据匹配结果去重的 SKILL

最近给大家分享了一篇推文:

如何去重上市公司前5大供应商与税调数据匹配结果?:https://mp.weixin.qq.com/s/5FeQMZqhDT_m_1vVSQC-dw

然后我让 Workbuddy 学习了这篇推文并生成了一个 SKILL,也就是附件中的 stata-dedup-matching.zip 文件。

# Stata 企业名称匹配结果去重 Skill

## 概述

本 skill 提供一套针对**中文企业名称模糊匹配结果**的系统性去重方法。适用场景:

- 上市公司供应商/客户数据与税调、工商、工企等数据的匹配结果去重
- 工业企业数据库与海关数据匹配结果去重
- 任何通过企业名称模糊匹配(`reclink`/`matchit`/`strdist` 等)产生的中间结果去重

核心思路:**同一 ID 存在多条候选匹配时,按优先级规则逐步淘汰"较差"的匹配,最终保留最合适的一条。**

## 触发信号

当用户提到以下内容时使用本 skill:
- 匹配结果中同一 ID 对应多条记录
- 需要从多个候选匹配中挑选最合理的一条
- 供应商/客户/企业名称匹配后的去重处理
- `duplicates report` 显示仍有重复

## 工作流程

### Step 1:收集必要信息

向用户确认以下变量:

1. **主键变量**:唯一标识待去重实体的变量(如 `gysid`、`firmid`、`企业代码`)
2. **名称A**:原始数据中的名称变量(如 `供应商名称`、`企业名称_原`)
3. **名称B**:匹配到的名称变量(如 `企业名称`、`工商名称`)
4. **输入文件**:匹配结果数据文件路径
5. **输出文件**:去重后结果保存路径

如果用户已提供代码或数据,从中推断上述变量,无需再次询问。

### Step 2:读取参考规则

读取 `references/dedup_rules.md`,其中包含:
- 14条去重规则的详细说明和代码模板
- 规则选择指南(根据数据特点选择适用规则)
- 完整代码模板(含占位符 `[主键]`、`[名称A]`、`[名称B]`)

### Step 3:生成去重代码

基于参考文档中的完整代码模板,将占位符替换为用户实际的变量名,生成可直接运行的 Stata `.do` 文件。

**代码生成规则:**
- 所有注释使用 `*-` 开头
- 每步结束后保留 `duplicates report [主键]` 用于验证效果
- 根据数据特点选择性包含或删除部分规则(参考 `references/dedup_rules.md` 中的"规则选择指南")
- 若用户数据不涉及银行,可删除规则4、5;若不涉及能源企业,可删除规则6

### Step 4:说明每步逻辑

生成代码后,用简短的中文说明每个关键步骤的去重逻辑,帮助用户理解和调整。重点说明:
- 整体优先级设计思路
- 各规则针对的具体数据问题
- 如何根据 `duplicates report` 的输出判断哪步效果最显著

### Step 5:提供调整建议

根据用户反馈,针对特定数据特征提供额外的去重规则建议,例如:
- 数据中含有特殊行业关键词时,添加相应的排除规则
- 需要更精细的字符串相似度阈值时,调整 `strdist` 的过滤条件
- 数据中含外资企业时,建议补充中英文名称对照去重规则

## 注意事项

- `strdist` 命令需提前安装:`ssc install strdist`
- `joinby` 用于 m:m 匹配,如数据已是 m:1 或 1:m 结构,改用 `merge`
- 去重前建议先用 `duplicates report [主键]` 了解重复的规模
- 每步去重后的 `duplicates report` 输出可帮助判断哪些规则最有效
- 规则顺序很重要:先用语义规则(规则1-12),最后用统计兜底(规则13-14)

然后今天恰好测试了下,效果还挺好用:

“匹配结果.dta” 数据中,变量名字带原始数据的是原始数据中的变量,myid 是原始数据的编号,其余变量是工商注册信息里面的变量,newgcid 是工商注册数据的编号,里面存在多条 myid 对应 newgcid 的情况,对其进行去重@skill:stata-dedup-matching@skill:stata-executor

其中 stata-executor 是告诉 workbuddy 在我的电脑上如何调用 Stata 执行代码的 SKILL,这个对大家估计用处不大,大家可以在 workbuddy 成功调用 Stata 执行代码的时候让它直接生成一个 SKILL 总结这个过程的经验,防止下次调用的时候反复出错。

效果还挺不错,下面的代码是去重复生成的代码:

*==============================================================================
*- 企业名称匹配结果去重
*- 主键变量:myid(原始数据编号)
*- 名称A(原始方):公司名称_原始数据
*- 名称B(匹配方):企业名称(工商注册数据)
*- 工商注册数据编号:newgcid
*- 输入:匹配结果.dta(28,302 obs)
*- 输出:去重结果.dta
*==============================================================================

cd "/Users/ac/Desktop/工商注册信息匹配2026-04-14"
use "匹配结果.dta", clear

*- 初始重复情况
di "===== 初始重复情况 ====="
duplicates report myid

*- 用局部宏简化中文变量名引用
local nameA 公司名称_原始数据
local nameB 企业名称
local id myid

duplicates tag `id', gen(tags)
gsort -tags `id'

*---------------------------------------------------------------------------
*- 第1步:优先保留名称完全一致的匹配
*---------------------------------------------------------------------------
gen temp1 = (`nameA' == `nameB')
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
di "===== 第1步后(名称完全一致)====="
duplicates report `id'
cap drop temp1 maxtemp1

*---------------------------------------------------------------------------
*- 第2步:优先保留集团对集团的匹配
*---------------------------------------------------------------------------
gen temp1 = (index(`nameA', "集团") & index(`nameB', "集团"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1

*---------------------------------------------------------------------------
*- 第3步:优先保留双方均非集团的匹配
*---------------------------------------------------------------------------
gen temp1 = (!index(`nameA', "集团") & !index(`nameB', "集团"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
di "===== 第3步后(集团层级过滤)====="
duplicates report `id'
cap drop temp1 maxtemp1

*---------------------------------------------------------------------------
*- 第4、5步:排除银行支行/分行
*---------------------------------------------------------------------------
foreach keyword in "支行" "分行" {
gen temp1 = (!index(`nameA', "`keyword'") & !index(`nameB', "`keyword'"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1
}
di "===== 第4-5步后(银行支行/分行)====="
duplicates report `id'

*---------------------------------------------------------------------------
*- 第6步:排除末端机构"站"(如加油站、服务站)
*---------------------------------------------------------------------------
gen temp1 = (!index(`nameA', "站") & !index(`nameB', "站"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1

*---------------------------------------------------------------------------
*- 第7步:排除内部部门"部"
*---------------------------------------------------------------------------
gen temp1 = (!index(`nameA', "部") & !index(`nameB', "部"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
di "===== 第7步后(站/部门过滤)====="
duplicates report `id'
cap drop temp1 maxtemp1

*---------------------------------------------------------------------------
*- 第7b步:排除子公司/分公司/支公司层级不对称匹配
*---------------------------------------------------------------------------
foreach keyword in "子公司" "分公司" "支公司" {
gen temp1 = (!index(`nameA', "`keyword'") & !index(`nameB', "`keyword'"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1
}
di "===== 第7b步后(子/分/支公司层级)====="
duplicates report `id'

*---------------------------------------------------------------------------
*- 第8步:优先保留标准公司结构(排除嵌套公司名)
*---------------------------------------------------------------------------
gen temp1 = (!ustrregexm(`nameA', "公司(.*)公司") & !ustrregexm(`nameB', "公司(.*)公司"))
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1

*---------------------------------------------------------------------------
*- 第9步:清理不对称嵌套结构
*---------------------------------------------------------------------------
cap drop tags
duplicates tag `id', gen(tags)
foreach suffix in "公司" "局" "煤矿" "中心" "厂" "部" {
drop if ustrregexm(`nameA', "公司(.*)`suffix'") & !ustrregexm(`nameB', "公司(.*)公司")
drop if !ustrregexm(`nameA', "公司(.*)`suffix'") & ustrregexm(`nameB', "公司(.*)公司")
drop if ustrregexm(`nameA', "公司(.*)公司") & !ustrregexm(`nameB', "公司(.*)`suffix'")
drop if !ustrregexm(`nameA', "公司(.*)公司") & ustrregexm(`nameB', "公司(.*)`suffix'")
}
drop if ustrregexm(`nameA', "公司$") & !ustrregexm(`nameB', "公司$")
di "===== 第8-9步后(嵌套结构过滤)====="
duplicates report `id'
cap drop tags

*---------------------------------------------------------------------------
*- 第10步:去掉"股份"后比较
*---------------------------------------------------------------------------
gen nameB_tmp = subinstr(`nameB', "股份", "", .)
gen temp1 = (`nameA' == nameB_tmp)
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1 nameB_tmp

*---------------------------------------------------------------------------
*- 第11步:强制去重(同 myid + 同企业名称)
*---------------------------------------------------------------------------
duplicates drop `id' `nameB', force
di "===== 第11步后(完全重复)====="
duplicates report `id'

*---------------------------------------------------------------------------
*- 第12步:去掉"责任"后比较
*---------------------------------------------------------------------------
gen nameB_tmp = subinstr(`nameB', "责任", "", .)
gen temp1 = (`nameA' == nameB_tmp)
bysort `id': egen maxtemp1 = max(temp1)
drop if temp1 == 0 & maxtemp1 > 0
cap drop temp1 maxtemp1 nameB_tmp
di "===== 第12步后(有限公司/有限责任公司)====="
duplicates report `id'

*---------------------------------------------------------------------------
*- 第13步:字符串相似度兜底(需安装 strdist)
*---------------------------------------------------------------------------
cap drop tags
duplicates tag `id', gen(tags)
cap ssc install strdist
strdist `nameA' `nameB' if tags > 0, gen(dist)
bysort `id': egen maxdist = max(dist)
keep if dist == maxdist | mi(dist)
di "===== 第13步后(字符串相似度兜底)====="
duplicates report `id'

*---------------------------------------------------------------------------
*- 第14步:最终强制去重并清理
*---------------------------------------------------------------------------
duplicates drop `id', force
cap drop dist maxdist temp1 maxtemp1 tags
di "===== 最终去重结果 ====="
duplicates report `id'
count

save "去重结果.dta", replace
di "去重完成,结果已保存至 去重结果.dta"

最后的效果还挺好:

点击这里跳转到 RStata 短书平台获取附件:Workbuddy:构建一个使用 Stata 进行数据匹配结果去重的 SKILL

评论