# ============================================================
# 4. 图 1:模型地图
# ============================================================
# 图形设计:05 章只突出真实 0、计数结果和比例结果;
# Heckman 作为下一章预告,用虚线灰框处理,避免抢占本章主线。
def draw_box(ax, xy, text, color, edge, w=2.85, h=0.72, fs=12, dashed=False):
x, y = xy
box = FancyBboxPatch(
(x - w / 2, y - h / 2), w, h,
boxstyle="round,pad=0.055,rounding_size=0.10",
facecolor=color, edgecolor=edge, linewidth=1.2,
linestyle="--" if dashed else "-"
)
ax.add_patch(box)
ax.text(x, y, text, ha="center", va="center", fontsize=fs, linespacing=1.3)
def arrow(ax, start, end, color="#333333", dashed=False):
arr = FancyArrowPatch(
start, end,
arrowstyle="-|>", mutation_scale=13,
linewidth=1.3, color=color,
linestyle="--" if dashed else "-",
shrinkA=8, shrinkB=8,
connectionstyle="arc3,rad=0.0"
)
ax.add_patch(arr)
fig, ax = plt.subplots(figsize=(12.5, 7.2))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.axis("off")
colors = {
"blue": ("#EAF4FF", "#3B82B6"),
"yellow": ("#FFF7D6", "#C99200"),
"green": ("#EDF8E9", "#5FA35B"),
"purple": ("#F3ECFF", "#8A68B8"),
"orange": ("#FFF1E3", "#D99045"),
"pink": ("#FCEAEA", "#CC6666"),
"gray": ("#F3F4F6", "#7A7A7A"),
}
# 节点坐标
pos = {
"start": (2.0, 6.2),
"in_data": (5.0, 6.2),
"trunc": (8.5, 6.2),
"zero_mean": (5.0, 5.0),
"tobit": (2.0, 3.9),
"real_zero": (5.0, 3.9),
"continuous": (3.5, 2.6),
"count": (6.5, 2.6),
"frac": (3.5, 1.4),
"hurdle": (6.5, 1.4),
"heckman": (9.2, 3.9),
}
draw_box(ax, pos["start"], "问题起点\n因变量受限或大量 0", *colors["blue"])
draw_box(ax, pos["in_data"], "样本仍在数据中?", *colors["yellow"])
draw_box(ax, pos["trunc"], "否:样本被截断\nTruncated regression", *colors["pink"])
draw_box(ax, pos["zero_mean"], "若样本在数据中\n0 的含义是什么?", *colors["green"])
draw_box(ax, pos["tobit"], "边界外取值被归并\nTobit", *colors["purple"])
draw_box(ax, pos["real_zero"], "真实零值\n是否发生 + 发生多少", *colors["blue"])
draw_box(ax, pos["continuous"], "正连续结果\nTwo-part model", *colors["orange"])
draw_box(ax, pos["count"], "计数结果\nHurdle / Zero-inflated", *colors["orange"])
draw_box(ax, pos["frac"], "比例结果\nFractional response", *colors["green"])
draw_box(ax, pos["hurdle"], "门槛或两类零值\nCount extensions", *colors["green"])
draw_box(ax, pos["heckman"], "结果变量不可观测\nHeckman selection\nChap. 06", *colors["gray"], dashed=True)
arrow(ax, (3.42, 6.2), (3.56, 6.2))
arrow(ax, (6.44, 6.2), (7.05, 6.2))
arrow(ax, (5.0, 5.85), (5.0, 5.37))
arrow(ax, (4.2, 4.65), (2.9, 4.15))
arrow(ax, (5.0, 4.65), (5.0, 4.25))
arrow(ax, (4.45, 3.55), (3.85, 2.95))
arrow(ax, (5.55, 3.55), (6.15, 2.95))
arrow(ax, (3.5, 2.25), (3.5, 1.78))
arrow(ax, (6.5, 2.25), (6.5, 1.78))
arrow(ax, (6.48, 3.9), (7.72, 3.9), dashed=True, color="#777777")
ax.text(9.2, 3.12, "提示:不是本章主线", ha="center", va="center", fontsize=10, color="#777777")
save_png_svg(fig, "limit_dep_alt_fig01_model_map")