Skip to the content.

← 目次← 前: 06次: 08 →

07. 非線形方程式の数値解法

テーマ: 需要・供給の均衡価格と、従価税の帰着(tax incidence)

学習点: 無名関数(lambda), 高階関数, 二分法, 弾力性の数値微分

依存: 標準ライブラリのみ / 難易度: 基礎

実行方法

uv run 07_market_equilibrium.py

スクリプト冒頭の PEP 723 メタデータ(# /// script)により、必要なライブラリは uv が自動的に仮想環境へ導入します。事前の pip install は不要です。

解説

何をするプログラムか

ミクロ経済学の需要・供給モデルを数値的に解くスクリプトです。需要関数 Qd(p) = 1000·p^(−1.2)、供給関数 Qs(p) = 12·p^(0.8) という定弾力性型(べき乗型)の関数を使うため、均衡価格は手計算では求めにくく、二分法という数値解法で Qd(p) = Qs(p) を満たす価格を探します。

後半では従価税(価格に比例する税、消費税がその代表例です)を導入し、税率を 0〜30% まで変えたときの消費者価格・生産者価格・取引数量・税収を計算します。「税は法律上誰が納めるかではなく、需要と供給の弾力性の比で実質的な負担が決まる」という税の帰着(tax incidence)の理論を、数値で検証するのが狙いです。

コードの読みどころ

理論的背景

均衡は Qd(p) = Qs(p) を満たす価格で決まります。従価税 t を課すと、消費者の支払う価格 p と生産者の受け取る価格 p/(1+t) が分離し、取引数量は減少します。このとき消費者価格の上昇分が税全体に占める割合(消費者負担率)は、線形近似のもとで Es / (Es + Ed ) となることが知られています。本例では Ed = −1.2、Es = +0.8 なので理論値は 0.8/(0.8+1.2) = 40% です。需要が弾力的(買い手が逃げやすい)ほど消費者負担は小さく、供給が弾力的なほど生産者負担が小さくなります。

実行結果の見方

まず均衡価格 9.1287、均衡数量 70.3896 が求まり、数値微分による弾力性が設定どおり −1.200 と +0.800 になっていることを確認してください。従価税の表では、税率 5% のとき消費者負担率は 40.6% で理論値 40.0% にほぼ一致し、税率が 30% に上がると 43.2% へと乖離が広がります。弾力性比の公式はあくまで小さな税に対する線形近似であることが、最終行の注記とともに読み取れます。また税率が上がるほど数量が 70.39 から 62.06 へ減っており、課税が取引そのものを縮小させる(死荷重を生む)様子も観察できます。

ソースコード

# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""07: 非線形方程式の数値解法 ---------------------------------------------
テーマ: 需要・供給の均衡価格と、従価税の帰着(tax incidence)
学習点: 無名関数(lambda), 高階関数, 二分法, 弾力性の数値微分
根拠: 均衡は Qd(p)=Qs(p)。従価税 t を課すと消費者価格 p と生産者価格 p/(1+t)
      が乖離する。税の負担配分は需要・供給の価格弾力性の比で決まり、
      消費者負担割合 = Es / (Es + |Ed|) が成り立つ(線形近似)。
"""
from typing import Callable

Demand = Callable[[float], float]
Supply = Callable[[float], float]


def solve(f: Callable[[float], float], lo: float, hi: float, tol=1e-10) -> float:
    """f(lo) と f(hi) の符号が異なる区間で f(x)=0 を二分法で解く。"""
    flo = f(lo)
    for _ in range(300):
        mid = (lo + hi) / 2
        fm = f(mid)
        if abs(fm) < tol or hi - lo < tol:
            return mid
        if flo * fm < 0:
            hi = mid
        else:
            lo, flo = mid, fm
    return (lo + hi) / 2


def elasticity(q: Callable[[float], float], p: float, h=1e-5) -> float:
    """点弾力性 (dQ/dp)*(p/Q) を中心差分で数値的に求める。"""
    dq = (q(p + h) - q(p - h)) / (2 * h)
    return dq * p / q(p)


def main() -> None:
    # 非線形(定弾力性型)の需要・供給関数
    demand: Demand = lambda p: 1000 * p ** -1.2      # 需要の価格弾力性 -1.2
    supply: Supply = lambda p: 12 * p ** 0.8         # 供給の価格弾力性 +0.8

    p_star = solve(lambda p: demand(p) - supply(p), 0.01, 1000)
    q_star = demand(p_star)
    print(f"均衡価格 p* = {p_star:.4f} / 均衡数量 q* = {q_star:.4f}")
    print(f"需要の弾力性 Ed = {elasticity(demand, p_star):+.3f}")
    print(f"供給の弾力性 Es = {elasticity(supply, p_star):+.3f}\n")

    print("[従価税の帰着] 税率を変えたときの価格・数量・税収")
    print(f"{'税率':>6}{'消費者価格':>12}{'生産者価格':>12}{'数量':>10}"
          f"{'税収':>11}{'消費者負担率':>13}")
    ed, es = abs(elasticity(demand, p_star)), elasticity(supply, p_star)
    theory = es / (es + ed)
    for t in [0.00, 0.05, 0.10, 0.20, 0.30]:
        # 消費者が払う価格 p、生産者受取 p/(1+t)
        p = solve(lambda p: demand(p) - supply(p / (1 + t)), 0.01, 1000)
        pp = p / (1 + t)
        q = demand(p)
        rev = (p - pp) * q
        share = (p - p_star) / (p - pp) if t > 0 else float("nan")
        print(f"{t:>6.0%}{p:>12.4f}{pp:>12.4f}{q:>10.4f}{rev:>11.4f}"
              f"{share:>13.1%}")
    print(f"\n理論値(弾力性比による消費者負担率)= Es/(Es+|Ed|) = {theory:.1%}")
    print("※ 税率が小さいほど数値結果は線形近似の理論値に一致する。")


if __name__ == "__main__":
    main()

実行結果

均衡価格 p* = 9.1287 / 均衡数量 q* = 70.3896
需要の弾力性 Ed = -1.200
供給の弾力性 Es = +0.800

[従価税の帰着] 税率を変えたときの価格・数量・税収
    税率       消費者価格       生産者価格        数量         税収       消費者負担率
    0%      9.1287      9.1287   70.3896     0.0000         nan%
    5%      9.3086      8.8653   68.7603    30.4792        40.6%
   10%      9.4835      8.6213   67.2419    57.9714        41.1%
   20%      9.8193      8.1828   64.4914   105.5437        42.2%
   30%     10.1388      7.7991   62.0606   145.2047        43.2%

理論値(弾力性比による消費者負担率)= Es/(Es+|Ed|) = 40.0%
※ 税率が小さいほど数値結果は線形近似の理論値に一致する。

← 目次← 前: 06次: 08 →