{"spec_id":"bland-altman-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nbland-altman-basic: Bland-Altman Agreement Plot\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove current directory from path to avoid importing this file as 'seaborn'\nsys.path = [p for p in sys.path if os.path.abspath(p) != os.getcwd()]\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\n\n\n# Theme tokens\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\n# Imprint palette (categorical) — first series always #009E73\nIMPRINT_PALETTE = sns.color_palette([\"#009E73\", \"#C475FD\"])\nBRAND = IMPRINT_PALETTE[0]  # scatter + mean line\nACCENT_1 = IMPRINT_PALETTE[1]  # limits of agreement\nNEUTRAL = INK  # semantic anchor: baseline / reference line, same hex as text\n\n# Theme-adaptive chrome, seaborn-native (see prompts/library/seaborn.md)\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 - Simulated blood pressure measurements from two sphygmomanometers\nnp.random.seed(42)\nn = 80\n\n# Method 1: Reference standard (e.g., mercury sphygmomanometer)\nmethod1 = np.random.normal(120, 15, n)\n\n# Method 2: New device with slight systematic bias and proportional error\nmethod2 = method1 + np.random.normal(2, 5, n) + 0.02 * (method1 - 120)\n\n# Calculate Bland-Altman statistics\nmean_values = (method1 + method2) / 2\ndifferences = method1 - method2\nmean_diff = np.mean(differences)\nstd_diff = np.std(differences, ddof=1)\nupper_loa = mean_diff + 1.96 * std_diff\nlower_loa = mean_diff - 1.96 * std_diff\n\n# Create plot — canonical landscape canvas (3200x1800 @ dpi=400)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\n\n# Scatter plot of differences vs means\nsns.scatterplot(x=mean_values, y=differences, s=100, alpha=0.7, color=BRAND, edgecolor=PAGE_BG, linewidth=0.5, ax=ax)\n\n# Rug of the raw differences on the y-axis — a seaborn-native way to surface\n# the shape of the difference distribution, which the ±1.96 SD limits assume\n# is approximately normal.\nsns.rugplot(y=differences, ax=ax, height=0.03, color=BRAND, alpha=0.5, lw=1)\n\n# Shaded band between the limits of agreement — reinforces the ±1.96 SD\n# envelope as a single focal region before the individual lines are read.\nax.axhspan(lower_loa, upper_loa, color=ACCENT_1, alpha=0.08, zorder=0)\n\n# Mean difference line (bias)\nax.axhline(y=mean_diff, color=BRAND, linewidth=2.5, label=f\"Mean: {mean_diff:.2f} mmHg\")\n\n# Limits of agreement (±1.96 SD)\nax.axhline(y=upper_loa, color=ACCENT_1, linewidth=1.75, linestyle=\"--\", label=f\"+1.96 SD: {upper_loa:.2f} mmHg\")\nax.axhline(y=lower_loa, color=ACCENT_1, linewidth=1.75, linestyle=\"--\", label=f\"-1.96 SD: {lower_loa:.2f} mmHg\")\n\n# Zero reference line\nax.axhline(y=0, color=NEUTRAL, linewidth=1, linestyle=\":\", alpha=0.5)\n\n# Annotate values on the right side\nx_max = ax.get_xlim()[1]\nax.annotate(\n    f\"{mean_diff:.1f}\",\n    xy=(x_max, mean_diff),\n    xytext=(5, 0),\n    textcoords=\"offset points\",\n    fontsize=9,\n    color=BRAND,\n    fontweight=\"bold\",\n    va=\"center\",\n)\nax.annotate(\n    f\"{upper_loa:.1f}\",\n    xy=(x_max, upper_loa),\n    xytext=(5, 0),\n    textcoords=\"offset points\",\n    fontsize=8,\n    color=ACCENT_1,\n    va=\"center\",\n)\nax.annotate(\n    f\"{lower_loa:.1f}\",\n    xy=(x_max, lower_loa),\n    xytext=(5, 0),\n    textcoords=\"offset points\",\n    fontsize=8,\n    color=ACCENT_1,\n    va=\"center\",\n)\n\n# Labels and styling\nax.set_xlabel(\"Mean of Two Methods (mmHg)\", fontsize=10)\nax.set_ylabel(\"Difference (Method 1 - Method 2) (mmHg)\", fontsize=10)\nax.set_title(\"bland-altman-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\")\nax.tick_params(axis=\"both\", labelsize=8)\n\n# Spines — seaborn-native despine (default: top + right removed)\nsns.despine(ax=ax)\n\n# Grid - subtle, both axes (scatter plot)\nax.grid(True, axis=\"both\", alpha=0.15, linewidth=0.8, color=INK)\n\n# Legend, positioned and styled via seaborn's move_legend — lower-right,\n# clear of both the y-axis rug ticks and the right-edge value annotations\nax.legend(fontsize=8)\nsns.move_legend(ax, \"lower right\", frameon=True, facecolor=ELEVATED_BG, edgecolor=INK_SOFT, framealpha=1.0)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}