Migrating from regression
How to move an existing project to @statili/compat-regression.
@statili/compat-regression is a drop-in replacement for regression (regression-js), which has been unchanged since December 2017.
Same five methods, same result shape, same equation strings. It fixes four defects in the original and adds a way to find out when a fit failed — something the original API has no room to express.
The change
- import regression from "regression";
+ import regression from "@statili/compat-regression";
npm install @statili/compat-regression
Nothing else changes. linear, exponential, logarithmic, power, polynomial and _round all keep their signatures, and require() works as before.
What is identical
Parity is measured, not claimed. The test suite loads regression 2.0.1 and compares both libraries over the same data, asserting equality of:
equation— includingpolynomial’s descending[aₙ … a₁, a₀]orderingstring— including they = 2x + -3a negative intercept producespoints,r2, andpredict(x)- the
_roundhelper and the shape of the default export
What is fixed
Null rows silently corrupted two of the five models
logarithmic and power skipped null rows when accumulating sums but divided by data.length. A single gap — the kind every charting library produces for a missing reading — moved the answer:
// y = 4·√x exactly, plus one gap row
const data = [[1, 4], [4, 8], [9, 12], [16, 16], [25, null]];
regression.power(data, { precision: 4 }).equation;
// [2.1311, 0.7772] ← exponent off by 55%
// @statili/compat-regression
[4, 0.5]; // correct
The original’s README states that “null values are ignored”. Its 2.0.0 changelog lists “Fixes null value bug”. Both were true of linear only.
Degenerate input produced confident wrong answers
There was no input validation anywhere. An empty array, a single point, a vertical line and NaN values each returned a plausible-looking result:
regression.linear([[1, 1], [1, 2], [1, 3]]);
// { equation: [0, 2], r2: 0, string: "y = 0x + 2", ... }
A horizontal line through the mean of three points that share an x — drawn with no signal that anything went wrong. Now:
const result = regression.linear([[1, 1], [1, 2], [1, 3]]);
result.ok; // false
result.error.type; // "DegenerateInput"
result.error.message; // "Cannot perform linear regression: all x-values are identical…"
Domain violations returned silent NaN
ln(x) for x ≤ 0, or ln(y) for y ≤ 0, produced NaN coefficients with no explanation. Those cases now report which constraint was violated and why.
The logarithmic intercept was rounded twice
The original computes the intercept from the already-rounded slope, carrying that rounding error into a second rounding. This is the one place output can differ:
const data = [[1, 2.4], [2, 4.1], [3, 5.6], [4, 8.2], [5, 9.8], [6, 12.4], [7, 13.9], [8, 16.1]];
regression.logarithmic(data).equation[0]; // 0.34
// @statili/compat-regression // 0.35
The true intercept is 0.34551, which rounds to 0.35. The difference only appears when the true value sits near a rounding boundary; at precision: 6 the two agree exactly.
What is added
Two fields, and only two:
interface Result {
// …everything `regression` returns, unchanged
ok: boolean;
error?: { type: string; message: string };
}
They are additive because the original API has no error channel. There is no way to say “I could not fit this” through its shape — which is exactly why it answers a vertical line with a confident horizontal one. Existing code ignores unknown properties and keeps working; code that wants to know can check:
const result = regression.linear(data);
if (!result.ok) {
console.warn(result.error.message);
return;
}
drawTrendLine(result.points);
error.type is one of InsufficientData, DegenerateInput, InvalidInput, MathError or NumericalStability.
On failure
Every original field is still present, so nothing downstream throws:
| Field | On failure |
|---|---|
equation |
NaN, sized to the model’s arity |
string |
"" |
points |
[] |
r2 |
NaN |
predict(x) |
[x, NaN] |
points is empty rather than full of NaN, which is the one place the failure shape deliberately differs. Charting libraries either throw on NaN points or render them as garbage; drawing nothing is the honest outcome.
Trade-offs
Size. 7.3 kB gzipped against the original’s 2.0 kB. Workspace dependencies are bundled rather than externalised, because the package being replaced is a single self-contained file that works from a script tag — a drop-in demanding three peer installs would not be one.
Formatting quirks are preserved. y = 2x + -3 stays. Callers parse and snapshot these strings, so tidying them would be a silent breaking change. Wrong numbers are fixed; cosmetic warts are not.
When to skip the shim
The shim exists to make switching a one-line change. If you are writing new code, use @statili/stats directly — it has a discriminated-union result, named coefficients rather than positional arrays, and features the original never had:
import { linear } from "@statili/stats";
const result = linear({ precision: 2 }, data);
if (!result.ok) return;
result.slope; // named, not equation[0]
result.pValueM; // significance of the slope
result.slopeInterval; // 95% confidence interval
result.interval(x); // confidence or prediction band at any x
It also offers multilinear, logistic and LOESS smoothing, none of which the original has.