前置条件与学习依赖 / PREREQUISITES
① 数学 / 统计基础
效用最大化与劳动供给选择、密度函数与直方图、局部多项式拟合、delta method、bootstrap 标准误。
② 经济学理论前置
税收理论中的劳动供给弹性(elasticity of taxable income, ETI)、预算线折点与 notch 的图形直觉。
③ 软件 / 计算前置
Stata 16+(外部命令 bunchinghistogrampolyfitbootstrap);Python(numpy、scipy、statsmodels 可选)。
④ 站内前置页面
先学 04 基准回归07 RDD(bunching 与 RDD 同属"利用阈值做准实验"家族)、05 IV
⑤ 难度分级
进阶 · 偏结构直觉

01 是什么:bunching、kink 与 notch

Bunching(聚束/群聚)指的是:当政策在某个 running variable 上制造了一个不连续的激励时,大量最优化个体会把自己的选择恰好停在这个不连续点上,从而在该变量的分布直方图上形成一个肉眼可见的"鼓包"。最经典的例子是个人所得税的税率跳档:中国 2011 年税改后,月应纳税所得额在 3500 元(免征额)以上的部分适用第一档 3% 税率,越过 1500 元(即应纳税所得额 5000 元)后税率跳到 10%。一个原本会赚 5100 元的纳税人,理性选择是把工资"做"到 5000 元整——因为多赚 100 元只按 3% 边际税率缴税,而越过门槛后边际税率翻倍,反而不划算。于是 5000 元这个点上"挤"了一大堆人。

需要区分两个概念:

  • Kink design(折点设计):预算集的斜率在拐点处变化,即边际税率 $t$ 从 $t_0$ 跳到 $t_1$,但预算线本身连续。个体在拐点处形成一个点质量(point mass),分布在拐点处"鼓"一下,两侧平滑。
  • Notch design(断点/缺口设计):预算集在阈值处发生水平位移(例如跨过门槛后一次性多缴一笔固定税,或失去一笔一次性补贴)。此时预算线在阈值处向下跳断,跨过阈值反而可能更穷。理性个体会在阈值右侧留出一个空洞(hole / dominated region)——没有人愿意选择那个区间,因为左端严格占优。

两种设计都能识别弹性,但 notch 多出一个"支配区间"需要额外建模;kink 是 reduced-form 版本,估计更干净。Kleven (2016, JEL) 的综述把这一类方法统称为 "bunching design",与 RDD 的区别在于:RDD 利用的是结果变量在 cutoff 处的跳跃,bunching 利用的是 running variable 自身密度在 cutoff 处的隆起。

02 识别假设与经济学原理

Bunching design 的识别逻辑非常"结构":它假设个体在给定预算集下做最优化,分布由偏好异质性驱动。核心假设包括:

  1. 优化无摩擦(frictionless optimization):个体能精确地把收入"摆"到拐点上,不存在调整成本、注意力缺失、操纵误差。Saez 2010 的原始模型假设无摩擦;后续 Chetty-Friedman-Olsen-Pistaferri (2011, AER) 引入了优化摩擦(optimization frictions),发现现实中 bunching 峰是"弥散"的。
  2. 反事实密度平滑(smooth counterfactual density):如果没有这个拐点,收入分布 $h_0(z)$ 在拐点附近应该是平滑的(由多项式近似)。实际密度 $h(z)$ 与反事实密度 $h_0(z)$ 之差就是"被挤过来的人"。
  3. 局部小弹性 / 局部近似:当税率变化幅度较小时,可以用局部线性近似把"移动距离"和"弹性"联系起来。
  4. 非策略性报告 / 无操纵:与 RDD 的 rddensity 检验类似,如果 bunching 是因为企业/个人精确操纵发票、账目(而非真实行为调整),弹性估计会被高估。

