elasticity.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. """Numerical elasticities of Bayesian x50 and s50 to scientific parameters."""
  2. import numpy as np
  3. import pandas as pd
  4. import matplotlib.pyplot as plt
  5. from .core import P_with, slope_at_x, theta_max, x_at_p
  6. PARAMETER_NAMES = ("omega", "a", "b", "s", "k", "vartheta")
  7. def _scientific_vector(theta_hat):
  8. return np.asarray(theta_hat[:6], float)
  9. def _as_theta(values):
  10. omega, a, b, s, k, vartheta = map(float, values)
  11. cap = theta_max(a, b, k, s)
  12. if not (0 < omega < 1 and a > k > 0 and b > 0 and s > 0 and 0 < vartheta < cap):
  13. raise ValueError("Perturbed parameter vector violates model constraints.")
  14. return omega, a, b, s, k, vartheta, cap
  15. def elasticity_x50_s50(theta_hat, relative_step=1e-4):
  16. """Return dimensionless local elasticities using central perturbations."""
  17. base = _scientific_vector(theta_hat)
  18. base_theta = _as_theta(base)
  19. x0 = x_at_p(base_theta)
  20. s0 = slope_at_x(base_theta, x0)
  21. rows = []
  22. for j, name in enumerate(PARAMETER_NAMES):
  23. step = relative_step * max(abs(base[j]), 1e-8)
  24. plus, minus = base.copy(), base.copy()
  25. plus[j] += step
  26. minus[j] -= step
  27. try:
  28. tp, tm = _as_theta(plus), _as_theta(minus)
  29. xp, xm = x_at_p(tp), x_at_p(tm)
  30. sp, sm = slope_at_x(tp, xp), slope_at_x(tm, xm)
  31. ex = (base[j] / x0) * (xp - xm) / (2.0 * step)
  32. es = (base[j] / s0) * (sp - sm) / (2.0 * step)
  33. except (ValueError, FloatingPointError):
  34. ex = es = np.nan
  35. rows.append({"Parameter": name, "Elasticity_x50": ex, "Elasticity_s50": es})
  36. return pd.DataFrame(rows)
  37. def elasticity_full_trim(fit_result, relative_step=1e-4):
  38. frames = []
  39. for dataset in ("FULL", "TRIM"):
  40. frame = elasticity_x50_s50(fit_result[dataset]["theta"], relative_step)
  41. frame.insert(0, "Dataset", dataset)
  42. frames.append(frame)
  43. return pd.concat(frames, ignore_index=True)
  44. def plot_combined_elasticity(table):
  45. """Plot all Bayesian parameter elasticities in one two-panel figure."""
  46. parameter_order = list(PARAMETER_NAMES)
  47. display_labels = [r"$\pi$", r"$a$", r"$b$", r"$s$", r"$k$", r"$\vartheta$"]
  48. x = np.arange(len(parameter_order))
  49. width = 0.36
  50. fig, axes = plt.subplots(1, 2, figsize=(11, 4.2), dpi=180)
  51. for offset, dataset in zip((-width / 2, width / 2), ("FULL", "TRIM")):
  52. block = table.set_index(["Dataset", "Parameter"]).loc[dataset].reindex(parameter_order)
  53. axes[0].bar(x + offset, block["Elasticity_x50"].abs(), width=width, label=dataset)
  54. axes[1].bar(x + offset, block["Elasticity_s50"].abs(), width=width, label=dataset)
  55. for label, axis in zip(("A", "B"), axes):
  56. axis.set_xticks(x, display_labels)
  57. axis.set_ylabel("Absolute elasticity")
  58. axis.text(0.02, 0.97, label, transform=axis.transAxes, ha="left", va="top", fontsize=15)
  59. axis.grid(alpha=0.25, axis="y")
  60. axes[0].legend(frameon=True, fontsize=9, loc="upper left")
  61. fig.tight_layout()
  62. return fig, axes