{"spec_id":"curve-dose-response","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncurve-dose-response: Pharmacological Dose-Response Curve\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-06-24\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\nfrom matplotlib.patches import Patch\nfrom scipy.optimize import curve_fit\n\n\n# Theme tokens — Imprint palette (see prompts/default-style-guide.md)\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\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nCOLOR_A = IMPRINT_PALETTE[0]  # brand green — Imatinib (first series, always #009E73)\nCOLOR_B = IMPRINT_PALETTE[1]  # lavender — Erlotinib\npalette_dict = {\"Imatinib\": COLOR_A, \"Erlotinib\": COLOR_B}\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 — synthetic pharmacological dose-response for two kinase inhibitors\nnp.random.seed(42)\n\nconcentrations = np.logspace(-9, -4, 8)\n\nbottom_a, top_a, ec50_a, hill_a = 5.0, 95.0, 3e-7, 1.2\nbottom_b, top_b, ec50_b, hill_b = 10.0, 80.0, 5e-6, 0.9\n\n# 4PL logistic: response = bottom + (top - bottom) / (1 + (ec50/conc)^hill)\nlogistic4pl = lambda conc, bottom, top, ec50, hill: bottom + (top - bottom) / (1 + (ec50 / conc) ** hill)\n\nresponse_a = logistic4pl(concentrations, bottom_a, top_a, ec50_a, hill_a) + np.random.normal(0, 2, len(concentrations))\nresponse_b = logistic4pl(concentrations, bottom_b, top_b, ec50_b, hill_b) + np.random.normal(0, 2, len(concentrations))\nsem_a = np.random.uniform(2, 5, len(concentrations))\nsem_b = np.random.uniform(2, 5, len(concentrations))\n\ndf = pd.concat(\n    [\n        pd.DataFrame({\"concentration\": concentrations, \"response\": response_a, \"sem\": sem_a, \"compound\": \"Imatinib\"}),\n        pd.DataFrame({\"concentration\": concentrations, \"response\": response_b, \"sem\": sem_b, \"compound\": \"Erlotinib\"}),\n    ],\n    ignore_index=True,\n)\n\n# Fit 4PL models via scipy curve_fit\nfit_params = {}\nfit_cov = {}\nfor compound, p0 in [(\"Imatinib\", [5, 95, 3e-7, 1.2]), (\"Erlotinib\", [10, 80, 5e-6, 0.9])]:\n    mask = df[\"compound\"] == compound\n    popt, pcov = curve_fit(\n        logistic4pl, df.loc[mask, \"concentration\"].values, df.loc[mask, \"response\"].values, p0=p0, maxfev=10000\n    )\n    fit_params[compound] = popt\n    fit_cov[compound] = pcov\n\nx_fit = np.logspace(-9.5, -3.5, 300)\n\n# Parametric bootstrap 95% CI for Imatinib\nn_boot = 300\nparam_samples = np.random.multivariate_normal(fit_params[\"Imatinib\"], fit_cov[\"Imatinib\"], size=n_boot)\nboot_curves = np.array([logistic4pl(x_fit, *s) for s in param_samples])\nci_lo = np.percentile(boot_curves, 2.5, axis=0)\nci_hi = np.percentile(boot_curves, 97.5, axis=0)\n\n# Long-format DataFrame for fitted curves (enables sns.lineplot hue+style mapping)\ndf_fit = pd.concat(\n    [\n        pd.DataFrame(\n            {\"concentration\": x_fit, \"response\": logistic4pl(x_fit, *fit_params[\"Imatinib\"]), \"compound\": \"Imatinib\"}\n        ),\n        pd.DataFrame(\n            {\"concentration\": x_fit, \"response\": logistic4pl(x_fit, *fit_params[\"Erlotinib\"]), \"compound\": \"Erlotinib\"}\n        ),\n    ],\n    ignore_index=True,\n)\n\n# Plot — landscape 3200×1800 px (figsize=(8, 4.5) × dpi=400, no bbox_inches='tight')\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# CI band — Imatinib only (spec: at least one fitted curve)\nax.fill_between(x_fit, ci_lo, ci_hi, color=COLOR_A, alpha=0.15, zorder=1)\n\n# Fitted curves via sns.lineplot (idiomatic seaborn: hue+style over long-format DataFrame)\nsns.lineplot(\n    data=df_fit,\n    x=\"concentration\",\n    y=\"response\",\n    hue=\"compound\",\n    hue_order=[\"Imatinib\", \"Erlotinib\"],\n    style=\"compound\",\n    dashes=False,\n    palette=palette_dict,\n    linewidth=2.5,\n    zorder=4,\n    ax=ax,\n    legend=False,\n)\n\n# Data points (seaborn scatterplot for idiomatic hue+style mapping)\nsns.scatterplot(\n    data=df,\n    x=\"concentration\",\n    y=\"response\",\n    hue=\"compound\",\n    style=\"compound\",\n    markers={\"Imatinib\": \"o\", \"Erlotinib\": \"s\"},\n    palette=palette_dict,\n    s=120,\n    edgecolor=PAGE_BG,\n    linewidth=0.8,\n    zorder=5,\n    ax=ax,\n    legend=False,\n)\n\n# Error bars (seaborn scatterplot doesn't support yerr natively)\nfor compound in [\"Imatinib\", \"Erlotinib\"]:\n    sub = df[df[\"compound\"] == compound]\n    ax.errorbar(\n        sub[\"concentration\"],\n        sub[\"response\"],\n        yerr=sub[\"sem\"],\n        fmt=\"none\",\n        ecolor=palette_dict[compound],\n        capsize=4,\n        capthick=1.5,\n        elinewidth=1.5,\n        alpha=0.7,\n        zorder=4,\n    )\n\n# EC50 dashed reference lines\nfor compound in [\"Imatinib\", \"Erlotinib\"]:\n    popt = fit_params[compound]\n    ec50_val = popt[2]\n    half_resp = popt[0] + (popt[1] - popt[0]) / 2\n    ax.hlines(\n        half_resp,\n        x_fit[0],\n        ec50_val,\n        colors=palette_dict[compound],\n        linestyles=\"dashed\",\n        linewidth=1.2,\n        alpha=0.55,\n        zorder=3,\n    )\n    ax.vlines(\n        ec50_val, -5, half_resp, colors=palette_dict[compound], linestyles=\"dashed\", linewidth=1.2, alpha=0.55, zorder=3\n    )\n\n# EC50 value annotations near crosshair intersections\nfor compound in [\"Imatinib\", \"Erlotinib\"]:\n    popt = fit_params[compound]\n    ec50_val = popt[2]\n    half_resp = popt[0] + (popt[1] - popt[0]) / 2\n    if ec50_val < 1e-6:\n        ec50_label = f\"EC50 = {ec50_val * 1e9:.0f} nM\"\n    else:\n        ec50_label = f\"EC50 = {ec50_val * 1e6:.1f} μM\"\n    ax.text(\n        ec50_val * 2.5, half_resp + 4, ec50_label, fontsize=7.5, color=palette_dict[compound], va=\"bottom\", ha=\"left\"\n    )\n\n# Asymptote reference lines (top and bottom plateaus)\nfor compound, dash in [(\"Imatinib\", (5, 5)), (\"Erlotinib\", (2, 4))]:\n    popt = fit_params[compound]\n    ax.axhline(popt[0], color=palette_dict[compound], linestyle=(0, dash), linewidth=0.8, alpha=0.35, zorder=1)\n    ax.axhline(popt[1], color=palette_dict[compound], linestyle=(0, dash), linewidth=0.8, alpha=0.35, zorder=1)\n\n# Style\nax.set_xscale(\"log\")\nax.set_xlabel(\"Concentration (M)\", fontsize=10, color=INK)\nax.set_ylabel(\"Response (%)\", fontsize=10, color=INK)\nax.set_title(\"curve-dose-response · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nsns.despine(ax=ax)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8)\nax.set_ylim(-5, 110)\n\n# Custom legend (explicit handles for line + marker + CI patch)\nlegend_handles = [\n    Line2D([0], [0], color=COLOR_A, linewidth=2.5, label=\"Imatinib (fit)\"),\n    Line2D([0], [0], color=COLOR_B, linewidth=2.5, label=\"Erlotinib (fit)\"),\n    Line2D(\n        [0],\n        [0],\n        marker=\"o\",\n        color=\"none\",\n        markerfacecolor=COLOR_A,\n        markersize=8,\n        markeredgecolor=PAGE_BG,\n        label=\"Imatinib (data)\",\n    ),\n    Line2D(\n        [0],\n        [0],\n        marker=\"s\",\n        color=\"none\",\n        markerfacecolor=COLOR_B,\n        markersize=8,\n        markeredgecolor=PAGE_BG,\n        label=\"Erlotinib (data)\",\n    ),\n    Patch(facecolor=COLOR_A, alpha=0.25, label=\"95% CI (Imatinib)\"),\n]\nax.legend(handles=legend_handles, fontsize=8, frameon=True, loc=\"upper left\", facecolor=ELEVATED_BG, edgecolor=INK_SOFT)\n\n# Save — bbox_inches omitted (must stay default None per seaborn canvas contract)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}