{"spec_id":"learning-curve-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nlearning-curve-basic: Model Learning Curve\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Remove the script directory from sys.path to avoid importing bokeh.py as bokeh\nscript_dir = Path(__file__).parent\nif str(script_dir) in sys.path:\n    sys.path.remove(str(script_dir))\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import Band, ColumnDataSource, HoverTool, Legend, LegendItem\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\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\nTRAIN_COLOR = \"#009E73\"  # Position 1: brand green\nVAL_COLOR = \"#C475FD\"  # Position 2: vermillion\n\n# Data - Simulate learning curve for a classification model\nnp.random.seed(42)\n\n# Training set sizes (10 points from 10% to 100% of data)\ntrain_sizes = np.array([50, 100, 200, 300, 400, 500, 600, 700, 800, 900])\n\n# Simulate 5-fold cross-validation scores\nn_folds = 5\nn_sizes = len(train_sizes)\n\n# Training scores: start high and stay high (slight decrease as model generalizes)\ntrain_scores_mean = 0.99 - 0.02 * np.log(train_sizes / train_sizes[0]) / np.log(train_sizes[-1] / train_sizes[0])\ntrain_scores_std = 0.01 + 0.01 * (1 - train_sizes / train_sizes[-1])\n\n# Validation scores: start low, improve with more data (typical learning curve shape)\nvalidation_scores_mean = 0.65 + 0.25 * (1 - np.exp(-train_sizes / 300))\nvalidation_scores_std = 0.08 * np.exp(-train_sizes / 400) + 0.01\n\n# Create bands for confidence intervals (±1 std)\ntrain_upper = train_scores_mean + train_scores_std\ntrain_lower = train_scores_mean - train_scores_std\nval_upper = validation_scores_mean + validation_scores_std\nval_lower = validation_scores_mean - validation_scores_std\n\n# Create ColumnDataSource for training data\ntrain_source = ColumnDataSource(\n    data={\"x\": train_sizes, \"y\": train_scores_mean, \"upper\": train_upper, \"lower\": train_lower}\n)\n\n# Create ColumnDataSource for validation data\nval_source = ColumnDataSource(\n    data={\"x\": train_sizes, \"y\": validation_scores_mean, \"upper\": val_upper, \"lower\": val_lower}\n)\n\n# Create figure (4800 x 2700 px for 16:9)\np = figure(\n    width=4800,\n    height=2700,\n    title=\"learning-curve-basic · bokeh · anyplot.ai\",\n    x_axis_label=\"Training Set Size (samples)\",\n    y_axis_label=\"Accuracy Score\",\n    tools=\"pan,wheel_zoom,box_zoom,reset,save\",\n)\n\n# Add confidence bands for training scores (Okabe-Ito: brand green)\ntrain_band = Band(\n    base=\"x\",\n    lower=\"lower\",\n    upper=\"upper\",\n    source=train_source,\n    fill_color=TRAIN_COLOR,\n    fill_alpha=0.2,\n    line_color=TRAIN_COLOR,\n    line_alpha=0.3,\n)\np.add_layout(train_band)\n\n# Add confidence bands for validation scores (Okabe-Ito: vermillion)\nval_band = Band(\n    base=\"x\",\n    lower=\"lower\",\n    upper=\"upper\",\n    source=val_source,\n    fill_color=VAL_COLOR,\n    fill_alpha=0.3,\n    line_color=VAL_COLOR,\n    line_alpha=0.4,\n)\np.add_layout(val_band)\n\n# Plot training score line\ntrain_line = p.line(x=\"x\", y=\"y\", source=train_source, line_color=TRAIN_COLOR, line_width=4, line_alpha=0.9)\n\n# Plot training score markers\ntrain_scatter = p.scatter(x=\"x\", y=\"y\", source=train_source, color=TRAIN_COLOR, size=22, alpha=0.9)\n\n# Plot validation score line\nval_line = p.line(x=\"x\", y=\"y\", source=val_source, line_color=VAL_COLOR, line_width=4, line_alpha=0.9)\n\n# Plot validation score markers\nval_scatter = p.scatter(x=\"x\", y=\"y\", source=val_source, color=VAL_COLOR, size=22, alpha=0.9)\n\n# Add hover tool for interactivity (Bokeh distinctive feature)\nhover_train = HoverTool(\n    renderers=[train_scatter],\n    tooltips=[\n        (\"Type\", \"Training Score\"),\n        (\"Samples\", \"@x{0}\"),\n        (\"Accuracy\", \"@y{0.000}\"),\n        (\"Std Range\", \"@lower{0.000} - @upper{0.000}\"),\n    ],\n    mode=\"mouse\",\n)\np.add_tools(hover_train)\n\nhover_val = HoverTool(\n    renderers=[val_scatter],\n    tooltips=[\n        (\"Type\", \"Validation Score\"),\n        (\"Samples\", \"@x{0}\"),\n        (\"Accuracy\", \"@y{0.000}\"),\n        (\"Std Range\", \"@lower{0.000} - @upper{0.000}\"),\n    ],\n    mode=\"mouse\",\n)\np.add_tools(hover_val)\n\n# Create legend - positioned inside plot area, top-left for better visibility\nlegend = Legend(\n    items=[\n        LegendItem(label=\"Training Score\", renderers=[train_line, train_scatter]),\n        LegendItem(label=\"Validation Score\", renderers=[val_line, val_scatter]),\n    ],\n    location=\"top_left\",\n)\n\np.add_layout(legend, \"center\")\n\n# Styling - increased sizes for better readability on 4800x2700 canvas\np.title.text_font_size = \"36pt\"\np.title.text_font_style = \"bold\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"28pt\"\np.yaxis.axis_label_text_font_size = \"28pt\"\np.xaxis.major_label_text_font_size = \"22pt\"\np.yaxis.major_label_text_font_size = \"22pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Legend styling - larger and more prominent\np.legend.label_text_font_size = \"34pt\"\np.legend.background_fill_alpha = 0.95\np.legend.background_fill_color = ELEVATED_BG\np.legend.border_line_color = INK_SOFT\np.legend.border_line_width = 2\np.legend.label_text_color = INK_SOFT\np.legend.padding = 25\np.legend.spacing = 20\np.legend.glyph_width = 50\np.legend.glyph_height = 40\n\n# Grid styling\np.grid.grid_line_alpha = 0.10\np.grid.grid_line_dash = \"dashed\"\np.grid.grid_line_color = INK\n\n# Axis range to show all data with padding\np.y_range.start = 0.55\np.y_range.end = 1.02\n\n# Theme-adaptive background and borders\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\n# Save as HTML for interactive version\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome for PNG (Selenium)\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}