{"spec_id":"survival-kaplan-meier","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nsurvival-kaplan-meier: Kaplan-Meier Survival Plot\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 72/100 | Updated: 2026-05-11\n\"\"\"\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Kaplan-Meier estimator function\ndef kaplan_meier(time, event):\n    \"\"\"Calculate Kaplan-Meier survival estimates with confidence intervals.\"\"\"\n    df = pd.DataFrame({\"time\": time, \"event\": event}).sort_values(\"time\")\n\n    # Get unique event times (only where events occurred)\n    event_times = df[df[\"event\"] == 1][\"time\"].unique()\n    event_times = np.sort(event_times)\n\n    survival = []\n    ci_lower = []\n    ci_upper = []\n    times = [0]\n    survival.append(1.0)\n    ci_lower.append(1.0)\n    ci_upper.append(1.0)\n\n    s = 1.0\n    var_sum = 0.0\n\n    for t in event_times:\n        n_at_risk = len(df[df[\"time\"] >= t])\n        n_events = len(df[(df[\"time\"] == t) & (df[\"event\"] == 1)])\n\n        if n_at_risk > 0:\n            s *= 1 - n_events / n_at_risk\n            if n_at_risk > n_events:\n                var_sum += n_events / (n_at_risk * (n_at_risk - n_events))\n\n        times.append(t)\n        survival.append(s)\n\n        # Greenwood's formula for confidence intervals\n        if s > 0 and var_sum > 0:\n            se = s * np.sqrt(var_sum)\n            ci_lower.append(max(0, s - 1.96 * se))\n            ci_upper.append(min(1, s + 1.96 * se))\n        else:\n            ci_lower.append(s)\n            ci_upper.append(s)\n\n    return np.array(times), np.array(survival), np.array(ci_lower), np.array(ci_upper)\n\n\n# Generate synthetic clinical trial data\nnp.random.seed(42)\nn_patients = 150\n\n# Treatment group (better survival)\nn_treatment = 75\ntreatment_time = np.random.exponential(scale=24, size=n_treatment)\ntreatment_time = np.clip(treatment_time, 0, 36)  # Max follow-up 36 months\ntreatment_event = np.random.binomial(1, 0.6, size=n_treatment)\n# Censor some patients (still alive at end of study)\ntreatment_event[treatment_time >= 35] = 0\n\n# Control group (worse survival)\nn_control = 75\ncontrol_time = np.random.exponential(scale=16, size=n_control)\ncontrol_time = np.clip(control_time, 0, 36)\ncontrol_event = np.random.binomial(1, 0.75, size=n_control)\ncontrol_event[control_time >= 35] = 0\n\n# Calculate Kaplan-Meier estimates for each group\ntreat_times, treat_surv, treat_ci_lo, treat_ci_hi = kaplan_meier(treatment_time, treatment_event)\nctrl_times, ctrl_surv, ctrl_ci_lo, ctrl_ci_hi = kaplan_meier(control_time, control_event)\n\n# Get censored observations for marking\ntreat_censor_times = treatment_time[treatment_event == 0]\nctrl_censor_times = control_time[control_event == 0]\n\n# Interpolate survival at censoring times for tick marks\ntreat_censor_surv = np.interp(treat_censor_times, treat_times, treat_surv)\nctrl_censor_surv = np.interp(ctrl_censor_times, ctrl_times, ctrl_surv)\n\n# Colors\ntreatment_color = \"#306998\"  # Python Blue\ncontrol_color = \"#FFD43B\"  # Python Yellow\n\n# Create plot\nsns.set_style(\"whitegrid\")\nfig, ax = plt.subplots(figsize=(16, 9))\n\n# Plot treatment group survival curve (step function)\nax.step(treat_times, treat_surv, where=\"post\", color=treatment_color, linewidth=3, label=\"Treatment\")\nax.fill_between(treat_times, treat_ci_lo, treat_ci_hi, step=\"post\", alpha=0.2, color=treatment_color, linewidth=0)\n\n# Plot control group survival curve (step function)\nax.step(ctrl_times, ctrl_surv, where=\"post\", color=control_color, linewidth=3, label=\"Control\")\nax.fill_between(ctrl_times, ctrl_ci_lo, ctrl_ci_hi, step=\"post\", alpha=0.3, color=control_color, linewidth=0)\n\n# Mark censored observations with tick marks\nax.scatter(treat_censor_times, treat_censor_surv, marker=\"|\", s=300, color=treatment_color, linewidths=2, zorder=5)\nax.scatter(ctrl_censor_times, ctrl_censor_surv, marker=\"|\", s=300, color=control_color, linewidths=2, zorder=5)\n\n# Calculate median survival times\ntreat_median_idx = np.where(treat_surv <= 0.5)[0]\ntreat_median = treat_times[treat_median_idx[0]] if len(treat_median_idx) > 0 else None\nctrl_median_idx = np.where(ctrl_surv <= 0.5)[0]\nctrl_median = ctrl_times[ctrl_median_idx[0]] if len(ctrl_median_idx) > 0 else None\n\n# Add median survival annotation\nif treat_median is not None:\n    ax.axhline(y=0.5, color=\"gray\", linestyle=\"--\", alpha=0.5, linewidth=1.5)\n    annotation_text = f\"Median survival:\\nTreatment: {treat_median:.1f} months\"\n    if ctrl_median is not None:\n        annotation_text += f\"\\nControl: {ctrl_median:.1f} months\"\n    ax.annotate(\n        annotation_text,\n        xy=(0.98, 0.55),\n        xycoords=\"axes fraction\",\n        fontsize=16,\n        ha=\"right\",\n        va=\"bottom\",\n        bbox={\"boxstyle\": \"round,pad=0.5\", \"facecolor\": \"white\", \"edgecolor\": \"gray\", \"alpha\": 0.9},\n    )\n\n# Labels and styling\nax.set_xlabel(\"Time (months)\", fontsize=20)\nax.set_ylabel(\"Survival Probability\", fontsize=20)\nax.set_title(\"survival-kaplan-meier · seaborn · pyplots.ai\", fontsize=24)\nax.tick_params(axis=\"both\", labelsize=16)\n\nax.set_xlim(0, 38)\nax.set_ylim(0, 1.05)\n\n# Legend\nax.legend(fontsize=18, loc=\"lower left\", framealpha=0.9)\n\n# Subtle grid\nax.grid(True, alpha=0.3, linestyle=\"--\")\n\nplt.tight_layout()\nplt.savefig(\"plot.png\", dpi=300, bbox_inches=\"tight\")\n"}