{"spec_id":"scatter-regression-polynomial","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nscatter-regression-polynomial: Scatter Plot with Polynomial Regression\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\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\"\nRULE = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\nBRAND = \"#009E73\"  # Imprint palette position 1 - always first series\nACCENT = \"#C475FD\"  # Imprint palette position 2 - regression line\n\n# Data - Simulating diminishing returns pattern (economics example)\nnp.random.seed(42)\nn_points = 80\n\n# Advertising spend (in thousands)\nx = np.linspace(5, 50, n_points)\n# Sales revenue with diminishing returns (quadratic relationship)\n# y = -0.05x² + 4x + 20 + noise\ny = -0.05 * x**2 + 4 * x + 20 + np.random.normal(0, 5, n_points)\n\n# Fit polynomial regression (degree 2 - quadratic) using numpy\npoly_degree = 2\ncoefficients = np.polyfit(x, y, poly_degree)\npoly_func = np.poly1d(coefficients)\ny_pred = poly_func(x)\n\n# Calculate R²\nss_res = np.sum((y - y_pred) ** 2)\nss_tot = np.sum((y - np.mean(y)) ** 2)\nr2 = 1 - (ss_res / ss_tot)\n\n# Generate smooth curve for regression line\nx_smooth = np.linspace(x.min(), x.max(), 200)\ny_smooth = poly_func(x_smooth)\n\n# Generate confidence band (approximate using residual standard error)\nresiduals = y - y_pred\nstd_error = np.std(residuals)\ny_upper = y_smooth + 1.96 * std_error\ny_lower = y_smooth - 1.96 * std_error\n\n# Create dataframes\ndf_points = pd.DataFrame({\"x\": x, \"y\": y})\ndf_curve = pd.DataFrame({\"x\": x_smooth, \"y\": y_smooth, \"y_upper\": y_upper, \"y_lower\": y_lower})\n\n# Get polynomial equation\na, b, c = coefficients\nequation = f\"y = {a:.3f}x² + {b:.3f}x + {c:.2f}\"\n\n# Create plot\nplot = (\n    ggplot()\n    # Confidence band with no border\n    + geom_ribbon(\n        aes(x=\"x\", ymin=\"y_lower\", ymax=\"y_upper\"),\n        data=df_curve,\n        fill=BRAND,\n        alpha=0.15,\n        color=None,\n        tooltips=layer_tooltips().line(\"95% band|@y_lower – @y_upper\"),\n    )\n    # Scatter points\n    + geom_point(\n        aes(x=\"x\", y=\"y\"),\n        data=df_points,\n        color=BRAND,\n        size=2.5,\n        alpha=0.65,\n        tooltips=layer_tooltips().line(\"Advertising spend|$@x k\").line(\"Sales revenue|$@y k\"),\n    )\n    # Polynomial regression line\n    + geom_line(\n        aes(x=\"x\", y=\"y\"), data=df_curve, color=ACCENT, size=1.5, tooltips=layer_tooltips().line(\"Fitted revenue|$@y k\")\n    )\n    # Labels and title\n    + labs(\n        x=\"Advertising Spend (thousands $)\",\n        y=\"Sales Revenue (thousands $)\",\n        title=\"scatter-regression-polynomial · python · letsplot · anyplot.ai\",\n    )\n    # Annotations for R² and equation - placed top-left, away from the point\n    # cloud (which only rises above y=95 for x > 27) and shielded with a\n    # background label box so any future data draw can't overlap the text\n    + geom_label(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=pd.DataFrame({\"x\": [x.min() + 1], \"y\": [y.max()], \"label\": [f\"R² = {r2:.3f}\"]}),\n        size=5,\n        color=INK,\n        fill=PAGE_BG,\n        label_size=0,\n        alpha=0.9,\n        hjust=0,\n    )\n    + geom_label(\n        aes(x=\"x\", y=\"y\", label=\"label\"),\n        data=pd.DataFrame({\"x\": [x.min() + 1], \"y\": [y.max() - 6], \"label\": [equation]}),\n        size=4,\n        color=INK_SOFT,\n        fill=PAGE_BG,\n        label_size=0,\n        alpha=0.9,\n        hjust=0,\n    )\n    # Theme\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_border=element_blank(),  # drop box border - L-shaped frame from axis_line only\n        panel_grid_major=element_line(color=RULE, size=0.3),\n        panel_grid_minor=element_blank(),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT),\n    )\n    + ggsize(800, 450)\n)\n\n# Save as PNG (scale 4x for 3200x1800)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\n\n# Save as HTML for interactive viewing\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}