{"spec_id":"calibration-beer-lambert","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ncalibration-beer-lambert: Beer-Lambert Calibration Curve\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave\nfrom scipy import stats\n\n\nLetsPlot.setup_html()\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Imprint palette: position 1 = calibration standards/regression; position 5 = unknown focal point\nBRAND = \"#009E73\"  # calibration standards + regression (Imprint position 1)\nFOCAL = \"#AE3030\"  # unknown sample highlight (Imprint position 5)\n\n# Theme-adaptive chrome tokens\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Data — calibration standards for UV-Vis spectrophotometry (Cu²⁺ at 810 nm)\nnp.random.seed(42)\nconcentrations = np.array([0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0])\nabsorbance_true = 0.045 * concentrations\nabsorbance_measured = absorbance_true + np.random.normal(0, 0.008, len(concentrations))\nabsorbance_measured[0] = max(0.002, absorbance_measured[0])\n\n# Linear regression\nslope, intercept, r_value, _p, _se = stats.linregress(concentrations, absorbance_measured)\nr_squared = r_value**2\n\n# Prediction interval over fit range\nn = len(concentrations)\nx_mean = np.mean(concentrations)\nx_fit = np.linspace(0, 12.5, 200)\ny_fit = slope * x_fit + intercept\nse_y = np.sqrt(np.sum((absorbance_measured - (slope * concentrations + intercept)) ** 2) / (n - 2))\nt_val = stats.t.ppf(0.975, n - 2)\npi = t_val * se_y * np.sqrt(1 + 1 / n + (x_fit - x_mean) ** 2 / np.sum((concentrations - x_mean) ** 2))\n\n# Unknown sample — concentration determined from absorbance via regression\nunknown_absorbance = 0.34\nunknown_concentration = (unknown_absorbance - intercept) / slope\n\n# DataFrames\ndf_standards = pd.DataFrame({\"concentration\": concentrations, \"absorbance\": absorbance_measured})\ndf_fit = pd.DataFrame({\"concentration\": x_fit, \"absorbance\": y_fit, \"upper\": y_fit + pi, \"lower\": y_fit - pi})\ndf_unknown = pd.DataFrame({\"concentration\": [unknown_concentration], \"absorbance\": [unknown_absorbance]})\ndf_segments = pd.DataFrame(\n    {\n        \"x\": [0.0, unknown_concentration],\n        \"y\": [unknown_absorbance, 0.0],\n        \"xend\": [unknown_concentration, unknown_concentration],\n        \"yend\": [unknown_absorbance, unknown_absorbance],\n    }\n)\n\neq_label = f\"y = {slope:.4f}x + {intercept:.4f}\\nR² = {r_squared:.5f}\"\ndf_eq = pd.DataFrame({\"x\": [1.0], \"y\": [0.49], \"label\": [eq_label]})\ndf_unknown_label = pd.DataFrame(\n    {\n        \"x\": [unknown_concentration + 0.5],\n        \"y\": [unknown_absorbance + 0.032],\n        \"label\": [f\"Unknown\\n({unknown_concentration:.1f} mg/L, A = {unknown_absorbance})\"],\n    }\n)\n\n# Build plot — layer_tooltips() is a lets-plot distinctive feature for interactive HTML output\nplot = (\n    ggplot()\n    + geom_ribbon(aes(x=\"concentration\", ymin=\"lower\", ymax=\"upper\"), data=df_fit, fill=BRAND, alpha=0.12)\n    + geom_line(aes(x=\"concentration\", y=\"absorbance\"), data=df_fit, color=BRAND, size=1.0)\n    + geom_segment(\n        aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), data=df_segments, color=FOCAL, size=0.7, linetype=\"dashed\"\n    )\n    + geom_point(\n        aes(x=\"concentration\", y=\"absorbance\"),\n        data=df_standards,\n        fill=BRAND,\n        color=\"white\",\n        size=3.5,\n        alpha=0.9,\n        shape=21,\n        stroke=1.0,\n        tooltips=layer_tooltips()\n        .title(\"Calibration Standard\")\n        .line(\"Concentration: @concentration mg/L\")\n        .line(\"Absorbance: @absorbance\"),\n    )\n    + geom_point(\n        aes(x=\"concentration\", y=\"absorbance\"),\n        data=df_unknown,\n        fill=FOCAL,\n        color=\"white\",\n        size=3.0,\n        shape=23,\n        stroke=1.0,\n        tooltips=layer_tooltips()\n        .title(\"Unknown Sample\")\n        .line(\"Concentration: @concentration mg/L\")\n        .line(\"Absorbance: @absorbance\"),\n    )\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=df_eq, size=5, color=INK, family=\"monospace\", hjust=0)\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"), data=df_unknown_label, size=4.5, color=FOCAL, hjust=0, fontface=\"italic\"\n    )\n    + labs(x=\"Concentration (mg/L)\", y=\"Absorbance\", title=\"calibration-beer-lambert · python · letsplot · anyplot.ai\")\n    + scale_x_continuous(limits=[-0.5, 13.5], breaks=[0, 2, 4, 6, 8, 10, 12])\n    + scale_y_continuous(limits=[-0.02, 0.58], breaks=[0, 0.1, 0.2, 0.3, 0.4, 0.5])\n    + coord_cartesian(xlim=[-0.5, 13.5], ylim=[-0.02, 0.58])\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        plot_title=element_text(size=16, color=INK, face=\"bold\"),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK_SOFT, size=0.3),\n        panel_grid_minor=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_border=element_blank(),\n        axis_ticks=element_blank(),\n        axis_ticks_length=0,\n        plot_margin=[30, 40, 20, 20],\n    )\n)\n\n# Save theme-suffixed PNG (3200×1800 via ggsize(800,450) × scale=4) and interactive HTML\nggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}