Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/xy/__tests__/xyMedianYAtXs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,36 @@ test('window at edges is truncated', () => {
expect(result.y).toStrictEqual(new Float64Array([5, 5]));
});

test('symmetric window shrinks at edges', () => {
const data = {
x: [1, 2, 3, 4, 5],
y: [10, 1, 5, 3, 8],
};

const result = xyMedianYAtXs(data, [1, 2, 3, 4, 5], {
windowSize: 5,
symmetric: true,
});

// index 0: window [10] -> 10
// index 1: window [10,1,5] -> 5
// index 2: window [10,1,5,3,8] -> 5
// index 3: window [5,3,8] -> 5
// index 4: window [8] -> 8
expect(result.y).toStrictEqual(new Float64Array([10, 5, 5, 5, 8]));
});

test('symmetric window keeps full size away from edges', () => {
const data = {
x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
y: [2, 8, 3, 100, 5, 6, 1, 9, 4, 7],
};

const result = xyMedianYAtXs(data, [3, 7], { symmetric: true });

expect(result.y).toStrictEqual(new Float64Array([5, 5]));
});

test('does not mutate original Float64Array y data', () => {
const y = new Float64Array([10, 1, 5, 3, 8]);
const yOriginal = new Float64Array(y);
Expand Down
20 changes: 17 additions & 3 deletions src/xy/xyMedianYAtXs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { xMedian } from '../x/xMedian.ts';
export interface XYMedianYAtXsOptions {
/** Number of points in the sliding window. Must be odd. Defaults to `5`. */
windowSize?: number;
/**
* If true, the window shrinks on both sides so that it stays centered on the
* closest index when points are missing on one side (near an edge).
* @default false
*/
symmetric?: boolean;
}

/**
Expand All @@ -22,16 +28,24 @@ export function xyMedianYAtXs(
xValues: NumberArray,
options: XYMedianYAtXsOptions = {},
): DataXY {
const { windowSize = 5 } = options;
const { windowSize = 5, symmetric = false } = options;
const { x, y } = data;

const halfWindow = Math.floor(windowSize / 2);
const lastIndex = y.length - 1;
const result = new Float64Array(xValues.length);

for (let i = 0; i < xValues.length; i++) {
const centerIndex = xFindClosestIndex(x, xValues[i]);
const fromIndex = Math.max(0, centerIndex - halfWindow);
const toIndex = Math.min(y.length, centerIndex + halfWindow + 1);
let currentHalfWindow = halfWindow;
if (symmetric) {
if (centerIndex < currentHalfWindow) currentHalfWindow = centerIndex;
if (lastIndex - centerIndex < currentHalfWindow) {
currentHalfWindow = lastIndex - centerIndex;
}
}
const fromIndex = Math.max(0, centerIndex - currentHalfWindow);
const toIndex = Math.min(y.length, centerIndex + currentHalfWindow + 1);
result[i] = xMedian(y, { exact: false, fromIndex, toIndex });
}

Expand Down
Loading