直觉:拐点处挤进来的人越多(excess mass $B$ 越大),说明个体对边际税率变化越敏感——也就是劳动供给弹性越大。把 $B$ 除以拐点处的"基线密度" $h_0(z^*)$,就得到平均移动距离 $\Delta z^*$;再用税率变化幅度做分母,就把移动距离换算成弹性。

03 完整推导:从效用最大化到 Saez 弹性公式

沿用 Saez (2010) 的准线性劳动供给模型。个体选择劳动收入 $z$,效用函数为:

Eq. 3.1 — 准线性效用与劳动供给
$$ U(c, z; n) = c - \frac{n}{1+1/e}\left(\frac{z}{n}\right)^{1+1/e} $$

其中 $c$ 为消费,$z$ 为税前收入,$n$ 为能力异质性参数(分布密度为 $f(n)$),$e$ 为常数弹性(elasticity of taxable income, ETI)。在比例税率 $t$ 下预算约束为 $c = z(1-t)$。一阶条件:

Eq. 3.2 — 一阶条件与 Marshallian 收入函数
$$ \frac{\partial U}{\partial z} = (1-t) - \left(\frac{z}{n}\right)^{1/e} = 0 \;\Longrightarrow\; z^* = n(1-t)^e $$

取对数:$\log z^* = \log n + e\log(1-t)$。现在在 $z^*$ 处制造一个 kink:边际税率从 $t_0$ 跳到 $t_1 > t_0$。原来在无 kink 情形下收入为 $z^*+\Delta z^*$ 的"边际个体",在 kink 下发现把收入降到 $z^*$ 更优(因为 $z^*$ 以上的边际回报下降)。他正好停在拐点:

Eq. 3.3 — 边际个体在两种税制下的收入对比
$$ \frac{z^*+\Delta z^*}{z^*} = \left(\frac{1-t_0}{1-t_1}\right)^{e} $$

当税率变化较小,$\log(1-t_1) - \log(1-t_0) \approx -\dfrac{t_1-t_0}{1-t_0}$,取对数后线性化:

Eq. 3.4 — 小 kink 下的弹性线性化
$$ \frac{\Delta z^*}{z^*} \approx e \cdot \frac{t_1 - t_0}{1 - t_0} $$

另一方面,所有原本落在 $[z^*, z^*+\Delta z^*]$ 区间的人都被"挤"到 $z^*$ 点上,这部分人数就是聚束质量(excess mass)

Eq. 3.5 — Excess mass 与平均移动距离
$$ B = \int_{z^*}^{z^*+\Delta z^*} h_0(z)\,dz \approx h_0(z^*) \cdot \Delta z^* \;\;\Rightarrow\;\; \Delta z^* \approx \frac{B}{h_0(z^*)} $$

把 Eq. 3.5 代入 Eq. 3.4,解出弹性:

Eq. 3.6 — Saez (2010) reduced-form bunching 弹性估计量
$$ \hat{e} = \frac{B / \big[h_0(z^*) \cdot z^*\big]}{\dfrac{t_1 - t_0}{1 - t_0}} = \frac{B \cdot (1 - t_0)}{h_0(z^*) \cdot z^* \cdot (t_1 - t_0)} $$

这就是 Saez (2010, AEJ:EP) 公式。用户提示中的简化式 $e=\frac{B/(h_0+k_1)}{t_1-t_0}$ 是 Eq. 3.6 的粗略记忆版:分子是"每单位密度对应的移动距离",分母是税率相对变化率。实际写作时应使用 Eq. 3.6 的标准形式。

Kink vs Notch 的区别

  • Kink:预算线连续、斜率变。只产生点质量,分布在拐点右侧没有"洞"。Eq. 3.6 直接可用。
  • Notch:预算线跳断。理性个体不会选择 $(z^*, z^*+\Delta z^{*+})$ 这段被支配区间,分布上出现空洞。估计时需要先识别支配区间(dominated region),再把空洞补回来;弹性从"洞的右边界"反推。Kleven & Waseem (2013, QJE) 用巴基斯坦所得税 notch 做了经典应用。

04 逐步操作流程

