@visactor/vutils 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/REGRESSION.md +36 -0
- package/cjs/common/ascending.js +2 -1
- package/cjs/common/deviation.js +1 -2
- package/cjs/common/getType.js +2 -1
- package/cjs/common/index.d.ts +1 -0
- package/cjs/common/index.js +2 -1
- package/cjs/common/index.js.map +1 -1
- package/cjs/common/isFunction.js +1 -2
- package/cjs/common/isObject.js +2 -1
- package/cjs/common/isPrototype.js +1 -1
- package/cjs/common/isRegExp.js +1 -1
- package/cjs/common/isShallowEqual.js +1 -1
- package/cjs/common/isString.js +1 -1
- package/cjs/common/isType.js +1 -1
- package/cjs/common/regression-bootstrap.d.ts +14 -0
- package/cjs/common/regression-bootstrap.js +37 -0
- package/cjs/common/regression-bootstrap.js.map +1 -0
- package/cjs/common/regression-linear.d.ts +1 -0
- package/cjs/common/regression-linear.js +11 -29
- package/cjs/common/regression-linear.js.map +1 -1
- package/cjs/common/regression-lowess.d.ts +9 -8
- package/cjs/common/regression-lowess.js +109 -130
- package/cjs/common/regression-lowess.js.map +1 -1
- package/cjs/common/regression-polynomial.d.ts +1 -0
- package/cjs/common/regression-polynomial.js +11 -92
- package/cjs/common/regression-polynomial.js.map +1 -1
- package/cjs/common/regression-solver.d.ts +26 -0
- package/cjs/common/regression-solver.js +180 -0
- package/cjs/common/regression-solver.js.map +1 -0
- package/cjs/data-structure/bounds.js +1 -1
- package/cjs/data-structure/hashTable.js +1 -2
- package/cjs/data-structure/index.js +1 -1
- package/cjs/data-structure/matrix.js +1 -1
- package/cjs/data-structure/point.js +1 -1
- package/dist/index.js +471 -340
- package/dist/index.min.js +16 -16
- package/es/common/ascending.js +2 -1
- package/es/common/deviation.js +1 -2
- package/es/common/getType.js +2 -1
- package/es/common/index.d.ts +1 -0
- package/es/common/index.js +2 -0
- package/es/common/index.js.map +1 -1
- package/es/common/isFunction.js +1 -2
- package/es/common/isObject.js +2 -1
- package/es/common/isPrototype.js +1 -1
- package/es/common/isRegExp.js +1 -1
- package/es/common/isShallowEqual.js +1 -1
- package/es/common/isString.js +1 -1
- package/es/common/isType.js +1 -1
- package/es/common/regression-bootstrap.d.ts +14 -0
- package/es/common/regression-bootstrap.js +31 -0
- package/es/common/regression-bootstrap.js.map +1 -0
- package/es/common/regression-linear.d.ts +1 -0
- package/es/common/regression-linear.js +9 -27
- package/es/common/regression-linear.js.map +1 -1
- package/es/common/regression-lowess.d.ts +9 -8
- package/es/common/regression-lowess.js +106 -130
- package/es/common/regression-lowess.js.map +1 -1
- package/es/common/regression-polynomial.d.ts +1 -0
- package/es/common/regression-polynomial.js +9 -86
- package/es/common/regression-polynomial.js.map +1 -1
- package/es/common/regression-solver.d.ts +26 -0
- package/es/common/regression-solver.js +168 -0
- package/es/common/regression-solver.js.map +1 -0
- package/es/data-structure/bounds.js +1 -1
- package/es/data-structure/hashTable.js +1 -2
- package/es/data-structure/index.js +1 -1
- package/es/data-structure/matrix.js +1 -1
- package/es/data-structure/point.js +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -2236,6 +2236,213 @@
|
|
|
2236
2236
|
return [min, max];
|
|
2237
2237
|
};
|
|
2238
2238
|
|
|
2239
|
+
function regressionPoints(data, x, y) {
|
|
2240
|
+
const points = [];
|
|
2241
|
+
data.forEach(d => {
|
|
2242
|
+
const a = x(d);
|
|
2243
|
+
const b = y(d);
|
|
2244
|
+
// 保留既有 accessor 对缺失观测的排除行为;无穷大不是可拟合观测。
|
|
2245
|
+
if (a != null && b != null && Number.isFinite(+a) && Number.isFinite(+b)) {
|
|
2246
|
+
points.push({ x: +a, y: +b });
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
return points;
|
|
2250
|
+
}
|
|
2251
|
+
function normalizeRegression(value, center, scale) {
|
|
2252
|
+
const delta = value - center;
|
|
2253
|
+
return Number.isFinite(delta) ? delta / scale : value / scale - center / scale;
|
|
2254
|
+
}
|
|
2255
|
+
function regressionBasis(points) {
|
|
2256
|
+
let xMin = Infinity;
|
|
2257
|
+
let xMax = -Infinity;
|
|
2258
|
+
let yMin = Infinity;
|
|
2259
|
+
let yMax = -Infinity;
|
|
2260
|
+
for (const p of points) {
|
|
2261
|
+
xMin = Math.min(xMin, p.x);
|
|
2262
|
+
xMax = Math.max(xMax, p.x);
|
|
2263
|
+
yMin = Math.min(yMin, p.y);
|
|
2264
|
+
yMax = Math.max(yMax, p.y);
|
|
2265
|
+
}
|
|
2266
|
+
if (!points.length) {
|
|
2267
|
+
return { xCenter: 0, xScale: 1, yCenter: 0, yScale: 1 };
|
|
2268
|
+
}
|
|
2269
|
+
const xCenter = xMin / 2 + xMax / 2;
|
|
2270
|
+
const yCenter = yMin / 2 + yMax / 2;
|
|
2271
|
+
return {
|
|
2272
|
+
xCenter,
|
|
2273
|
+
yCenter,
|
|
2274
|
+
xScale: Math.max(Math.abs(xMin - xCenter), Math.abs(xMax - xCenter)) || 1,
|
|
2275
|
+
yScale: Math.max(Math.abs(yMin - yCenter), Math.abs(yMax - yCenter)) || 1
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
/** 秩不足时对 QR 的小型 R 做单边 Jacobi SVD,获得明确的最小范数解。 */
|
|
2279
|
+
function minimumNorm(r, rhs) {
|
|
2280
|
+
const p = rhs.length;
|
|
2281
|
+
const a = r.map(row => row.slice());
|
|
2282
|
+
const v = Array.from({ length: p }, (_, i) => Array.from({ length: p }, (_, j) => +(i === j)));
|
|
2283
|
+
for (let sweep = 0; sweep < 50; sweep++) {
|
|
2284
|
+
let changed = false;
|
|
2285
|
+
for (let j = 0; j < p - 1; j++) {
|
|
2286
|
+
for (let k = j + 1; k < p; k++) {
|
|
2287
|
+
let aa = 0;
|
|
2288
|
+
let bb = 0;
|
|
2289
|
+
let ab = 0;
|
|
2290
|
+
for (let i = 0; i < p; i++) {
|
|
2291
|
+
aa += a[i][j] ** 2;
|
|
2292
|
+
bb += a[i][k] ** 2;
|
|
2293
|
+
ab += a[i][j] * a[i][k];
|
|
2294
|
+
}
|
|
2295
|
+
if (Math.abs(ab) <= Number.EPSILON * 8 * Math.sqrt(aa * bb)) {
|
|
2296
|
+
continue;
|
|
2297
|
+
}
|
|
2298
|
+
changed = true;
|
|
2299
|
+
const tau = (bb - aa) / (2 * ab);
|
|
2300
|
+
const t = (tau >= 0 ? 1 : -1) / (Math.abs(tau) + Math.hypot(1, tau));
|
|
2301
|
+
const c = 1 / Math.hypot(1, t);
|
|
2302
|
+
const s = c * t;
|
|
2303
|
+
for (let i = 0; i < p; i++) {
|
|
2304
|
+
const aj = a[i][j];
|
|
2305
|
+
const ak = a[i][k];
|
|
2306
|
+
const vj = v[i][j];
|
|
2307
|
+
const vk = v[i][k];
|
|
2308
|
+
a[i][j] = c * aj - s * ak;
|
|
2309
|
+
a[i][k] = s * aj + c * ak;
|
|
2310
|
+
v[i][j] = c * vj - s * vk;
|
|
2311
|
+
v[i][k] = s * vj + c * vk;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
if (!changed) {
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
const norms = Array.from({ length: p }, (_, j) => Math.hypot(...a.map(row => row[j])));
|
|
2320
|
+
const tolerance = Math.max(...norms) * Number.EPSILON * p * 64;
|
|
2321
|
+
const coef = new Array(p).fill(0);
|
|
2322
|
+
let rank = 0;
|
|
2323
|
+
for (let j = 0; j < p; j++) {
|
|
2324
|
+
if (norms[j] <= tolerance) {
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
rank++;
|
|
2328
|
+
let projection = 0;
|
|
2329
|
+
for (let i = 0; i < p; i++) {
|
|
2330
|
+
projection += a[i][j] * rhs[i];
|
|
2331
|
+
}
|
|
2332
|
+
projection /= norms[j] ** 2;
|
|
2333
|
+
for (let i = 0; i < p; i++) {
|
|
2334
|
+
coef[i] += v[i][j] * projection;
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
return { coef, rank };
|
|
2338
|
+
}
|
|
2339
|
+
function fitRegression(points, degree, basis = regressionBasis(points)) {
|
|
2340
|
+
const p = degree + 1;
|
|
2341
|
+
if (degree === 1) {
|
|
2342
|
+
let mx = 0;
|
|
2343
|
+
let my = 0;
|
|
2344
|
+
let n = 0;
|
|
2345
|
+
for (const row of points) {
|
|
2346
|
+
n++;
|
|
2347
|
+
mx += (normalizeRegression(row.x, basis.xCenter, basis.xScale) - mx) / n;
|
|
2348
|
+
my += (normalizeRegression(row.y, basis.yCenter, basis.yScale) - my) / n;
|
|
2349
|
+
}
|
|
2350
|
+
let sxx = 0;
|
|
2351
|
+
let sxy = 0;
|
|
2352
|
+
for (const row of points) {
|
|
2353
|
+
const dx = normalizeRegression(row.x, basis.xCenter, basis.xScale) - mx;
|
|
2354
|
+
sxx += dx * dx;
|
|
2355
|
+
sxy += dx * (normalizeRegression(row.y, basis.yCenter, basis.yScale) - my);
|
|
2356
|
+
}
|
|
2357
|
+
const b = sxx === 0 ? 0 : sxy / sxx;
|
|
2358
|
+
const coef = sxx === 0 ? [my / (1 + mx * mx), (mx * my) / (1 + mx * mx)] : [my - b * mx, b];
|
|
2359
|
+
return { coef, rank: n === 0 ? 0 : sxx === 0 ? 1 : 2, basis };
|
|
2360
|
+
}
|
|
2361
|
+
const r = Array.from({ length: p }, () => new Array(p).fill(0));
|
|
2362
|
+
const rhs = new Array(p).fill(0);
|
|
2363
|
+
const row = new Array(p);
|
|
2364
|
+
for (const point of points) {
|
|
2365
|
+
const z = normalizeRegression(point.x, basis.xCenter, basis.xScale);
|
|
2366
|
+
row[0] = 1;
|
|
2367
|
+
for (let j = 1; j < p; j++) {
|
|
2368
|
+
row[j] = row[j - 1] * z;
|
|
2369
|
+
}
|
|
2370
|
+
let value = normalizeRegression(point.y, basis.yCenter, basis.yScale);
|
|
2371
|
+
// 增量 Givens QR 仅保留 p×p 的 R,bootstrap 不分配 N×p 矩阵。
|
|
2372
|
+
for (let j = 0; j < p; j++) {
|
|
2373
|
+
const norm = Math.hypot(r[j][j], row[j]);
|
|
2374
|
+
if (norm === 0) {
|
|
2375
|
+
continue;
|
|
2376
|
+
}
|
|
2377
|
+
const c = r[j][j] / norm;
|
|
2378
|
+
const s = row[j] / norm;
|
|
2379
|
+
r[j][j] = norm;
|
|
2380
|
+
for (let k = j + 1; k < p; k++) {
|
|
2381
|
+
const previous = r[j][k];
|
|
2382
|
+
r[j][k] = c * previous + s * row[k];
|
|
2383
|
+
row[k] = -s * previous + c * row[k];
|
|
2384
|
+
}
|
|
2385
|
+
const previous = rhs[j];
|
|
2386
|
+
rhs[j] = c * previous + s * value;
|
|
2387
|
+
value = -s * previous + c * value;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
const tolerance = Math.max(...r.map((row, i) => Math.abs(row[i]))) * Number.EPSILON * p * 64;
|
|
2391
|
+
if (r.some((row, i) => Math.abs(row[i]) <= tolerance)) {
|
|
2392
|
+
return Object.assign(Object.assign({}, minimumNorm(r, rhs)), { basis });
|
|
2393
|
+
}
|
|
2394
|
+
const coef = new Array(p);
|
|
2395
|
+
for (let i = p - 1; i >= 0; i--) {
|
|
2396
|
+
let value = rhs[i];
|
|
2397
|
+
for (let j = i + 1; j < p; j++) {
|
|
2398
|
+
value -= r[i][j] * coef[j];
|
|
2399
|
+
}
|
|
2400
|
+
coef[i] = value / r[i][i];
|
|
2401
|
+
}
|
|
2402
|
+
return { coef, rank: p, basis };
|
|
2403
|
+
}
|
|
2404
|
+
function regressionPrediction(model, x) {
|
|
2405
|
+
const z = normalizeRegression(x, model.basis.xCenter, model.basis.xScale);
|
|
2406
|
+
let value = 0;
|
|
2407
|
+
for (let j = model.coef.length - 1; j >= 0; j--) {
|
|
2408
|
+
value = value * z + model.coef[j];
|
|
2409
|
+
}
|
|
2410
|
+
return model.basis.yCenter + model.basis.yScale * value;
|
|
2411
|
+
}
|
|
2412
|
+
/** 保留公开的升幂 coef;预测始终使用稳定基底。 */
|
|
2413
|
+
function regressionCoefficients(model) {
|
|
2414
|
+
let coef = [model.coef[model.coef.length - 1]];
|
|
2415
|
+
const a = -model.basis.xCenter / model.basis.xScale;
|
|
2416
|
+
const b = 1 / model.basis.xScale;
|
|
2417
|
+
for (let j = model.coef.length - 2; j >= 0; j--) {
|
|
2418
|
+
const next = new Array(coef.length + 1).fill(0);
|
|
2419
|
+
coef.forEach((c, i) => {
|
|
2420
|
+
next[i] += a * c;
|
|
2421
|
+
next[i + 1] += b * c;
|
|
2422
|
+
});
|
|
2423
|
+
next[0] += model.coef[j];
|
|
2424
|
+
coef = next;
|
|
2425
|
+
}
|
|
2426
|
+
coef = coef.map(c => c * model.basis.yScale);
|
|
2427
|
+
coef[0] += model.basis.yCenter;
|
|
2428
|
+
if (coef.some(c => !Number.isFinite(c))) {
|
|
2429
|
+
throw new RangeError('Regression coefficients are not finite');
|
|
2430
|
+
}
|
|
2431
|
+
return coef;
|
|
2432
|
+
}
|
|
2433
|
+
function regressionGrid(min, max, count, predict) {
|
|
2434
|
+
if (!Number.isInteger(count) || count <= 0 || !Number.isFinite(min) || !Number.isFinite(max)) {
|
|
2435
|
+
return [];
|
|
2436
|
+
}
|
|
2437
|
+
const width = max - min;
|
|
2438
|
+
const step = count === 1 ? 0 : width / (count - 1);
|
|
2439
|
+
return Array.from({ length: count }, (_, i) => {
|
|
2440
|
+
const t = count === 1 ? 0 : i / (count - 1);
|
|
2441
|
+
const x = i === 0 ? min : i === count - 1 ? max : Number.isFinite(width) ? min + i * step : (1 - t) * min + t * max;
|
|
2442
|
+
return { x, y: predict(x) };
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2239
2446
|
/** Compute a two-sided normal critical value without subtracting a small tail probability from one. */
|
|
2240
2447
|
function confidenceCriticalValue(alpha) {
|
|
2241
2448
|
if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
|
@@ -2347,39 +2554,19 @@
|
|
|
2347
2554
|
function regressionLinear(data, x = d => d.x, y = d => d.y, options) {
|
|
2348
2555
|
var _a;
|
|
2349
2556
|
const alpha = (_a = options === null || options === void 0 ? void 0 : options.alpha) !== null && _a !== void 0 ? _a : 0.05;
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2557
|
+
const points = regressionPoints(data, x, y);
|
|
2558
|
+
const model = fitRegression(points, 1);
|
|
2559
|
+
const coef = regressionCoefficients(model);
|
|
2560
|
+
const a = coef[0];
|
|
2561
|
+
const b = coef[1];
|
|
2562
|
+
const predict = (xx) => regressionPrediction(model, xx);
|
|
2563
|
+
const comps = computeLinearCIComponents(data, x, y, predict);
|
|
2353
2564
|
let meanY = 0;
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
visitPoints(data, x, y, (xi, yi) => {
|
|
2357
|
-
n++;
|
|
2358
|
-
meanX += (xi - meanX) / n;
|
|
2359
|
-
meanY += (yi - meanY) / n;
|
|
2360
|
-
meanXY += (xi * yi - meanXY) / n;
|
|
2361
|
-
meanX2 += (xi * xi - meanX2) / n;
|
|
2565
|
+
points.forEach((p, i) => {
|
|
2566
|
+
meanY += (p.y - meanY) / (i + 1);
|
|
2362
2567
|
});
|
|
2363
|
-
const { a, b } = ordinaryLeastSquares(meanX, meanY, meanXY, meanX2);
|
|
2364
|
-
const predict = (xx) => a + b * xx;
|
|
2365
|
-
const comps = computeLinearCIComponents(data, x, y, predict);
|
|
2366
2568
|
function evaluateGrid(N) {
|
|
2367
|
-
|
|
2368
|
-
if (comps.n === 0 || N <= 0) {
|
|
2369
|
-
return out;
|
|
2370
|
-
}
|
|
2371
|
-
if (comps.min === comps.max) {
|
|
2372
|
-
for (let i = 0; i < N; i++) {
|
|
2373
|
-
out.push({ x: comps.min, y: predict(comps.min) });
|
|
2374
|
-
}
|
|
2375
|
-
return out;
|
|
2376
|
-
}
|
|
2377
|
-
const step = (comps.max - comps.min) / (N - 1);
|
|
2378
|
-
for (let i = 0; i < N; i++) {
|
|
2379
|
-
const px = i === N - 1 ? comps.max : comps.min + step * i;
|
|
2380
|
-
out.push({ x: px, y: predict(px) });
|
|
2381
|
-
}
|
|
2382
|
-
return out;
|
|
2569
|
+
return regressionGrid(comps.min, comps.max, N, predict);
|
|
2383
2570
|
}
|
|
2384
2571
|
function confidenceInterval(N = 50) {
|
|
2385
2572
|
const out = [];
|
|
@@ -2424,6 +2611,7 @@
|
|
|
2424
2611
|
}
|
|
2425
2612
|
return {
|
|
2426
2613
|
coef: { a, b },
|
|
2614
|
+
rank: model.rank,
|
|
2427
2615
|
predict,
|
|
2428
2616
|
rSquared: rSquared(data, x, y, meanY, predict),
|
|
2429
2617
|
evaluateGrid,
|
|
@@ -2589,222 +2777,226 @@
|
|
|
2589
2777
|
};
|
|
2590
2778
|
}
|
|
2591
2779
|
|
|
2592
|
-
function tricube(u) {
|
|
2593
|
-
const uu = Math.abs(u);
|
|
2594
|
-
if (uu >= 1) {
|
|
2595
|
-
return 0;
|
|
2596
|
-
}
|
|
2597
|
-
const t = 1 - uu * uu * uu;
|
|
2598
|
-
return t * t * t;
|
|
2599
|
-
}
|
|
2600
2780
|
/**
|
|
2601
|
-
*
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
const n = sortedData.length;
|
|
2605
|
-
if (n <= maxSamples) {
|
|
2606
|
-
return sortedData;
|
|
2607
|
-
}
|
|
2608
|
-
const sampled = [];
|
|
2609
|
-
// More aggressive sampling - use exact step size
|
|
2610
|
-
const step = n / maxSamples;
|
|
2611
|
-
for (let i = 0; i < maxSamples; i++) {
|
|
2612
|
-
const idx = Math.min(Math.floor(i * step), n - 1);
|
|
2613
|
-
sampled.push(sortedData[idx]);
|
|
2614
|
-
}
|
|
2615
|
-
return sampled;
|
|
2616
|
-
}
|
|
2617
|
-
/**
|
|
2618
|
-
* Simple lowess implementation (univariate x)
|
|
2619
|
-
* options:
|
|
2620
|
-
* - span: fraction of points used in local regression (0,1]
|
|
2621
|
-
* - degree: 0 (constant) or 1 (linear)
|
|
2622
|
-
* - iterations: number of robustifying iterations
|
|
2623
|
-
* - alpha: confidence level for CI
|
|
2624
|
-
* - maxSamples: maximum number of points to use (default: 1000 for fast processing)
|
|
2781
|
+
* 局部线性 LOWESS。默认全量观测、span=2/3、三次稳健重加权。
|
|
2782
|
+
* delta 显式插值加速;maxSamples 仅保留显式选择的历史抽样用途。
|
|
2783
|
+
* predict 与 evaluateGrid 查询构建时固定的同一模型,不触发再拟合。
|
|
2625
2784
|
*/
|
|
2626
2785
|
function regressionLowess(data, x = d => d.x, y = d => d.y, options = {}) {
|
|
2627
|
-
var _a;
|
|
2628
|
-
const span = options.span
|
|
2629
|
-
const degree = options.degree
|
|
2630
|
-
const
|
|
2631
|
-
const
|
|
2632
|
-
const
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2786
|
+
var _a, _b, _c, _d, _e;
|
|
2787
|
+
const span = (_a = options.span) !== null && _a !== void 0 ? _a : 2 / 3;
|
|
2788
|
+
const degree = (_b = options.degree) !== null && _b !== void 0 ? _b : 1;
|
|
2789
|
+
const iterations = (_c = options.iterations) !== null && _c !== void 0 ? _c : 3;
|
|
2790
|
+
const delta = (_d = options.delta) !== null && _d !== void 0 ? _d : 0;
|
|
2791
|
+
const alpha = (_e = options.alpha) !== null && _e !== void 0 ? _e : 0.05;
|
|
2792
|
+
if (!(span > 0 && span <= 1) ||
|
|
2793
|
+
!Number.isInteger(iterations) ||
|
|
2794
|
+
iterations < 0 ||
|
|
2795
|
+
!Number.isFinite(delta) ||
|
|
2796
|
+
delta < 0 ||
|
|
2797
|
+
(degree !== 0 && degree !== 1) ||
|
|
2798
|
+
(options.maxSamples != null && (!Number.isInteger(options.maxSamples) || options.maxSamples < 2))) {
|
|
2799
|
+
throw new RangeError('Invalid LOWESS span, degree, iterations, delta or maxSamples');
|
|
2800
|
+
}
|
|
2801
|
+
let pts = regressionPoints(data, x, y).sort((a, b) => a.x - b.x || a.y - b.y);
|
|
2802
|
+
if (options.maxSamples != null && pts.length > options.maxSamples) {
|
|
2803
|
+
const full = pts;
|
|
2804
|
+
const count = options.maxSamples;
|
|
2805
|
+
pts = Array.from({ length: count }, (_, i) => full[Math.floor((i * (full.length - 1)) / (count - 1))]);
|
|
2806
|
+
}
|
|
2807
|
+
const n = pts.length;
|
|
2808
|
+
const ptsX = pts.map(p => p.x);
|
|
2809
|
+
const ptsY = pts.map(p => p.y);
|
|
2810
|
+
const basis = regressionBasis(pts);
|
|
2811
|
+
const normalizedX = ptsX.map(v => normalizeRegression(v, basis.xCenter, basis.xScale));
|
|
2812
|
+
const normalizedY = ptsY.map(v => normalizeRegression(v, basis.yCenter, basis.yScale));
|
|
2813
|
+
const count = Math.min(n, Math.max(2, Math.floor(span * n + 1e-10)));
|
|
2814
|
+
let robustWeights;
|
|
2815
|
+
function exact(x0) {
|
|
2816
|
+
if (!n) {
|
|
2653
2817
|
return 0;
|
|
2654
2818
|
}
|
|
2655
|
-
|
|
2819
|
+
const query = normalizeRegression(x0, basis.xCenter, basis.xScale);
|
|
2820
|
+
// 找到连续的最近邻窗口;相等距离选择较小的 x(ties 的 y 已排序)。
|
|
2656
2821
|
let left = 0;
|
|
2657
|
-
let right = n;
|
|
2822
|
+
let right = n - count;
|
|
2658
2823
|
while (left < right) {
|
|
2659
|
-
const mid = (left + right)
|
|
2660
|
-
|
|
2824
|
+
const mid = (left + right) >>> 1;
|
|
2825
|
+
const lowerDistance = x0 - ptsX[mid];
|
|
2826
|
+
const upperDistance = ptsX[mid + count] - x0;
|
|
2827
|
+
if (Number.isFinite(lowerDistance) && Number.isFinite(upperDistance)
|
|
2828
|
+
? lowerDistance > upperDistance
|
|
2829
|
+
: query - normalizedX[mid] > normalizedX[mid + count] - query) {
|
|
2661
2830
|
left = mid + 1;
|
|
2662
2831
|
}
|
|
2663
2832
|
else {
|
|
2664
2833
|
right = mid;
|
|
2665
2834
|
}
|
|
2666
2835
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
if (lo >= 0 && (hi >= n || x0 - ptsX[lo] <= ptsX[hi] - x0)) {
|
|
2673
|
-
lo--;
|
|
2836
|
+
let start = left;
|
|
2837
|
+
let end = left + count;
|
|
2838
|
+
if (ptsX[start] === ptsX[end - 1]) {
|
|
2839
|
+
while (start > 0 && ptsX[start - 1] === ptsX[start]) {
|
|
2840
|
+
start--;
|
|
2674
2841
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
const actualStart = lo + 1;
|
|
2680
|
-
const end = hi;
|
|
2681
|
-
// Find max distance and compute weights in single pass
|
|
2682
|
-
let maxDist = 0;
|
|
2683
|
-
const windowSize = end - actualStart;
|
|
2684
|
-
const distances = new Array(windowSize);
|
|
2685
|
-
for (let i = actualStart; i < end; i++) {
|
|
2686
|
-
const dist = Math.abs(ptsX[i] - x0);
|
|
2687
|
-
distances[i - actualStart] = dist;
|
|
2688
|
-
if (dist > maxDist) {
|
|
2689
|
-
maxDist = dist;
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
// Compute weights using pre-calculated distances
|
|
2693
|
-
let sumw = 0;
|
|
2694
|
-
const w = new Array(windowSize);
|
|
2695
|
-
for (let i = 0; i < windowSize; i++) {
|
|
2696
|
-
const u = maxDist === 0 ? 0 : distances[i] / maxDist;
|
|
2697
|
-
let wi = tricube(u);
|
|
2698
|
-
if (robustWeights && robustWeights[actualStart + i] != null) {
|
|
2699
|
-
wi *= robustWeights[actualStart + i];
|
|
2700
|
-
}
|
|
2701
|
-
w[i] = wi;
|
|
2702
|
-
sumw += wi;
|
|
2703
|
-
}
|
|
2704
|
-
if (sumw === 0) {
|
|
2705
|
-
// Fall back to the nearest y, preserving input order for equal distances.
|
|
2706
|
-
let nearestIdx = 0;
|
|
2707
|
-
let nearestDistance = Math.abs(ptsX[0] - x0);
|
|
2708
|
-
for (let i = 1; i < n; i++) {
|
|
2709
|
-
const distance = Math.abs(ptsX[i] - x0);
|
|
2710
|
-
if (distance < nearestDistance ||
|
|
2711
|
-
(distance === nearestDistance && sampledPoints[i].index < sampledPoints[nearestIdx].index)) {
|
|
2712
|
-
nearestIdx = i;
|
|
2713
|
-
nearestDistance = distance;
|
|
2714
|
-
}
|
|
2842
|
+
while (end < n && ptsX[end] === ptsX[start]) {
|
|
2843
|
+
end++;
|
|
2715
2844
|
}
|
|
2716
|
-
return ptsY[nearestIdx];
|
|
2717
2845
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2846
|
+
const rawRadius = Math.max(Math.abs(ptsX[start] - x0), Math.abs(ptsX[end - 1] - x0));
|
|
2847
|
+
const scaled = !Number.isFinite(rawRadius);
|
|
2848
|
+
const radius = scaled
|
|
2849
|
+
? Math.max(Math.abs(normalizedX[start] - query), Math.abs(normalizedX[end - 1] - query))
|
|
2850
|
+
: rawRadius;
|
|
2851
|
+
// 局部响应坐标不能由远处、未参与该邻域的观测决定精度。
|
|
2852
|
+
let yMin = Infinity;
|
|
2853
|
+
let yMax = -Infinity;
|
|
2854
|
+
for (let i = start; i < end; i++) {
|
|
2855
|
+
yMin = Math.min(yMin, ptsY[i]);
|
|
2856
|
+
yMax = Math.max(yMax, ptsY[i]);
|
|
2724
2857
|
}
|
|
2725
|
-
|
|
2858
|
+
const yCenter = yMin / 2 + yMax / 2;
|
|
2859
|
+
const yScale = Math.max(Math.abs(yMin - yCenter), Math.abs(yMax - yCenter)) || 1;
|
|
2726
2860
|
let sw = 0;
|
|
2727
2861
|
let sx = 0;
|
|
2728
2862
|
let sy = 0;
|
|
2729
2863
|
let sxx = 0;
|
|
2730
2864
|
let sxy = 0;
|
|
2731
|
-
for (let i =
|
|
2732
|
-
const
|
|
2733
|
-
const
|
|
2734
|
-
const
|
|
2735
|
-
const
|
|
2736
|
-
sw +=
|
|
2737
|
-
sx +=
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2865
|
+
for (let i = start; i < end; i++) {
|
|
2866
|
+
const z = radius === 0 ? 0 : (scaled ? normalizedX[i] - query : ptsX[i] - x0) / radius;
|
|
2867
|
+
const u = Math.abs(z);
|
|
2868
|
+
const t = u >= 1 ? 0 : 1 - u * u * u;
|
|
2869
|
+
const w = t * t * t * (robustWeights ? robustWeights[i] : 1);
|
|
2870
|
+
sw += w;
|
|
2871
|
+
sx += w * z;
|
|
2872
|
+
const response = normalizeRegression(ptsY[i], yCenter, yScale);
|
|
2873
|
+
sy += w * response;
|
|
2874
|
+
sxx += w * z * z;
|
|
2875
|
+
sxy += w * z * response;
|
|
2876
|
+
}
|
|
2877
|
+
if (sw === 0) {
|
|
2878
|
+
// 核边界零权重时使用最近有效观测的加权常数;等距离取均值。
|
|
2879
|
+
let distance = Infinity;
|
|
2880
|
+
let weight = 0;
|
|
2881
|
+
let value = 0;
|
|
2882
|
+
for (let i = 0; i < n; i++) {
|
|
2883
|
+
const w = robustWeights ? robustWeights[i] : 1;
|
|
2884
|
+
if (w === 0) {
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
const d = Math.abs(scaled ? normalizedX[i] - query : ptsX[i] - x0);
|
|
2888
|
+
if (d < distance) {
|
|
2889
|
+
distance = d;
|
|
2890
|
+
weight = w;
|
|
2891
|
+
value = ptsY[i];
|
|
2892
|
+
}
|
|
2893
|
+
else if (d === distance) {
|
|
2894
|
+
value = value * (weight / (weight + w)) + ptsY[i] * (w / (weight + w));
|
|
2895
|
+
weight += w;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (weight === 0) {
|
|
2899
|
+
throw new RangeError('LOWESS has no effective local observations');
|
|
2900
|
+
}
|
|
2901
|
+
return value;
|
|
2902
|
+
}
|
|
2903
|
+
const mx = sx / sw;
|
|
2904
|
+
const my = sy / sw;
|
|
2905
|
+
const variance = sxx / sw - mx * mx;
|
|
2906
|
+
const slope = degree === 0 || variance <= Number.EPSILON * 32 ? 0 : (sxy / sw - mx * my) / variance;
|
|
2907
|
+
return yCenter + yScale * (my - slope * mx);
|
|
2908
|
+
}
|
|
2909
|
+
// delta 不删除观测。只减少局部回归求值位置,全部残差参与重加权。
|
|
2910
|
+
const anchors = [];
|
|
2911
|
+
for (let i = 0; i < n;) {
|
|
2912
|
+
anchors.push(i);
|
|
2913
|
+
if (i === n - 1) {
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
let next = i + 1;
|
|
2917
|
+
if (delta > 0) {
|
|
2918
|
+
while (next + 1 < n && ptsX[next + 1] - ptsX[i] <= delta) {
|
|
2919
|
+
next++;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
// 重复 x 一起使用同一预测;端点不能丢失。
|
|
2923
|
+
while (next + 1 < n && ptsX[next + 1] === ptsX[next]) {
|
|
2924
|
+
next++;
|
|
2925
|
+
}
|
|
2926
|
+
i = next;
|
|
2748
2927
|
}
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2928
|
+
let fits = new Array(n);
|
|
2929
|
+
function fitObservations() {
|
|
2930
|
+
const out = new Array(n);
|
|
2931
|
+
for (let a = 0; a < anchors.length; a++) {
|
|
2932
|
+
const index = anchors[a];
|
|
2933
|
+
out[index] = exact(ptsX[index]);
|
|
2934
|
+
if (a === 0) {
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2937
|
+
const previous = anchors[a - 1];
|
|
2938
|
+
const rawWidth = ptsX[index] - ptsX[previous];
|
|
2939
|
+
const width = Number.isFinite(rawWidth) ? rawWidth : normalizedX[index] - normalizedX[previous];
|
|
2940
|
+
for (let i = previous + 1; i < index; i++) {
|
|
2941
|
+
const t = width === 0
|
|
2942
|
+
? 0
|
|
2943
|
+
: (Number.isFinite(rawWidth) ? ptsX[i] - ptsX[previous] : normalizedX[i] - normalizedX[previous]) / width;
|
|
2944
|
+
out[i] = out[previous] * (1 - t) + out[index] * t;
|
|
2755
2945
|
}
|
|
2756
|
-
return out;
|
|
2757
2946
|
}
|
|
2758
|
-
return
|
|
2947
|
+
return out;
|
|
2759
2948
|
}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2949
|
+
for (let iteration = 0; iteration <= iterations; iteration++) {
|
|
2950
|
+
fits = fitObservations();
|
|
2951
|
+
if (iteration === iterations) {
|
|
2952
|
+
break;
|
|
2763
2953
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2954
|
+
const residuals = fits.map((v, i) => {
|
|
2955
|
+
const residual = ptsY[i] - v;
|
|
2956
|
+
return Number.isFinite(residual)
|
|
2957
|
+
? Math.abs(residual / basis.yScale)
|
|
2958
|
+
: Math.abs(normalizedY[i] - normalizeRegression(v, basis.yCenter, basis.yScale));
|
|
2959
|
+
});
|
|
2960
|
+
const med = median(residuals.slice());
|
|
2961
|
+
const currentFits = fits;
|
|
2962
|
+
robustWeights = residuals.map((r, i) => {
|
|
2963
|
+
if (med === 0) {
|
|
2964
|
+
const tolerance = Number.EPSILON * 64 * (Math.max(Math.abs(ptsY[i]), Math.abs(currentFits[i])) / basis.yScale);
|
|
2965
|
+
return r <= tolerance ? 1 : 0;
|
|
2966
|
+
}
|
|
2967
|
+
const u = r / (6 * med);
|
|
2968
|
+
return u >= 1 ? 0 : (1 - u * u) ** 2;
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
function predictSingle(x0) {
|
|
2972
|
+
if (delta === 0 || n < 2 || x0 < ptsX[0] || x0 > ptsX[n - 1]) {
|
|
2973
|
+
return exact(x0);
|
|
2766
2974
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
out[i] = { x: min, y: v };
|
|
2975
|
+
let lo = 0;
|
|
2976
|
+
let hi = n - 1;
|
|
2977
|
+
while (hi - lo > 1) {
|
|
2978
|
+
const mid = (lo + hi) >>> 1;
|
|
2979
|
+
if (ptsX[mid] <= x0) {
|
|
2980
|
+
lo = mid;
|
|
2774
2981
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
const step = (max - min) / (N - 1);
|
|
2778
|
-
// optionally add robust iterations
|
|
2779
|
-
let robustWeights;
|
|
2780
|
-
// Disable robustness iterations for large datasets to improve performance
|
|
2781
|
-
// Users can override by setting iterations explicitly
|
|
2782
|
-
const effectiveIterations = options.iterations != null ? iterations : n > 500 ? 0 : iterations;
|
|
2783
|
-
if (effectiveIterations > 0) {
|
|
2784
|
-
for (let iter = 0; iter < effectiveIterations; iter++) {
|
|
2785
|
-
// compute fits - pre-allocate arrays
|
|
2786
|
-
const fits = new Array(n);
|
|
2787
|
-
const res = new Array(n);
|
|
2788
|
-
for (let i = 0; i < n; i++) {
|
|
2789
|
-
fits[i] = predictSingle(ptsX[i], robustWeights);
|
|
2790
|
-
res[i] = Math.abs(ptsY[i] - fits[i]);
|
|
2791
|
-
}
|
|
2792
|
-
// median absolute deviation
|
|
2793
|
-
const sortedRes = res.slice().sort((a, b) => a - b);
|
|
2794
|
-
const med = sortedRes[Math.floor(n / 2)] || 0;
|
|
2795
|
-
robustWeights = new Array(n);
|
|
2796
|
-
for (let i = 0; i < n; i++) {
|
|
2797
|
-
const u = med === 0 ? 0 : res[i] / (6 * med);
|
|
2798
|
-
const w = Math.abs(u) >= 1 ? 0 : (1 - u * u) * (1 - u * u);
|
|
2799
|
-
robustWeights[i] = w;
|
|
2800
|
-
}
|
|
2982
|
+
else {
|
|
2983
|
+
hi = mid;
|
|
2801
2984
|
}
|
|
2802
2985
|
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
out[i] = { x: px, y: predictSingle(px, robustWeights) };
|
|
2986
|
+
if (ptsX[hi] === ptsX[lo]) {
|
|
2987
|
+
return fits[lo];
|
|
2806
2988
|
}
|
|
2807
|
-
|
|
2989
|
+
const width = ptsX[hi] - ptsX[lo];
|
|
2990
|
+
const t = Number.isFinite(width)
|
|
2991
|
+
? (x0 - ptsX[lo]) / width
|
|
2992
|
+
: (normalizeRegression(x0, basis.xCenter, basis.xScale) - normalizedX[lo]) / (normalizedX[hi] - normalizedX[lo]);
|
|
2993
|
+
return fits[lo] * (1 - t) + fits[hi] * t;
|
|
2994
|
+
}
|
|
2995
|
+
function predict(x0) {
|
|
2996
|
+
return Array.isArray(x0) ? x0.map(predictSingle) : predictSingle(x0);
|
|
2997
|
+
}
|
|
2998
|
+
function evaluateGrid(N) {
|
|
2999
|
+
return regressionGrid(ptsX[0], ptsX[n - 1], N, predictSingle);
|
|
2808
3000
|
}
|
|
2809
3001
|
function confidenceInterval(N = 50) {
|
|
2810
3002
|
const out = [];
|
|
@@ -2864,111 +3056,26 @@
|
|
|
2864
3056
|
predict,
|
|
2865
3057
|
evaluate: predict,
|
|
2866
3058
|
evaluateGrid,
|
|
2867
|
-
confidenceInterval
|
|
3059
|
+
confidenceInterval,
|
|
3060
|
+
// 节点值按 x 排序,重复观测仍保持其统计权重。
|
|
3061
|
+
fitted: pts.map((p, i) => ({ x: p.x, y: fits[i] }))
|
|
2868
3062
|
};
|
|
2869
3063
|
}
|
|
2870
3064
|
|
|
2871
|
-
function solveLinearSystem(A, b) {
|
|
2872
|
-
// Gaussian elimination with partial pivoting
|
|
2873
|
-
const n = b.length;
|
|
2874
|
-
// clone
|
|
2875
|
-
const M = new Array(n);
|
|
2876
|
-
for (let i = 0; i < n; i++) {
|
|
2877
|
-
M[i] = A[i].slice();
|
|
2878
|
-
M[i].push(b[i]);
|
|
2879
|
-
}
|
|
2880
|
-
for (let k = 0; k < n; k++) {
|
|
2881
|
-
// find pivot
|
|
2882
|
-
let maxRow = k;
|
|
2883
|
-
let maxVal = Math.abs(M[k][k]);
|
|
2884
|
-
for (let i = k + 1; i < n; i++) {
|
|
2885
|
-
const v = Math.abs(M[i][k]);
|
|
2886
|
-
if (v > maxVal) {
|
|
2887
|
-
maxVal = v;
|
|
2888
|
-
maxRow = i;
|
|
2889
|
-
}
|
|
2890
|
-
}
|
|
2891
|
-
if (maxRow !== k) {
|
|
2892
|
-
const tmp = M[k];
|
|
2893
|
-
M[k] = M[maxRow];
|
|
2894
|
-
M[maxRow] = tmp;
|
|
2895
|
-
}
|
|
2896
|
-
// singular check
|
|
2897
|
-
if (Math.abs(M[k][k]) < 1e-12) {
|
|
2898
|
-
// return least squares fallback zeros
|
|
2899
|
-
const res = new Array(n).fill(0);
|
|
2900
|
-
return res;
|
|
2901
|
-
}
|
|
2902
|
-
// normalize row
|
|
2903
|
-
for (let j = k + 1; j <= n; j++) {
|
|
2904
|
-
M[k][j] = M[k][j] / M[k][k];
|
|
2905
|
-
}
|
|
2906
|
-
M[k][k] = 1;
|
|
2907
|
-
// eliminate
|
|
2908
|
-
for (let i = 0; i < n; i++) {
|
|
2909
|
-
if (i === k) {
|
|
2910
|
-
continue;
|
|
2911
|
-
}
|
|
2912
|
-
const factor = M[i][k];
|
|
2913
|
-
if (factor === 0) {
|
|
2914
|
-
continue;
|
|
2915
|
-
}
|
|
2916
|
-
for (let j = k + 1; j <= n; j++) {
|
|
2917
|
-
M[i][j] -= factor * M[k][j];
|
|
2918
|
-
}
|
|
2919
|
-
M[i][k] = 0;
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
const x = new Array(n);
|
|
2923
|
-
for (let i = 0; i < n; i++) {
|
|
2924
|
-
x[i] = M[i][n];
|
|
2925
|
-
}
|
|
2926
|
-
return x;
|
|
2927
|
-
}
|
|
2928
3065
|
function regressionPolynomial(data, x = d => d.x, y = d => d.y, options = {}) {
|
|
2929
3066
|
var _a, _b;
|
|
2930
|
-
|
|
2931
|
-
if (degree < 0) {
|
|
2932
|
-
degree
|
|
3067
|
+
const degree = (_a = options.degree) !== null && _a !== void 0 ? _a : 0;
|
|
3068
|
+
if (!Number.isInteger(degree) || degree < 0) {
|
|
3069
|
+
throw new RangeError('Polynomial degree must be an nonnegative integer');
|
|
2933
3070
|
}
|
|
2934
3071
|
const alpha = (_b = options.alpha) !== null && _b !== void 0 ? _b : 0.05;
|
|
2935
|
-
const
|
|
2936
|
-
const
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
for (let k = 0; k < sums.length; k++) {
|
|
2940
|
-
sums[k] += xp;
|
|
2941
|
-
xp *= dx;
|
|
2942
|
-
}
|
|
2943
|
-
});
|
|
2944
|
-
// build normal matrix
|
|
2945
|
-
const A = new Array(m);
|
|
2946
|
-
for (let i = 0; i < m; i++) {
|
|
2947
|
-
A[i] = new Array(m).fill(0);
|
|
2948
|
-
for (let j = 0; j < m; j++) {
|
|
2949
|
-
A[i][j] = sums[i + j];
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
const B = new Array(m).fill(0);
|
|
2953
|
-
visitPoints(data, x, y, (dx, dy) => {
|
|
2954
|
-
let xp = 1;
|
|
2955
|
-
for (let k = 0; k < m; k++) {
|
|
2956
|
-
B[k] += dy * xp;
|
|
2957
|
-
xp *= dx;
|
|
2958
|
-
}
|
|
2959
|
-
});
|
|
2960
|
-
const coef = solveLinearSystem(A, B);
|
|
2961
|
-
const predict = (xx) => {
|
|
2962
|
-
let xp = 1;
|
|
2963
|
-
let v = 0;
|
|
2964
|
-
for (let k = 0; k < coef.length; k++) {
|
|
2965
|
-
v += coef[k] * xp;
|
|
2966
|
-
xp *= xx;
|
|
2967
|
-
}
|
|
2968
|
-
return v;
|
|
2969
|
-
};
|
|
3072
|
+
const points = regressionPoints(data, x, y);
|
|
3073
|
+
const model = fitRegression(points, degree);
|
|
3074
|
+
const coef = regressionCoefficients(model);
|
|
3075
|
+
const predict = (xx) => regressionPrediction(model, xx);
|
|
2970
3076
|
return {
|
|
2971
3077
|
degree,
|
|
3078
|
+
rank: model.rank,
|
|
2972
3079
|
coef,
|
|
2973
3080
|
predict,
|
|
2974
3081
|
rSquared: rSquared(data, x, y, (() => {
|
|
@@ -2982,39 +3089,13 @@
|
|
|
2982
3089
|
return cnt === 0 ? 0 : sum / cnt;
|
|
2983
3090
|
})(), predict),
|
|
2984
3091
|
evaluateGrid(N) {
|
|
2985
|
-
const out = [];
|
|
2986
|
-
if (N <= 0) {
|
|
2987
|
-
return out;
|
|
2988
|
-
}
|
|
2989
|
-
// compute range
|
|
2990
3092
|
let min = Infinity;
|
|
2991
3093
|
let max = -Infinity;
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
min = dx;
|
|
2996
|
-
}
|
|
2997
|
-
if (dx > max) {
|
|
2998
|
-
max = dx;
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3094
|
+
points.forEach(p => {
|
|
3095
|
+
min = Math.min(min, p.x);
|
|
3096
|
+
max = Math.max(max, p.x);
|
|
3001
3097
|
});
|
|
3002
|
-
|
|
3003
|
-
return out;
|
|
3004
|
-
}
|
|
3005
|
-
if (min === max) {
|
|
3006
|
-
const v = predict(min);
|
|
3007
|
-
for (let i = 0; i < N; i++) {
|
|
3008
|
-
out.push({ x: min, y: v });
|
|
3009
|
-
}
|
|
3010
|
-
return out;
|
|
3011
|
-
}
|
|
3012
|
-
const step = (max - min) / (N - 1);
|
|
3013
|
-
for (let i = 0; i < N; i++) {
|
|
3014
|
-
const px = i === N - 1 ? max : min + step * i;
|
|
3015
|
-
out.push({ x: px, y: predict(px) });
|
|
3016
|
-
}
|
|
3017
|
-
return out;
|
|
3098
|
+
return regressionGrid(min, max, N, predict);
|
|
3018
3099
|
},
|
|
3019
3100
|
confidenceInterval(N = 50) {
|
|
3020
3101
|
const out = [];
|
|
@@ -3496,6 +3577,55 @@
|
|
|
3496
3577
|
return estimates;
|
|
3497
3578
|
}
|
|
3498
3579
|
|
|
3580
|
+
/** 成对 percentile bootstrap;每次重采样仅拟合一次,固定原始坐标基底。 */
|
|
3581
|
+
function regressionBootstrap(data, options) {
|
|
3582
|
+
var _a;
|
|
3583
|
+
const degree = options.method === 'linear' ? 1 : (_a = options.degree) !== null && _a !== void 0 ? _a : NaN;
|
|
3584
|
+
if ((options.method !== 'linear' && options.method !== 'polynomial') ||
|
|
3585
|
+
!Number.isInteger(degree) ||
|
|
3586
|
+
degree < 1 ||
|
|
3587
|
+
!data.length ||
|
|
3588
|
+
data.some(p => !Number.isFinite(p.x) || !Number.isFinite(p.y)) ||
|
|
3589
|
+
options.grid.some(x => !Number.isFinite(x)) ||
|
|
3590
|
+
!(options.level > 0 && options.level < 1) ||
|
|
3591
|
+
!Number.isInteger(options.resamples) ||
|
|
3592
|
+
options.resamples < 1 ||
|
|
3593
|
+
!Number.isInteger(options.seed) ||
|
|
3594
|
+
options.seed < 0 ||
|
|
3595
|
+
options.seed > 0xffffffff) {
|
|
3596
|
+
throw new RangeError('Invalid paired regression bootstrap inputs');
|
|
3597
|
+
}
|
|
3598
|
+
if (data.length <= degree + 1) {
|
|
3599
|
+
throw new RangeError('Regression bootstrap requires positive residual degrees of freedom');
|
|
3600
|
+
}
|
|
3601
|
+
const sorted = data.slice().sort((a, b) => a.x - b.x || a.y - b.y);
|
|
3602
|
+
const basis = regressionBasis(sorted);
|
|
3603
|
+
if (fitRegression(sorted, degree, basis).rank < degree + 1) {
|
|
3604
|
+
throw new RangeError('Original regression design is rank deficient');
|
|
3605
|
+
}
|
|
3606
|
+
const random = randomLCG(options.seed);
|
|
3607
|
+
const sample = new Array(sorted.length);
|
|
3608
|
+
const values = options.grid.map(() => new Array(options.resamples));
|
|
3609
|
+
for (let b = 0; b < options.resamples; b++) {
|
|
3610
|
+
for (let i = 0; i < sample.length; i++) {
|
|
3611
|
+
sample[i] = sorted[Math.floor(random() * sorted.length)];
|
|
3612
|
+
}
|
|
3613
|
+
const model = fitRegression(sample, degree, basis);
|
|
3614
|
+
for (let j = 0; j < options.grid.length; j++) {
|
|
3615
|
+
const value = regressionPrediction(model, options.grid[j]);
|
|
3616
|
+
if (!Number.isFinite(value)) {
|
|
3617
|
+
throw new RangeError('Regression bootstrap prediction is not finite');
|
|
3618
|
+
}
|
|
3619
|
+
values[j][b] = value;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
const tail = (1 - options.level) / 2;
|
|
3623
|
+
return options.grid.map((x, j) => {
|
|
3624
|
+
values[j].sort((a, b) => a - b);
|
|
3625
|
+
return { x, lower: quantileSorted(values[j], tail), upper: quantileSorted(values[j], 1 - tail) };
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3499
3629
|
class HashValue {
|
|
3500
3630
|
}
|
|
3501
3631
|
// 基于 array 和 object的 HashTable
|
|
@@ -8975,6 +9105,7 @@
|
|
|
8975
9105
|
exports.randomLCG = randomLCG;
|
|
8976
9106
|
exports.range = range;
|
|
8977
9107
|
exports.rectInsideAnotherRect = rectInsideAnotherRect;
|
|
9108
|
+
exports.regressionBootstrap = regressionBootstrap;
|
|
8978
9109
|
exports.regressionLinear = regressionLinear;
|
|
8979
9110
|
exports.regressionLogistic = regressionLogistic;
|
|
8980
9111
|
exports.regressionLowess = regressionLowess;
|