Update index.html
Browse files- index.html +42 -31
index.html
CHANGED
|
@@ -520,41 +520,52 @@
|
|
| 520 |
|
| 521 |
// --- MATHEMATICAL REgRESSION ENGINE ---
|
| 522 |
|
| 523 |
-
// Linear regression
|
|
|
|
|
|
|
|
|
|
| 524 |
function calculateLinearFit(points) {
|
| 525 |
if (points.length < 2) return null;
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
sumX
|
| 530 |
-
sumY += p.y;
|
| 531 |
-
sumXY += p.x * p.y;
|
| 532 |
-
sumXX += p.x * p.x;
|
| 533 |
-
}
|
| 534 |
-
let m, c;
|
| 535 |
-
if (forceOrigin) {
|
| 536 |
-
let sumXY_o = 0, sumXX_o = 0;
|
| 537 |
for (let p of points) {
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
| 540 |
}
|
| 541 |
-
m
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
ssTot
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
}
|
| 559 |
|
| 560 |
// Quadratic regression
|
|
|
|
| 520 |
|
| 521 |
// --- MATHEMATICAL REgRESSION ENGINE ---
|
| 522 |
|
| 523 |
+
// Linear regression.
|
| 524 |
+
// "Force scale through origin" only pins the intercept when that line
|
| 525 |
+
// still fits. A negative straight line that does not pass through (0,0)
|
| 526 |
+
// must stay a negative linear fit, not get replaced by a curve.
|
| 527 |
function calculateLinearFit(points) {
|
| 528 |
if (points.length < 2) return null;
|
| 529 |
+
|
| 530 |
+
const fit = (throughOrigin) => {
|
| 531 |
+
const n = points.length;
|
| 532 |
+
let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
for (let p of points) {
|
| 534 |
+
sumX += p.x;
|
| 535 |
+
sumY += p.y;
|
| 536 |
+
sumXY += p.x * p.y;
|
| 537 |
+
sumXX += p.x * p.x;
|
| 538 |
}
|
| 539 |
+
let m, c;
|
| 540 |
+
if (throughOrigin) {
|
| 541 |
+
m = sumXX === 0 ? 0 : sumXY / sumXX;
|
| 542 |
+
c = 0;
|
| 543 |
+
} else {
|
| 544 |
+
const denominator = (n * sumXX) - (sumX * sumX);
|
| 545 |
+
if (denominator === 0) return null;
|
| 546 |
+
m = ((n * sumXY) - (sumX * sumY)) / denominator;
|
| 547 |
+
c = (sumY - (m * sumX)) / n;
|
| 548 |
+
}
|
| 549 |
+
const yMean = sumY / n;
|
| 550 |
+
let ssTot = 0, ssRes = 0;
|
| 551 |
+
for (let p of points) {
|
| 552 |
+
ssTot += Math.pow(p.y - yMean, 2);
|
| 553 |
+
const yPred = m * p.x + c;
|
| 554 |
+
ssRes += Math.pow(p.y - yPred, 2);
|
| 555 |
+
}
|
| 556 |
+
const r2 = ssTot === 0 ? 1 : 1 - (ssRes / ssTot);
|
| 557 |
+
return { m, c, r2, type: 'linear', label: 'Linear Fit', f: (x) => m * x + c };
|
| 558 |
+
};
|
| 559 |
+
|
| 560 |
+
const free = fit(false);
|
| 561 |
+
if (!free) return null;
|
| 562 |
+
if (!forceOrigin) return free;
|
| 563 |
+
|
| 564 |
+
const forced = fit(true);
|
| 565 |
+
// Origin constraint is only kept when it does not destroy a clearly linear trend
|
| 566 |
+
// (including a negative gradient whose intercept is not zero).
|
| 567 |
+
if (forced && forced.r2 >= 0.95 && forced.r2 >= free.r2 - 0.02) return forced;
|
| 568 |
+
return free;
|
| 569 |
}
|
| 570 |
|
| 571 |
// Quadratic regression
|