① 构造直方图 / 分 bin
把 running variable(如个人月收入)按宽度 $\Delta z$ 分 bin,计算每个 bin 的人数 $c_j$。bin width 通常取带宽的 1/10 到 1/20,既要看得清峰、又不能太碎。建议同时画实际密度直方图。
② 估计反事实分布 $h_0(z)$
用一个多项式(通常 4–7 阶)对 bin 计数回归:$c_j = \sum_{p=0}^P \beta_p (z_j - z^*)^p + \gamma \cdot \mathbb{1}[z_j \in \text{excluded}] + \varepsilon_j$。回归时排除拐点附近的窗口 $[z^*-w_L, z^*+w_R]$(excluded region),否则峰本身会把多项式拉上去,低估 excess mass。
③ 计算 excess mass $B$
$B = \sum_{j \in \text{excluded}} [c_j - \hat c_j^{counterfactual}]$。标准化的相对聚束量 $b = B / \hat h_0(z^*)$(即平均移动距离 $\Delta z^*$)。再按 Eq. 3.6 换算成弹性。
④ Bootstrap 标准误
对个体数据做有放回重抽样(1000–2000 次),每次重复 ①–③ 全流程,得到 $B$、$b$、$\hat e$ 的经验分布,取 2.5%/97.5% 分位数作置信区间。不要只用 bin-level 的 OLS 标准误——它忽略了个体抽样波动和多项式阶数选择的不确定性。
⑤ 可视化与稳健性
画直方图 + 反事实多项式曲线 + 阴影标注 excluded region。稳健性:改变 bin width、多项式阶数、excluded window 宽度;报告 placebo kink(在随机位置设假拐点,应看不到峰)。

05 完整 Stata 代码

下面代码分两部分:先用 Stata 官方/社区 bunching 命令跑一遍(适合快速出图),再手写 histogram + 多项式 + excess mass + bootstrap(适合理解原理与自定义)。所有模拟数据由 set seed 生成,可直接运行。

stata · bunching.do(一:官方/社区命令 + 模拟数据)
*==============================================================*
* Bunching / Kink 演示:Saez (2010) 弹性估计
* 情境:月收入 z,在 z*=5000 处边际税率从 t0=0.03 跳到 t1=0.10
*==============================================================*
clear all
set more off
set seed 20260911

* --- 首次运行安装 ---
* ssc install bunching, replace       // 社区命令,自动做反事实多项式+bootstrap
* ssc install estout,   replace

