diff --git a/common/changes/@visactor/vdataset/fix-box_plot_2025-11-18-07-47.json b/common/changes/@visactor/vdataset/fix-box_plot_2025-11-18-07-47.json new file mode 100644 index 0000000..bc0a9b7 --- /dev/null +++ b/common/changes/@visactor/vdataset/fix-box_plot_2025-11-18-07-47.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vdataset", + "comment": "set boxplot whisker as actual bound value(Q1 - N*IQR) or fixed value && add alpha option of regression function", + "type": "none" + } + ], + "packageName": "@visactor/vdataset" +} \ No newline at end of file diff --git a/packages/vdataset/__tests__/boxplot.test.ts b/packages/vdataset/__tests__/boxplot.test.ts index eba3ed3..32d721e 100644 --- a/packages/vdataset/__tests__/boxplot.test.ts +++ b/packages/vdataset/__tests__/boxplot.test.ts @@ -80,4 +80,74 @@ describe('boxplot transform', () => { expect(ids).toContain('a'); expect(ids).toContain('b'); }); + + test('whisker should be fixed not ranged by latset value', () => { + const data = [ + { + v: -1 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 3 + }, + { + v: 4 + }, + { + v: 4 + }, + { + v: 7 + }, + { + v: 8 + }, + { + v: 9 + }, + { + v: 10 + } + ]; + const out: any = boxplot(data, { field: 'v' }); + expect(out.length).toBe(1); + const o = out[0]; + expect(o.lowerWhisker).toBe(1.5); + expect(o.upperWhisker).toBe(5.5); + }); }); diff --git a/packages/vdataset/src/transform/boxplot.ts b/packages/vdataset/src/transform/boxplot.ts index 5d3c62e..b088543 100644 --- a/packages/vdataset/src/transform/boxplot.ts +++ b/packages/vdataset/src/transform/boxplot.ts @@ -146,25 +146,9 @@ export const boxplot: Transform = (data: Array, options?: IBoxplotOption const lowerBound = isArray(whiskers) ? quantileSorted(sorted, whiskers[0]) : q1 - whiskers * iqr; const upperBound = isArray(whiskers) ? quantileSorted(sorted, whiskers[1]) : q3 + whiskers * iqr; - // whiskers are the most extreme data points inside the bounds - let lowerWhisker = dataMin; - for (let i = 0; i < sorted.length; i++) { - if (sorted[i] >= lowerBound) { - lowerWhisker = sorted[i]; - break; - } - } - let upperWhisker = dataMax; - for (let i = sorted.length - 1; i >= 0; i--) { - if (sorted[i] <= upperBound) { - upperWhisker = sorted[i]; - break; - } - } - const outliers: number[] = []; for (let i = 0; i < sorted.length; i++) { - if (sorted[i] < lowerWhisker || sorted[i] > upperWhisker) { + if (sorted[i] < lowerBound || sorted[i] > upperBound) { outliers.push(sorted[i]); } } @@ -191,8 +175,8 @@ export const boxplot: Transform = (data: Array, options?: IBoxplotOption obj[iqrName] = iqr; obj[minName] = dataMin; obj[maxName] = dataMax; - obj[lowerWhiskerName] = lowerWhisker; - obj[upperWhiskerName] = upperWhisker; + obj[lowerWhiskerName] = lowerBound; + obj[upperWhiskerName] = upperBound; obj[outliersName] = outliers; if (includeValues) { obj[valuesName] = rawValues.get(key) || []; diff --git a/packages/vutils/src/common/regression-linear.ts b/packages/vutils/src/common/regression-linear.ts index 64d3e35..44cf8b4 100644 --- a/packages/vutils/src/common/regression-linear.ts +++ b/packages/vutils/src/common/regression-linear.ts @@ -55,7 +55,15 @@ export function rSquared( return sst === 0 ? 0 : 1 - ssr / sst; } -export function regressionLinear(data: any[], x: (d: any) => number = d => d.x, y: (d: any) => number = d => d.y) { +export function regressionLinear( + data: any[], + x: (d: any) => number = d => d.x, + y: (d: any) => number = d => d.y, + options?: { + alpha?: number; + } +) { + const alpha = options?.alpha ?? 0.05; // accumulate online means (sufficient statistics) let n = 0; let meanX = 0; @@ -95,7 +103,7 @@ export function regressionLinear(data: any[], x: (d: any) => number = d => d.x, return out; } - function confidenceInterval(N: number = 50, alpha: number = 0.05) { + function confidenceInterval(N: number = 50) { const out: { x: number; mean: number; lower: number; upper: number; predLower: number; predUpper: number }[] = []; if (comps.n === 0 || N <= 0) { return out; diff --git a/packages/vutils/src/common/regression-logistic.ts b/packages/vutils/src/common/regression-logistic.ts index 04aae8e..be6eb77 100644 --- a/packages/vutils/src/common/regression-logistic.ts +++ b/packages/vutils/src/common/regression-logistic.ts @@ -11,10 +11,11 @@ export function regressionLogistic( data: any[], x: (d: any) => number = d => d.x, y: (d: any) => number = d => d.y, - options?: { maxIteration?: number; tol?: number } + options?: { maxIteration?: number; tol?: number; alpha?: number } ) { const maxIter = options?.maxIteration ?? 25; const tol = options?.tol ?? 1e-6; + const alpha = options?.alpha ?? 0.05; // build arrays const xs: number[] = []; const ys: number[] = []; @@ -120,7 +121,7 @@ export function regressionLogistic( return out; } - function confidenceInterval(N: number = 50, alpha: number = 0.05) { + function confidenceInterval(N: number = 50) { const out: { x: number; mean: number; lower: number; upper: number; predLower: number; predUpper: number }[] = []; if (N <= 0) { diff --git a/packages/vutils/src/common/regression-lowess.ts b/packages/vutils/src/common/regression-lowess.ts index 60cde62..fadb22d 100644 --- a/packages/vutils/src/common/regression-lowess.ts +++ b/packages/vutils/src/common/regression-lowess.ts @@ -21,10 +21,11 @@ export function regressionLowess( data: any[], x: (d: any) => number = d => d.x, y: (d: any) => number = d => d.y, - options: { span?: number; degree?: 1 | 0; iterations?: number } = {} + options: { span?: number; degree?: 1 | 0; iterations?: number; alpha?: number } = {} ) { const span = options.span || 0.3; const degree = options.degree === 0 ? 0 : 1; + const alpha = options?.alpha ?? 0.05; const iterations = options.iterations == null ? 2 : options.iterations; const ptsX: number[] = []; @@ -170,7 +171,7 @@ export function regressionLowess( return out; } - function confidenceInterval(N: number = 50, alpha: number = 0.05) { + function confidenceInterval(N: number = 50) { const out: { x: number; mean: number; lower: number; upper: number; predLower: number; predUpper: number }[] = []; if (N <= 0) { diff --git a/packages/vutils/src/common/regression-polynomial.ts b/packages/vutils/src/common/regression-polynomial.ts index 83cd5a6..39783a5 100644 --- a/packages/vutils/src/common/regression-polynomial.ts +++ b/packages/vutils/src/common/regression-polynomial.ts @@ -69,12 +69,13 @@ export function regressionPolynomial( data: any[], x: (d: any) => number = d => d.x, y: (d: any) => number = d => d.y, - options: { degree?: number } = {} + options: { degree?: number; alpah?: number } = {} ) { let degree = options.degree ?? 0; if (degree < 0) { degree = 0; } + const alpha = options.alpah ?? 0.5; const m = degree + 1; const sums: number[] = new Array(2 * degree + 1).fill(0); @@ -171,7 +172,7 @@ export function regressionPolynomial( } return out; }, - confidenceInterval(N: number = 50, alpha: number = 0.05) { + confidenceInterval(N: number = 50) { const out: { x: number; mean: number; lower: number; upper: number; predLower: number; predUpper: number }[] = []; if (N <= 0) {