Polynomial Regression in JavaScript

Characterizing several JavaScript polynomial-regression implementations against a NumPy gold standard, so you can trade off numerical stability, speed, and code simplicity for your context. Start with the recommendation below; expand the methodology when you want to audit how the evidence was produced.

For a dedicated speed study, see the fit-speed grid — how the two fastest scaled methods (simple (scaled) and forsythe) slow down as the data grows from 8 to 1 million points, across polynomial degrees 1–6 and three data shapes, in all three major JavaScript engines (V8, JavaScriptCore, SpiderMonkey).
Methodology, definitions, and important timing caveats No JS library does the fully robust thing (center and scale, or a QR/SVD solve), and the popular ones fail differently: ml.js solves a raw Vandermonde with normal equations — no centering, no scaling — so it loses accuracy for shifted or large x. d3-regression mean-centers x for degree ≥ 2 (so it tolerates large offsets there) but never scales, so it still degrades on wide-range x and high degree — and its degree-1 path doesn't center at all, so even a straight-line fit on offset x (epoch-ms timestamps ≈ 1.7e12) loses accuracy.

Accuracy = each implementation's in-range predictions vs NumPy (numpy.polynomial.Polynomial.fit, computed offline into numpy-reference.js), relative to the response range. Cell colour: <1e‑6 <1e‑3 worse. is the fit quality. Speed = each implementation timed relative to the fastest correct method on that case (which reads 1.00×) — so a bigger number means slower. Every method within 10% of it is marked ⚡ and counts as tied for fastest, since the exact winner among methods that close is just measurement noise. Timing note: a d3-regression fit also returns an adaptively-sampled polyline for drawing the curve, which for degree ≥ 2 can cost far more than the fit itself — most of all on well-scaled x, where its angle-based subdivision goes deepest. This page only uses predict and the coefficients, so both d3 rows pass .domain([0,0]) to skip that sampling; coefficients and predictions are unchanged by it.

Each card plots the data with every implementation's fitted curve overlaid (coloured as in the legend below); the y-axis is clamped to the data range, so on ill-conditioned cases the unstable fits visibly shoot off the frame. The picker below recommends an implementation live from these measured runs.
Tradeoff Summary
Running tests…
Which implementation should I use?
Waiting for the benchmark run…
Loading test cases…