{"spec_id":"survival-kaplan-meier","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nsurvival-kaplan-meier: Kaplan-Meier Survival Plot\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 72/100 | Updated: 2026-05-11\n\"\"\"\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Data - Clinical trial survival data for two treatment groups\nnp.random.seed(42)\n\n# Generate realistic survival times (exponential-like distribution)\nn_per_group = 80\n\n# Treatment group (better survival)\ntreatment_times = np.random.exponential(scale=24, size=n_per_group)\ntreatment_times = np.clip(treatment_times, 0.5, 60)  # Cap at 60 months\ntreatment_censored = np.random.binomial(1, 0.25, n_per_group)  # 25% censored\ntreatment_events = 1 - treatment_censored\n\n# Control group (worse survival)\ncontrol_times = np.random.exponential(scale=16, size=n_per_group)\ncontrol_times = np.clip(control_times, 0.5, 60)\ncontrol_censored = np.random.binomial(1, 0.20, n_per_group)  # 20% censored\ncontrol_events = 1 - control_censored\n\n# Kaplan-Meier calculation for Treatment group\norder = np.argsort(treatment_times)\ntreat_t_sorted = treatment_times[order]\ntreat_e_sorted = treatment_events[order]\ntreat_unique = np.unique(treat_t_sorted[treat_e_sorted == 1])\n\ntreat_time_pts = [0.0]\ntreat_surv_probs = [1.0]\ntreat_std_errs = [0.0]\ntreat_surv = 1.0\ntreat_var = 0.0\n\nfor t in treat_unique:\n    n_risk = np.sum(treat_t_sorted >= t)\n    d = np.sum((treat_t_sorted == t) & (treat_e_sorted == 1))\n    if n_risk > 0 and d > 0:\n        treat_surv *= (n_risk - d) / n_risk\n        if n_risk > d:\n            treat_var += d / (n_risk * (n_risk - d))\n    treat_time_pts.append(t)\n    treat_surv_probs.append(treat_surv)\n    treat_std_errs.append(np.sqrt(treat_var) * treat_surv if treat_surv > 0 else 0)\n\ntreat_times_km = np.array(treat_time_pts)\ntreat_surv_km = np.array(treat_surv_probs)\ntreat_se_km = np.array(treat_std_errs)\n\n# Kaplan-Meier calculation for Control group\norder = np.argsort(control_times)\nctrl_t_sorted = control_times[order]\nctrl_e_sorted = control_events[order]\nctrl_unique = np.unique(ctrl_t_sorted[ctrl_e_sorted == 1])\n\nctrl_time_pts = [0.0]\nctrl_surv_probs = [1.0]\nctrl_std_errs = [0.0]\nctrl_surv = 1.0\nctrl_var = 0.0\n\nfor t in ctrl_unique:\n    n_risk = np.sum(ctrl_t_sorted >= t)\n    d = np.sum((ctrl_t_sorted == t) & (ctrl_e_sorted == 1))\n    if n_risk > 0 and d > 0:\n        ctrl_surv *= (n_risk - d) / n_risk\n        if n_risk > d:\n            ctrl_var += d / (n_risk * (n_risk - d))\n    ctrl_time_pts.append(t)\n    ctrl_surv_probs.append(ctrl_surv)\n    ctrl_std_errs.append(np.sqrt(ctrl_var) * ctrl_surv if ctrl_surv > 0 else 0)\n\nctrl_times_km = np.array(ctrl_time_pts)\nctrl_surv_km = np.array(ctrl_surv_probs)\nctrl_se_km = np.array(ctrl_std_errs)\n\n# Get censored observation times for tick marks\ntreat_censor_times = treatment_times[treatment_events == 0]\nctrl_censor_times = control_times[control_events == 0]\n\n# Interpolate survival at censored times for tick marks\ntreat_censor_surv = np.interp(treat_censor_times, treat_times_km, treat_surv_km)\nctrl_censor_surv = np.interp(ctrl_censor_times, ctrl_times_km, ctrl_surv_km)\n\n# Plot\nfig, ax = plt.subplots(figsize=(16, 9))\n\n# Python colors\ntreatment_color = \"#306998\"\ncontrol_color = \"#FFD43B\"\n\n# Treatment group curve with CI\nax.step(treat_times_km, treat_surv_km, where=\"post\", color=treatment_color, linewidth=3, label=\"Treatment Group\")\ntreat_upper = np.clip(treat_surv_km + 1.96 * treat_se_km, 0, 1)\ntreat_lower = np.clip(treat_surv_km - 1.96 * treat_se_km, 0, 1)\nax.fill_between(treat_times_km, treat_lower, treat_upper, step=\"post\", alpha=0.2, color=treatment_color)\n\n# Control group curve with CI\nax.step(ctrl_times_km, ctrl_surv_km, where=\"post\", color=control_color, linewidth=3, label=\"Control Group\")\nctrl_upper = np.clip(ctrl_surv_km + 1.96 * ctrl_se_km, 0, 1)\nctrl_lower = np.clip(ctrl_surv_km - 1.96 * ctrl_se_km, 0, 1)\nax.fill_between(ctrl_times_km, ctrl_lower, ctrl_upper, step=\"post\", alpha=0.3, color=control_color)\n\n# Censored observation tick marks\nax.scatter(treat_censor_times, treat_censor_surv, marker=\"|\", s=400, color=treatment_color, linewidth=2, zorder=5)\nax.scatter(ctrl_censor_times, ctrl_censor_surv, marker=\"|\", s=400, color=\"#CC9A00\", linewidth=2, zorder=5)\n\n# Calculate median survival times\ntreat_median_idx = np.where(treat_surv_km <= 0.5)[0]\nctrl_median_idx = np.where(ctrl_surv_km <= 0.5)[0]\n\ntreat_median = treat_times_km[treat_median_idx[0]] if len(treat_median_idx) > 0 else None\nctrl_median = ctrl_times_km[ctrl_median_idx[0]] if len(ctrl_median_idx) > 0 else None\n\n# Add median survival annotation lines\nif treat_median:\n    ax.axhline(y=0.5, color=\"gray\", linestyle=\":\", linewidth=1.5, alpha=0.5)\n    ax.axvline(x=treat_median, color=treatment_color, linestyle=\":\", linewidth=1.5, alpha=0.7)\nif ctrl_median:\n    ax.axvline(x=ctrl_median, color=\"#CC9A00\", linestyle=\":\", linewidth=1.5, alpha=0.7)\n\n# Add median text\nmedian_text = \"\"\nif treat_median:\n    median_text += f\"Treatment median: {treat_median:.1f} mo\"\nif ctrl_median:\n    median_text += f\"\\nControl median: {ctrl_median:.1f} mo\"\n\nax.text(\n    0.98,\n    0.02,\n    median_text.strip(),\n    transform=ax.transAxes,\n    fontsize=16,\n    verticalalignment=\"bottom\",\n    horizontalalignment=\"right\",\n    bbox={\"boxstyle\": \"round,pad=0.5\", \"facecolor\": \"white\", \"edgecolor\": \"gray\", \"alpha\": 0.8},\n)\n\n# Styling\nax.set_xlabel(\"Time (months)\", fontsize=20)\nax.set_ylabel(\"Survival Probability\", fontsize=20)\nax.set_title(\"survival-kaplan-meier · matplotlib · pyplots.ai\", fontsize=24)\nax.tick_params(axis=\"both\", labelsize=16)\nax.set_xlim(0, 65)\nax.set_ylim(0, 1.05)\nax.legend(fontsize=16, loc=\"upper right\")\nax.grid(True, alpha=0.3, linestyle=\"--\")\n\nplt.tight_layout()\nplt.savefig(\"plot.png\", dpi=300, bbox_inches=\"tight\")\n"}