{"spec_id":"timeseries-forecast-uncertainty","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ntimeseries-forecast-uncertainty: Time Series Forecast with Uncertainty Band\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-19\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\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# Okabe-Ito palette\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\nCOLOR_HISTORICAL = IMPRINT[0]\nCOLOR_FORECAST = IMPRINT[1]\n\n# Higher alpha in dark mode — orange over near-black otherwise looks brownish\nALPHA_95 = 0.30 if THEME == \"dark\" else 0.22\nALPHA_80 = 0.42 if THEME == \"dark\" else 0.30\n\nnp.random.seed(42)\n\n# Data — stock price with ~3-month history and 4-week forecast\nn_historical = 60\nn_forecast = 20\ndates = pd.date_range(start=\"2025-01-01\", periods=n_historical + n_forecast, freq=\"B\")\n\nt = np.arange(n_historical)\nhistorical_prices = 150 + 0.15 * t + 2.5 * np.sin(2 * np.pi * t / 20) + np.random.normal(0, 1.5, n_historical)\n\nt_fc = np.arange(n_historical, n_historical + n_forecast)\nforecast_prices = 150 + 0.15 * t_fc + 2.5 * np.sin(2 * np.pi * t_fc / 20)\n\nhorizon = np.arange(1, n_forecast + 1)\nstd_growth = 1.5 * np.sqrt(horizon)\nlower_95 = forecast_prices - 1.96 * std_growth\nupper_95 = forecast_prices + 1.96 * std_growth\nlower_80 = forecast_prices - 1.28 * std_growth\nupper_80 = forecast_prices + 1.28 * std_growth\n\n# Wide-form for CI bands; long-form for seaborn's data-aware lineplot\ndf_wide = pd.DataFrame(\n    {\n        \"date\": dates,\n        \"actual\": list(historical_prices) + [np.nan] * n_forecast,\n        \"forecast\": [np.nan] * (n_historical - 1) + [historical_prices[-1]] + list(forecast_prices),\n        \"lower_80\": [np.nan] * (n_historical - 1) + [historical_prices[-1]] + list(lower_80),\n        \"upper_80\": [np.nan] * (n_historical - 1) + [historical_prices[-1]] + list(upper_80),\n        \"lower_95\": [np.nan] * (n_historical - 1) + [historical_prices[-1]] + list(lower_95),\n        \"upper_95\": [np.nan] * (n_historical - 1) + [historical_prices[-1]] + list(upper_95),\n    }\n)\n\nlong_data = pd.concat(\n    [\n        df_wide[[\"date\", \"actual\"]].rename(columns={\"actual\": \"price\"}).assign(series=\"Historical\"),\n        df_wide[[\"date\", \"forecast\"]].rename(columns={\"forecast\": \"price\"}).assign(series=\"Forecast\"),\n    ]\n).dropna()\n\n# Configure seaborn theme\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.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Subtle forecast-region shading to visually separate forecast from history\nax.axvspan(dates[n_historical - 1], dates[-1], alpha=0.04, color=INK, zorder=0)\n\n# Confidence interval bands (95% outermost/lightest, 80% inner/more opaque — nested)\nax.fill_between(df_wide[\"date\"], df_wide[\"lower_95\"], df_wide[\"upper_95\"], alpha=ALPHA_95, color=COLOR_FORECAST)\nax.fill_between(df_wide[\"date\"], df_wide[\"lower_80\"], df_wide[\"upper_80\"], alpha=ALPHA_80, color=COLOR_FORECAST)\n\n# Seaborn lineplot — idiomatic long-form API with hue + style + dashes\nsns.lineplot(\n    data=long_data,\n    x=\"date\",\n    y=\"price\",\n    hue=\"series\",\n    style=\"series\",\n    palette={\"Historical\": COLOR_HISTORICAL, \"Forecast\": COLOR_FORECAST},\n    dashes={\"Historical\": (1, 0), \"Forecast\": (6, 2)},\n    linewidth=3,\n    ax=ax,\n    legend=False,\n)\n\n# Forecast boundary marker\nax.axvline(x=dates[n_historical - 1], color=INK_SOFT, linestyle=\":\", linewidth=1.5, alpha=0.5)\nax.text(\n    dates[n_historical - 1],\n    0.97,\n    \"  Forecast →\",\n    transform=ax.get_xaxis_transform(),\n    color=INK_SOFT,\n    fontsize=8,\n    va=\"top\",\n)\n\n# Style — title at 11pt, axes at 10pt for clear typographic hierarchy\nax.set_title(\n    \"timeseries-forecast-uncertainty · python · seaborn · anyplot.ai\",\n    fontsize=11,\n    fontweight=\"medium\",\n    color=INK,\n    pad=8,\n)\nax.set_xlabel(\"Date\", fontsize=10, color=INK)\nax.set_ylabel(\"Stock Price ($)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nsns.despine(ax=ax)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\nax.set_axisbelow(True)\n\n# Combined legend: line handles + CI band patches\nlegend_elements = [\n    Line2D([0], [0], color=COLOR_HISTORICAL, linewidth=3, label=\"Historical\"),\n    Line2D([0], [0], color=COLOR_FORECAST, linewidth=3, linestyle=(0, (6, 2)), label=\"Forecast\"),\n    Patch(facecolor=COLOR_FORECAST, alpha=ALPHA_80, label=\"80% Confidence\"),\n    Patch(facecolor=COLOR_FORECAST, alpha=ALPHA_95, label=\"95% Confidence\"),\n]\nax.legend(handles=legend_elements, fontsize=8, loc=\"upper left\", framealpha=1.0, fancybox=False, edgecolor=INK_SOFT)\n\nplt.tight_layout()\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}