{"spec_id":"residual-plot","library":"makie","language":"julia","code":"# anyplot.ai\n# residual-plot: Residual Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 94/100 | Created: 2026-09-05\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst MUTED    = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data ---------------------------------------------------------------\n# Advertising spend ($1000s) predicting weekly revenue ($1000s). The true\n# relationship is linear but noise variance grows with spend, so a simple\n# OLS fit leaves heteroscedastic residuals — exactly what this plot is\n# meant to expose.\nn = 300\nad_spend = sort(rand(n) .* 190 .+ 10)\nnoise_scale = 1.0 .+ 0.06 .* ad_spend\nrevenue = 50 .+ 4.5 .* ad_spend .+ randn(n) .* noise_scale .* 3.5\n\nspend_mean = mean(ad_spend)\nrevenue_mean = mean(revenue)\nslope = sum((ad_spend .- spend_mean) .* (revenue .- revenue_mean)) / sum((ad_spend .- spend_mean) .^ 2)\nintercept = revenue_mean - slope * spend_mean\nfitted_revenue = intercept .+ slope .* ad_spend\nresiduals = revenue .- fitted_revenue\n\nresid_std = std(residuals)\nis_outlier = abs.(residuals) .> 2 * resid_std\n\n# Quantify the heteroscedasticity story: local spread in the narrow (low\n# fitted-revenue) and wide (high fitted-revenue) quartiles, called out\n# directly on the plot instead of leaving the funnel shape to speak for itself.\nlo_cut = quantile(fitted_revenue, 0.25)\nhi_cut = quantile(fitted_revenue, 0.75)\nlo_mask = fitted_revenue .< lo_cut\nhi_mask = fitted_revenue .> hi_cut\nlo_sigma = std(residuals[lo_mask])\nhi_sigma = std(residuals[hi_mask])\nlo_anchor_x = quantile(fitted_revenue[lo_mask], 0.5)\nhi_anchor_x = quantile(fitted_revenue[hi_mask], 0.5)\nlo_anchor_y = maximum(residuals[lo_mask])\nhi_anchor_y = maximum(residuals[hi_mask])\n\nidx_max = argmax(residuals)\nidx_min = argmin(residuals)\n\n# --- Plot -----------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title              = \"residual-plot · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    subtitle           = \"Residual spread widens with fitted revenue — a classic heteroscedasticity signature\",\n    subtitlesize       = 13,\n    subtitlecolor      = INK_SOFT,\n    xlabel             = \"Fitted Revenue (\\$1000s)\",\n    ylabel             = \"Residual (\\$1000s)\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelsize     = 12,\n    yticklabelsize     = 12,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n)\n\nhspan!(ax, -2 * resid_std, 2 * resid_std; color = RGBAf(MUTED.r, MUTED.g, MUTED.b, 0.10))\nhlines!(ax, [-2resid_std, 2resid_std]; color = MUTED, linewidth = 1.5, linestyle = :dot)\nhlines!(ax, [0]; color = INK_SOFT, linewidth = 2.5, linestyle = :dash)\n\nscatter!(\n    ax, fitted_revenue[.!is_outlier], residuals[.!is_outlier];\n    color = IMPRINT_PALETTE[1], markersize = 11, alpha = 0.65, strokewidth = 0,\n    label = \"Residual\",\n)\nscatter!(\n    ax, fitted_revenue[is_outlier], residuals[is_outlier];\n    color = IMPRINT_PALETTE[5], markersize = 14, strokewidth = 1, strokecolor = PAGE_BG,\n    label = \"Outlier (|residual| > 2σ)\",\n)\n\n# Quantified spread callouts anchored directly in data coordinates — makes the\n# heteroscedasticity insight explicit rather than leaving the funnel to speak\n# for itself.\ntext!(\n    ax, lo_anchor_x, lo_anchor_y;\n    text = \"SD ≈ $(round(lo_sigma, digits = 1))\", color = INK, fontsize = 13,\n    font = :bold, align = (:center, :bottom), offset = (0, 10),\n)\ntext!(\n    ax, hi_anchor_x, hi_anchor_y;\n    text = \"SD ≈ $(round(hi_sigma, digits = 1))\", color = INK, fontsize = 13,\n    font = :bold, align = (:center, :bottom), offset = (0, 10),\n)\n\n# Label the two most extreme outliers to give the outlier cluster a\n# deliberate focal point instead of leaving it as undifferentiated dots.\ntext!(\n    ax, fitted_revenue[idx_max], residuals[idx_max];\n    text = \"largest overshoot: +$(round(Int, residuals[idx_max]))\",\n    color = IMPRINT_PALETTE[5], fontsize = 12, font = :bold,\n    align = (:left, :bottom), offset = (12, 8),\n)\ntext!(\n    ax, fitted_revenue[idx_min], residuals[idx_min];\n    text = \"largest undershoot: $(round(Int, residuals[idx_min]))\",\n    color = IMPRINT_PALETTE[5], fontsize = 12, font = :bold,\n    align = (:left, :top), offset = (12, -8),\n)\n\naxislegend(\n    ax;\n    position = :lb,\n    labelcolor = INK_SOFT,\n    labelsize = 12,\n    framevisible = false,\n    backgroundcolor = :transparent,\n)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}