* --- 1. 模拟数据:能力 n ~ LogNormal,常数弹性 e=0.3 ---
* 无 kink 时 z0 = n*(1-t0)^e;有 kink 时,z0>5000 的人被"挤"到 5000
set obs 50000
gen n = exp(rnormal(8.5, 0.6))        // 能力分布
local e_true = 0.3
local t0 = 0.03
local t1 = 0.10
local zstar = 5000
gen z0 = n*(1-`t0')^`e_true'          // 无 kink 时的潜在收入
gen z = z0
* 把 z0 落在 [5000, 5000+dz*] 的边际个体挤到拐点
* dz* 由弹性公式反推:dz*/z* = e*(t1-t0)/(1-t0)
local dz = `zstar' * `e_true' * (`t1'-`t0')/(1-`t0')
replace z = `zstar' if z0>=`zstar' & z0<=`zstar'+`dz'

* --- 2. 用社区命令 bunching 跑(推荐主回归)---
* z 是 running variable,a(5000) 指定拐点,nomore= 不截断右侧
bunching z, a(`zstar') ///
    polynomial(4)            ///  4 阶多项式拟合反事实
    required(500)            ///  excluded window 左侧 500
    cf(500)                  ///  excluded window 右侧 500
    binwidth(100)            ///  每 bin 100 元
    start(-2000)             ///  图的左界
    generate(bunch)          ///  输出估计值到 r()
    graph(title("Bunching:个税税率跳档处的聚束"))

* --- 3. 手写版本:histogram + 多项式 + excess mass + bootstrap ---
* 分 bin
local binw = 100
local zmin = 2000
local zmax = 10000
local nb = (`zmax'-`zmin')/`binw'
matrix cnt = J(`nb',1,.)
forvalues j=1/`nb' {
    local lo = `zmin' + (`j'-1)*`binw'
    local hi = `zmin' + `j'*`binw'
    quietly count if z>=`lo' & z<`hi'
    matrix cnt[`j',1] = r(N)
}
clear
set obs `nb'
gen cnt = .
gen zc  = .
forvalues j=1/`nb' {
    quietly replace cnt = cnt[`j',1] in `j'
    quietly replace zc  = `zmin' + (`j'-0.5)*`binw' in `j'
}
gen rel = zc - `zstar'          // 相对拐点的位置

* 多项式拟合反事实:排除拐点左右各 500 元
gen excluded = (abs(rel)<=500)
reg cnt c.rel##c.rel##c.rel##c.rel i.excluded
* 解释:四次多项式 + excluded 虚拟变量;
* excluded 虚拟变量的系数和就是该窗口内被"挤掉"的人数
predict cnt_hat
* excess mass = 实际 - 反事实(在 excluded 窗口内)
gen diff = cnt - cnt_hat if excluded==1
sum diff, detail
scalar B = r(sum)
* 拐点处反事实密度
sum cnt_hat if abs(rel)<=`binw'/2
scalar h0 = r(mean)/`binw'
scalar zstar = `zstar'
* Saez 弹性
scalar e_hat = (B/(h0*zstar)) / ((`t1'-`t0')/(1-`t0'))
display "Excess mass B = " B
display "Saez elasticity e_hat = " e_hat " (true = `e_true')"

* 画直方图 + 反事实线
twoway (bar cnt zc, barwidth(80) color(gs12)) ///
       (line cnt_hat zc, lwidth(2) lcolor(navy)), ///
       xline(`zstar', lpattern(dash) lcolor(maroon)) ///
       legend(label(1 实际人数) label(2 反事实密度)) ///
       xtitle("月应纳税所得额") ytitle("每 bin 人数") ///
       title("Bunching 手写估计:反事实多项式与 excess mass")
stata · bunching.do(二:bootstrap 标准误)
*==============================================================*
* Bootstrap 标准误:对个体有放回重抽样,重复全流程
*==============================================================*
* 把上面"模拟数据"段保留为程序,用 bootstrap 调用
cap prog drop bunching_est
prog define bunching_est, rclass
    version 16
    syntax , [zstar(real 5000) binw(real 100) w(real 500) t0(real .03) t1(real .10)]
    tempvar bin rel excl
    gen `bin' = floor((z - (`zstar'-3000))/`binw')
    * 直接调用 bunching 命令的返回值
    bunching z, a(`zstar') polynomial(4) required(`w') cf(`w') binwidth(`binw') ///
        start(-2000) quiet
    return scalar e = r(e)          // 若命令返回弹性,直接取
    return scalar b = r(b)          // 标准化 excess mass
end

* 运行 bootstrap(500 次,演示用;正式文章建议 1000-2000 次)
bootstrap e=r(e) b=r(b), reps(500) seed(20260911) ///
    saving("bunching_boot.dta", replace): bunching_est
estat bootstrap, all
* 输出:e 的偏差、标准误、正态/百分位/BCa 置信区间
* 报告时建议用 BCa 或 percentile 区间,而非正态近似
python · bunching.py(手写 excess mass,便于理解)
# ==============================================================
# Bunching 手写估计:模拟数据 + 多项式反事实 + excess mass
# ==============================================================
import numpy as np
import matplotlib.pyplot as plt
from numpy.polynomial import polynomial as P

rng = np.random.default_rng(20260911)

# --- 1. 模拟数据:与 Stata 版本一致 ---
N = 50000
n = np.exp(rng.normal(8.5, 0.6, N))
e_true, t0, t1, zstar = 0.3, 0.03, 0.10, 5000
z0 = n * (1 - t0) ** e_true
dz = zstar * e_true * (t1 - t0) / (1 - t0)
z = z0.copy()
bunch_mask = (z0 >= zstar) & (z0 <= zstar + dz)
z[bunch_mask] = zstar                      # 把边际个体挤到拐点

# --- 2. 分 bin ---
binw = 100.0
edges = np.arange(2000, 10001, binw)
centers = (edges[:-1] + edges[1:]) / 2
cnt, _ = np.histogram(z, bins=edges)

# --- 3. 多项式拟合反事实(排除拐点 ±500) ---
rel = centers - zstar
excluded = np.abs(rel) <= 500
mask = ~excluded
coef = np.polyfit(rel[mask], cnt[mask], 4)
cnt_hat = np.polyval(coef, rel)

B = (cnt[excluded] - cnt_hat[excluded]).sum()
h0 = cnt_hat[np.abs(rel) <= binw / 2].mean() / binw
e_hat = (B / (h0 * zstar)) / ((t1 - t0) / (1 - t0))
print(f"Excess mass B = {B:.1f}")
print(f"Saez elasticity e_hat = {e_hat:.3f}  (true = {e_true})")

# --- 4. 可视化 ---
fig, ax = plt.subplots(figsize=(8, 4.5))
ax.bar(centers, cnt, width=binw*0.9, color='#cccccc', label='实际人数')
ax.plot(centers, cnt_hat, lw=2, color='#1c5d6e', label='反事实密度(4 阶多项式)')
ax.axvline(zstar, ls='--', color='#a03a2c', label='kink z*=5000')
ax.axvspan(zstar-500, zstar+500, color='#b87333', alpha=0.15, label='excluded window')
ax.set_xlabel('月应纳税所得额'); ax.set_ylabel('每 bin 人数')
ax.set_title(f'Bunching: excess mass B={B:.0f},  e={e_hat:.3f}')
ax.legend(); plt.tight_layout(); plt.show()

06 论文案例

英文经典文献

English · AEJ:EP
Do Taxpayers Bunch at Kink Points?
Emmanuel Saez · American Economic Journal: Economic Policy, 2010
Bunching 方法的奠基之作。用美国个人所得税 1040 报税数据,证明收入分布在 EITC(劳动所得税抵免)kink 处有聚束,提出 reduced-form 弹性估计量(即本页 Eq. 3.6)。任何做 bunching 的论文都必须引这篇。
English · JEL
Bunching
Henrik Jacobsen Kleven · Journal of Economic Literature, 2016
最权威的 bunching 综述。系统区分 kink vs notch、无摩擦 vs 有优化摩擦、reduced-form vs structural bunching。是写论文 introduction 部分的标准引用。
English · AER
Adjustment Costs, Firm Responses, and Micro vs. Macro Labor Supply Elasticities: Evidence from Danish Tax Records
Chetty, Friedman, Olsen & Pistaferri · American Economic Review, 2011
用丹麦全额个税 administrative data 发现:微观 bunching 峰是"弥散"的(个体不能无限精确优化),由此引入优化摩擦概念,估计的微观弹性与宏观弹性差异巨大。
English · 理论起源
The Effect of Taxes on Labor Supply (Diamond 1980 讲座)
Peter A. Diamond · 1980 年代起关于 kink / 预算集非线性的早期讨论
在 Saez (2010) 之前,Diamond 等人已讨论过非线性预算线下劳动供给的识别问题;Saez 把它变成了可操作的 reduced-form 工具。

中文顶刊与中文工作论文

中文 · 《世界经济》
个人所得税改革与劳动供给:基于调整成本与收益的研究
《世界经济》2024 年第 4 期(中国社会科学院世界经济与政治研究所)
利用 2011、2018 年个税改革在免征额与 2000 元阈值附近的聚束特征,识别劳动者对工作激励的行为反应。研究发现考虑调整成本后劳动供给弹性约为 0.409,是忽略调整成本估计值的 3.3 倍——是中文顶刊里 bunching 方法用得最规范的一篇。
中文 · 综述
聚束分析法及其在税收政策评估领域的应用
《中央财经大学学报》系列综述
系统介绍 bunching 方法的反事实分布拟合、excess mass 计算、超越方程求解弹性、kink 与 notch 区别。适合作为中文入门读物。
中文 · 工作论文
企业应对税收优惠的行为决策与福利损失(小微企业所得税减半征收)
2025 年工作论文(公开报道)
利用小微企业年应纳税所得额减半征收政策的门槛,用 bunching 估计企业面对税率变化的应纳税所得额反应,计算应税收入弹性与无谓损失。是企业层面 bunching 应用的代表性范式。
English · WP(中国情境)
Bunching at Kinked Wage Deduction: Elasticity of Substitution between Skilled and Unskilled Labor in China
Jianguo Wang 等(中国企业层面数据)
利用中国企业工资薪金税前扣除按企业平均工资比例设置的 kink,发现企业平均工资分布在 kink 处显著聚束,企业通过调整技能/非技能劳动力比例来压低应税平均工资,借此估计技能—非技能劳动的替代弹性。

07 常见错误

错误 1:bin width 拍脑袋,不做稳健性

bin 太粗会把峰"磨平"、$B$ 被低估;bin 太碎会引入噪声、$B$ 被高估。必须报告多档 bin width(如 50/100/200)下的 $B$ 与 $\hat e$,并附 placebo kink 检验。

错误 2:反事实分布把峰本身也拟合进去

回归多项式时若不 excluded 拐点附近窗口,4 阶多项式会把"鼓包"当成真实分布趋势,导致 $\hat h_0(z^*)$ 偏高、$B$ 被严重低估。必须先选定 excluded window(通常靠图形判断或迭代法),再回归。

错误 3:忽略密集边际(intensive margin)与调整成本

Saez (2010) 假设无摩擦。Chetty et al. (2011) 证明现实中个体因调整成本只能"近似"聚束,峰是弥散的。若直接用 Eq. 3.6 会低估真实弹性。中文文献(《世界经济》2024)已开始引入调整成本修正。

错误 4:标准误只用 bin-level OLS

bin 是构造出来的,OLS 残差独立同分布假设不成立;多项式阶数、excluded window 选择也带不确定性。必须用个体层面 bootstrap(或 parametric bootstrap),报告 BCa / percentile 置信区间。

错误 5:把 kink 当 notch 处理(或反过来)

Notch 会产生"支配区间"空洞,必须先识别 dominated region 再补回;kink 没有空洞,直接拟合即可。混淆二者会错误地把"无人区间"当作真实分布的一部分。

错误 6:不做 placebo kink

在离真实拐点较远的位置随机设一个假 kink,重复同样流程,$B$ 应该不显著。如果 placebo kink 也显著,说明你看到的"峰"只是数据本身的波动或其他制度因素,bunching 识别无效。

08 与其他方法的交叉链接

  • RDD(07):同属"利用阈值做准实验"。RDD 看结果变量在 cutoff 的跳跃,bunching 看 running variable 密度在 cutoff 的隆起。两者都需要"无 manipulation"假设,都要做 rddensity / placebo 类检验。
  • IV(05)DID(06):bunching 估计的是"边际个体"的局部弹性,外推到全体需要结构模型;DID/IV 估计的是处理效应。写论文时常把 bunching 作为机制检验——证明 bunching 峰确实由税率变化驱动。
  • 非参/半参方法(29):反事实密度的多项式拟合本质是局部多项式非参数估计;多项式阶数选择、带宽选择与核回归的偏差—方差权衡同源。
  • 合成控制(24):bunching 内部的"反事实分布"思想与 SCM 的"反事实合成"在哲学上一致——都是在没有 treatment 的世界里构造一条平滑基准线。