用Python绘制埃奇沃思盒:无差异曲线与帕累托最优可视化

发布时间:2026/10/13 17:38:28
用Python绘制埃奇沃思盒:无差异曲线与帕累托最优可视化 很多人学微观经济学的时候第一次接触埃奇沃思盒Edgeworth Box都会觉得有点抽象。两条坐标轴、两个消费者、两种商品、一堆弯弯曲曲的无差异曲线要全靠手绘来理解交换经济里的帕累托最优确实挺费劲。我当年也是这么过来的直到后来自己动手写代码才发现把这个盒子画出来、动起来比看十页教科书都有用。这篇博文就用Python核心库matplotlib带你一步步把埃奇沃思盒画出来顺便把背后的一般均衡思想也聊透。这个内容适合几类人看正在学中级微观经济学、被交换经济模型绕晕的学生想用可视化辅助研究或备课的经济学研究者以及Python刚入门、想找点有意思的绘图项目练手的读者。我会把原理、代码和踩过的坑都放在一起你照着敲一遍基本就彻底理解了。1. 内容整体设计与思路拆解1.1 埃奇沃思盒到底是什么先花点时间把埃奇沃思盒本身说清楚。它是英国经济学家埃奇沃思Francis Ysidro Edgeworth提出的一种分析工具用来研究两个消费者、两种商品、总量固定的纯交换经济。盒子的宽度代表商品X的总量高度代表商品Y的总量左下角是消费者A的原点右上角是消费者B的原点坐标系是反向的。盒子里每个点都代表一种分配方案A获得多少X和YB获得剩下的部分两者之和恰好等于社会总禀赋。这一点是整个模型的基石——没有生产只有交换总量被限制死了。在盒子里画出A和B各自的无差异曲线族之后就能讨论“哪些分配是有效率的”。有效率意味着不可能在不损害任何一方的条件下让另一个人变得更好这种分配对应的点正好是两组无差异曲线的切点。把这些切点连起来就得到一条从A原点到B原点的曲线叫做契约曲线Contract Curve也叫帕累托集。有意思的是这条线上的点只是“有效率”但未必“公平”。实际成交点落在哪里取决于双方的初始禀赋和议价能力。埃奇沃思盒的强大之处就在于你可以在同一张图里把这些经济学概念全部可视化出来。1.2 为什么用Python而不是手绘手绘埃奇沃思盒当然是每个经济学学生的基本功但手绘有几个天然的短板第一无差异曲线族很难画整齐曲线多了以后全是乱线第二切点位置的判断依赖直觉误差非常大第三想对比不同效用函数或者不同初始禀赋的场景就要重新画一张图效率极低。用Python画埃奇沃思盒核心优势就是参数化了。效用函数一变、初始禀赋一变、总量一变重新运行脚本就得到新的图。而且你可以很容易地在图里叠加多个元素——无差异曲线、禀赋点、契约曲线、交换路径箭头手绘几乎不可能做到这种信息密度。Python生态里做这件事的主力工具是matplotlib。它不是专门为经济学设计的但恰恰因为通用反而可以自由组织图形对象。加上numpy做数值计算毫无压力。注意我也见过有人用Desmos或者GeoGebra在线工具画但那些工具定制性弱且不容易批量出图。如果你需要把图插进论文、课件甚至网页给自己做笔记matplotlib才是自由度最高的选择。1.3 核心图形元素拆解一张完整的埃奇沃思盒图至少需要这几个元素底部和左侧坐标轴表示A的消费数量顶部和右侧坐标轴表示B的消费数量盒子的矩形区域本身代表总禀赋约束A的无差异曲线族从A原点出发向左上凸B的无差异曲线族从B原点出发向右下凸这里比较容易搞混因为B的原点在右上角初始禀赋点W以及经过W的两条无差异曲线围成的“互利交换空间”核Core。如果你想展示交换结果还需要画出契约曲线。我的经验是先画盒子框架再画无差异曲线最后叠加点和曲线。分图层画出了问题也好排查。2. 核心细节解析与实操要点2.1 坐标系统与函数轴的技巧画埃奇沃思盒最核心也最容易被新人卡住的地方就是坐标系转换。A的坐标从(0,0)到(总量X, 总量Y)这很直观但B的坐标完全相反B拥有的X数量要从盒子右边的边界向左数Y数量要从盒子上边界向下数。在matplotlib里怎么实现这个“反向观察”最直接的办法不是真的去翻转坐标轴而是在数据层面做换算。例如总量为(X_total, Y_total)B消费的X数量是X_b它对应的绘图坐标就是X_total - X_b。同理无差异曲线也不能全部以左下角为原点直接绘制。A的曲线用标准坐标系计算B的曲线则需要先把B的数据点取负值偏移让B的“原点”落在盒子右上角。这一步如果理解不到位画出来的B曲线会完全错位我最初就栽在这里图看起来就像两堆毫无关系的散点。2.2 无差异曲线族怎么生成无差异曲线的形状由效用函数决定。教科书最常用的柯布-道格拉斯效用函数Cobb-DouglasU(X,Y) X^α · Y^(1-α)。给定一个效用水平U₀解出Y关于X的表达式Y (U₀ / X^α)^(1/(1-α))。用numpy生成一组等间距的U值就能画出同一条效用水平下对应的整条曲线用多组U值就得到无差异曲线族。我自己的习惯是用np.linspace(1, 10, 8)这类方式生成效用水平序列这样图上的曲线族疏密适中又不至于相互重叠。如果你用固定步长生成U值靠近原点的地方会非常密集看图的时候什么都分辨不出来。2.3 工具版本与选型建议画这种图建议直接用matplotlib的面向对象接口plt.subplots不要用纯plt.plot那一套因为你需要频繁在同一个坐标系里叠加元素、设置不同轴标签面向对象方式让代码结构更清晰后期调整也更顺手。numpy是必须的曲线数值计算不能没有它。如果以后想把埃奇沃思盒做成交互式的可以在matplotlib的基础上加ipywidgets用滑块调效用参数和初始禀赋做到即时更新。我后面会简单提一下这个方向但这篇先用静态图把逻辑讲透。3. 实操过程与核心环节实现3.1 第一步设定参数与画盒子先把经济环境定义清楚。比如某经济体里有两个消费者A和B两种商品X和Y社会总禀赋也就是盒子尺寸设为X_total10, Y_total10A和B的初始禀赋为W_A(6, 4)W_B(4, 6)。代码里先把盒子的长方形框架画出来。盒子左下角对应A的原点右上角对应B的原点。我在图里会标注“消费者A”和“消费者B”各自的原点位置避免读者看图时迷失方向。import numpy as np import matplotlib.pyplot as plt X_total 10 Y_total 10 W_A np.array([6, 4]) W_B np.array([X_total, Y_total]) - W_A fig, ax plt.subplots(figsize(7, 7)) ax.plot([0, X_total, X_total, 0, 0], [0, 0, Y_total, Y_total, 0], k-, lw1.5) ax.set_xlim(0, X_total) ax.set_ylim(0, Y_total) ax.set_xlabel(Good X for A →) ax.set_ylabel(Good Y for A ↑) ax.set_title(Edgeworth Box) plt.show()这段代码跑出来就是最简单的空盒子。注意我用set_xlim把视口限制在盒子范围内这是为了避免坐标轴的延展超出盒子边界让图形比例失调。3.2 第二步生成A的无差异曲线先定义柯布-道格拉斯的逆函数给定效用水平UX取值数组计算对应的Y。这里需要注意X不能为0否则对数/幂运算会出现除零或无穷值所以X从0.01开始取。A的效用权重α我设为0.5意味着A对两种商品的偏好相同对称偏好。def indifference_curve(eff_level, alpha, x_vals): if alpha 1: return eff_level / x_vals return (eff_level / (x_vals**alpha)) ** (1 / (1 - alpha)) x_grid np.linspace(0.01, X_total, 300) for eff in [2, 4, 6, 8, 10, 12]: y_vals indifference_curve(eff, 0.5, x_grid) ax.plot(x_grid, y_vals, b-, alpha0.7, labelA if eff 2 else )这条曲线的含义是对A来说这条线上所有(X,Y)组合带来的效用相同。曲线越远离A的原点效用越高。3.3 第三步生成B的无差异曲线核心难点B的原点在右上角不能直接复用A的绘图坐标。正确的做法是先站在B的视角以B的原点为“左下角”生成曲线数值然后把这些数值映射到A的坐标系里。具体逻辑如果B消费X_b那么在A坐标系里的位置是X_total - X_b如果B消费Y_b在A坐标系里的位置是Y_total - Y_b。所以B曲线的逻辑是for eff in [2, 4, 6, 8, 10, 12]: y_b indifference_curve(eff, 0.5, x_grid) x_plot X_total - x_grid y_plot Y_total - y_b ax.plot(x_plot, y_plot, r--, alpha0.7, labelB if eff 2 else )注意这里有两个容易出错的地方。第一x_grid是B视角的X消费量从0.01到X_total第二x_plot和y_plot才是绘制使用的坐标。如果你直接拿y_b去plotB的曲线会从盒子左上角冒出来完全错位。从视觉上看B的每一元效用曲线都是红色的虚线和A的蓝色实线交叉。两组曲线的切点肉眼就能找出来这就是帕累托最优点的雏形。提示如果想强调某两条曲线在初始禀赋点相交只需把eff替换成该点的实际效用值即可。效用值计算公式是U X^0.5 * Y^0.5直接用numpy算。3.4 第四步标注初始禀赋点与核心区域初始禀赋W_A(6,4)对应W_B(4,6)。在图上用一个黑色星号标注并添加文字说明。然后分别计算A在W点的效用水平B在W点的效用水平把对应两条无差异曲线高亮出来。U_A_W np.sqrt(W_A[0] * W_A[1]) U_B_W np.sqrt(W_B[0] * W_B[1]) y_A_w indifference_curve(U_A_W, 0.5, x_grid) ax.plot(x_grid, y_A_w, b-, lw2.5, alpha0.9) y_B_w indifference_curve(U_B_W, 0.5, x_grid) ax.plot(X_total - x_grid, Y_total - y_B_w, r--, lw2.5, alpha0.9) ax.plot(*W_A, k*, markersize15) ax.annotate(W (Endowment), xyW_A, xytext(6.5, 5.2), arrowpropsdict(arrowstyle-, colorblack))经过W的两条无差异曲线围成的区域就是互利交换的“核”。在这个区域里的任一点A和B比初始状态都至少不下降且至少有一方严格变好。我通常用fill_between把这个区域填充出来直观冲击力很强。# 求高亮曲线在x_grid上的公共区间 common_x x_grid lower np.minimum(y_A_w, Y_total - y_B_w) upper np.maximum(y_A_w, Y_total - y_B_w) # 填充两条高亮曲线之间的区域 ax.fill_between(common_x, y_A_w, Y_total - y_B_w, where(y_A_w Y_total - y_B_w), colorgray, alpha0.3, labelCore)严格来说核的边界就是过W的这两条无差异曲线区域内所有分配都是双方自愿交换可能达成的结果。3.5 第五步画契约曲线契约曲线是A、B无差异曲线所有切点的集合。对柯布-道格拉斯效用函数来说切点条件等价于边际替代率相等。MRS_A (α/(1-α))·(Y/X)MRS_B用B视角计算时要小心偏置。推导后可以得到契约曲线的解析式。当α_A 0.5α_B 0.5时契约曲线是一条从A原点到B原点的直线Y X。这一点非常特殊因此我通常再用一个非对称例子来展示曲线形态比如让α_A0.7α_B0.3这时契约曲线会变成一条弧线。alpha_A 0.7 alpha_B 0.3 x_vals_cc np.linspace(0.1, X_total - 0.1, 200) # 契约曲线两消费者MRS相等的点 y_vals_cc (alpha_A/(1-alpha_A)) * (alpha_B/(1-alpha_B)) * x_vals_cc y_vals_cc y_vals_cc / (1 (alpha_A/(1-alpha_A)) * (alpha_B/(1-alpha_B))) ax.plot(x_vals_cc, y_vals_cc, g-, lw2, labelContract Curve)这里我不展开数学推导但你要知道逻辑链条A的MRS等于B的MRS再加上资源约束X_A X_B X_total、Y_A Y_B Y_total联立求解就能得到这条线。3.6 第六步完整的图与导出把前面几步整合到一个脚本里最终输出一张完整的埃奇沃思盒图。注意图例、标题、坐标轴标签都要齐全。导出时用dpi300的PNG或者直接存SVG方便插入论文或课件。plt.legend(locupper right) plt.tight_layout() plt.savefig(edgeworth_box.png, dpi300) plt.show()如果你像我一样经常需要对比参数建议直接把参数区块定义成字典放函数里封装好。下面是一个更工程化的写法。def draw_edgeworth_box(X_total10, Y_total10, W_A(6, 4), alpha_A0.5, alpha_B0.5, eff_levels[2,4,6,8,10,12]): W_B (X_total - W_A[0], Y_total - W_A[1]) fig, ax plt.subplots(figsize(7, 7)) ... return fig, ax这样之后想画十张八张对比图只是改参数的问题不需要重复复制代码。4. 常见问题与排查技巧实录4.1 B的无差异曲线位置错乱这是评论区最经常出现的问题。现象是红色虚线跑到了盒子外面或者和蓝色实线方向完全不匹配。原因几乎都是坐标转换错误。请记住B的曲线生成时x_grid代表的是B视角的X消费量这个值和A坐标系的关系是互补。任何一条B曲线上的点绘制坐标都要经过X_total - 和Y_total - 的换算缺一不可。另一种情况是x_grid端点取了0导致曲线在端点发散看起来整条线飞出去了。把起点改成0.01、终点改成X_total - 0.01问题就消失了。4.2 曲线族太密或太疏效用等级序列的生成方式直接影响可读性。如果你用np.linspace(1, 20, 100)图上会有100条线密集到完全看不出形状用np.linspace(1, 10, 5)又太稀疏切点不好找。我个人的经验是8到10条曲线最合适。效用等级用2到12之间取8个点视觉密度刚刚好。4.3 填充核区域失败fill_between要求两条曲线都定义在同一个x网格上而且where条件的逻辑不能搞反。我建议先把两条高亮曲线分别存成数组再检查两者的大小关系确认后再填充。打印几组值对照检查比自己盯着图猜要高效得多。4.4 图例和图例框遮挡曲线matplotlib默认的图例位置在右上角但埃奇沃思盒往往右上角正好是B的高效用区域会挡住重要图形。我习惯把图例放在盒子外面下方或者放在左下角空白处。如果放在图外记得用bbox_to_anchor参数。4.5 存出来的图字太小存图前检查figsize。如果你在屏幕上显示时觉得字号合适导出时很可能因为默认figsize是6×4英寸而显得很小。建议figsize直接设成(8,8)字体大小用fontsize参数统一设置导出dpi300时依然清晰。5. 交互式方向与扩展思路静态图能解决“理解”问题但如果你想探索“如果效用参数变了会怎样”静态图就不够用了。我强烈建议你做一个交互式版本用ipywidgets加两个滑块一个控制α_A一个控制α_B每动一下滑块图就重新绘制。这个扩展项目很值得做因为参数对契约曲线形状的影响非常直观。比如α_A和α_B差别越大契约曲线弯得越厉害差别为0时契约曲线就是对角线。这个“眼见为实”的过程比任何文字推导都有说服力。你还可以扩展成多期交换的动态模拟。给双方设定一个具体的“交换路径”从W点逐步走向契约线上某个交易结果用动画matplotlib.animation展示过程。这适用于讲价格调整机制或瓦尔拉斯试探过程我见过有人做了之后课堂效果非常好。如果数据是真实的比如实验经济学或者调查数据你还可以把实际观察到的交易结果散布在盒子里对比理论预测点做教学演示的时候非常震撼。6. 关于代码风格的一点反思画埃奇沃思盒这个任务规模不大但代码风格会影响之后扩展的体验。我吃过亏的地方是一开始把所有画图命令全部罗列在脚本里改一个参数就要全局搜索替换后来全部重构为函数才舒服。建议你一开始就按“参数定义-数据计算-绘图-标注-导出”五个区块组织代码区块之间用空行和注释分割。哪怕只有几十行这种结构也值得建立。等画交互式版本、多子图对比版本时你会感谢自己当初的克制。另外一个建议数值计算和画图分开。先把所有的曲线数值都算好放在变量里再集中画图。这样遇到调试问题可以先用print看看数据是否正确再判断是不是绘图配置的问题排查路径清晰很多。在这篇博文里我用matplotlibnumpy把埃奇沃思盒从空框架一步步画成了包含无差异曲线族、初始禀赋点、核和契约曲线的完整模型图。重点放在B消费者坐标转换、曲线生成和常见错误排查上因为这些都是实操中最容易卡住的地方。按这个流程走完一遍你既掌握了埃奇沃思盒的数学逻辑也顺手练熟了Python绘图的基本功。如果你自己跑代码时还有哪一步不太顺大概率是坐标补数或者参数边界的问题按第4部分对照检查就行。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询