{"spec_id":"calibration-beer-lambert","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncalibration-beer-lambert: Beer-Lambert Calibration Curve\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom scipy import stats\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nPI_ALPHA = 0.18 if THEME == \"dark\" else 0.10  # more visible against near-black background\n\n# Imprint palette — brand green always first\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]\nSECOND = IMPRINT_PALETTE[1]\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — varied standard set to distinguish from sibling implementations\nnp.random.seed(7)\nconcentrations = np.array([0.0, 1.5, 3.0, 5.0, 7.0, 9.0, 11.0, 13.5])\nepsilon_l = 0.045\ntrue_absorbance = epsilon_l * concentrations\nmeasured_absorbance = true_absorbance + np.random.normal(0, 0.008, len(concentrations))\nmeasured_absorbance[0] = 0.001  # blank near zero\n\ndf = pd.DataFrame({\"Concentration (mg/L)\": concentrations, \"Absorbance\": measured_absorbance})\n\n# Linear regression\nslope, intercept, r_value, _, _ = stats.linregress(concentrations, measured_absorbance)\nr_squared = r_value**2\n\n# Prediction interval (wider than CI, spec-required)\nn = len(concentrations)\nx_mean = np.mean(concentrations)\nfit_x = np.linspace(-0.5, 15.0, 200)\nfit_y = slope * fit_x + intercept\nresiduals = measured_absorbance - (slope * concentrations + intercept)\nse_pred = np.sqrt(\n    (np.sum(residuals**2) / (n - 2)) * (1 + 1 / n + (fit_x - x_mean) ** 2 / np.sum((concentrations - x_mean) ** 2))\n)\nt_crit = stats.t.ppf(0.975, df=n - 2)\npred_upper = fit_y + t_crit * se_pred\npred_lower = fit_y - t_crit * se_pred\n\n# Unknown sample at a different location from sibling implementations (~12 mg/L)\nunknown_absorbance = 0.54\nunknown_concentration = (unknown_absorbance - intercept) / slope\n\n# Canvas: landscape 3200×1800 px — figsize × dpi, no bbox_inches='tight'\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\n\n# Prediction interval band (alpha theme-adaptive for dark-bg visibility)\nax.fill_between(fit_x, pred_lower, pred_upper, color=BRAND, alpha=PI_ALPHA, label=\"95% Prediction Interval\")\n\n# Scatter + regression line with 95% CI via regplot\nsns.regplot(\n    data=df,\n    x=\"Concentration (mg/L)\",\n    y=\"Absorbance\",\n    ax=ax,\n    ci=95,\n    color=BRAND,\n    scatter_kws={\"s\": 80, \"edgecolor\": \"white\", \"linewidths\": 0.7, \"zorder\": 5},\n    line_kws={\"linewidth\": 2.0, \"zorder\": 4},\n    label=\"Linear Fit (95% CI)\",\n)\n\n# Rug ticks showing calibration standard positions along concentration axis\nsns.rugplot(x=concentrations, height=0.04, color=BRAND, alpha=0.5, expand_margins=False, ax=ax)\n\n# Unknown sample marker\nax.plot(\n    unknown_concentration,\n    unknown_absorbance,\n    marker=\"D\",\n    markersize=9,\n    color=SECOND,\n    markeredgecolor=\"white\",\n    markeredgewidth=0.7,\n    zorder=6,\n    label=\"Unknown Sample\",\n)\n\n# Dashed projection lines to axes\nax.plot(\n    [unknown_concentration, unknown_concentration],\n    [0, unknown_absorbance],\n    linestyle=\"--\",\n    color=SECOND,\n    linewidth=1.0,\n    alpha=0.7,\n)\nax.plot(\n    [0, unknown_concentration],\n    [unknown_absorbance, unknown_absorbance],\n    linestyle=\"--\",\n    color=SECOND,\n    linewidth=1.0,\n    alpha=0.7,\n)\n\n# Regression equation annotation\neq_text = f\"y = {slope:.4f}x + {intercept:.4f}\\nR² = {r_squared:.4f}\"\nax.text(\n    0.05,\n    0.93,\n    eq_text,\n    transform=ax.transAxes,\n    fontsize=9,\n    verticalalignment=\"top\",\n    color=INK,\n    bbox={\"boxstyle\": \"round,pad=0.4\", \"facecolor\": ELEVATED_BG, \"edgecolor\": INK_SOFT, \"alpha\": 0.9},\n)\n\n# Unknown sample annotation — shows interpolation direction explicitly (storytelling)\nax.annotate(\n    f\"A = {unknown_absorbance:.2f} AU  →  c = {unknown_concentration:.1f} mg/L\",\n    xy=(unknown_concentration, unknown_absorbance),\n    xytext=(unknown_concentration - 6.5, unknown_absorbance + 0.07),\n    fontsize=7.5,\n    color=SECOND,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": SECOND, \"lw\": 1.1},\n)\n\nax.set_xlabel(\"Concentration (mg/L)\", fontsize=10)\nax.set_ylabel(\"Absorbance\", fontsize=10)\nax.set_title(\"calibration-beer-lambert · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, labelcolor=INK_SOFT)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_xlim(-0.5, 15.5)\nax.set_ylim(-0.04, 0.75)\nax.legend(fontsize=8, loc=\"lower right\", framealpha=0.9)\nsns.despine(ax=ax)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}