FieldReducers: Fix median (#98184)

Co-authored-by: Kristina Durivage <kristina.durivage@grafana.com>
Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
This commit is contained in:
jackyin
2025-01-15 05:38:34 +08:00
committed by GitHub
parent c5111b8132
commit 69da0bb22c
2 changed files with 55 additions and 1 deletions

View File

@@ -254,6 +254,29 @@ describe('Stats Calculators', () => {
expect(reduce(someNulls, ReducerID.count)).toEqual(4);
});
it('median should ignoreNulls by default', () => {
const someNulls = createField('y', [3, null, 2, 1, 4]);
expect(reduce(someNulls, ReducerID.median)).toEqual(2.5);
});
it('median should use fieldConfig nullValueMode.Ignore and not count nulls', () => {
const someNulls = createField('y', [3, null, 2, 1, 4]);
someNulls.config.nullValueMode = NullValueMode.Ignore;
expect(reduce(someNulls, ReducerID.median)).toEqual(2.5);
});
it('median should use fieldConfig nullValueMode.Null and count nulls', () => {
const someNulls = createField('y', [3, null, 2, 1, 4]);
someNulls.config.nullValueMode = NullValueMode.Null;
expect(reduce(someNulls, ReducerID.median)).toEqual(2);
});
it('median should use fieldConfig nullValueMode.AsZero and count nulls as zero', () => {
const someNulls = createField('y', [3, null, 2, 1, 4]);
someNulls.config.nullValueMode = NullValueMode.AsZero;
expect(reduce(someNulls, ReducerID.median)).toEqual(2);
});
it('can reduce to percentiles', () => {
// This `Array.from` will build an array of elements from 1 to 99
const percentiles = [...Array.from({ length: 99 }, (_, i) => i + 1)];

View File

@@ -283,7 +283,8 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
id: ReducerID.median,
name: 'Median',
description: 'Median Value',
standard: true,
standard: false,
reduce: calculateMedian,
aliasIds: ['median'],
preservesUnits: true,
},
@@ -584,6 +585,7 @@ export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero:
if (isNumber(calcs.firstNotNull) && isNumber(calcs.diff)) {
calcs.diffperc = (calcs.diff / calcs.firstNotNull) * 100;
}
return calcs;
}
@@ -703,3 +705,32 @@ function calculatePercentile(field: Field, percentile: number, ignoreNulls: bool
const index = Math.round((sorted.length - 1) * percentile);
return sorted[index];
}
function calculateMedian(field: Field<number>, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs {
const numbers: number[] = [];
for (let i = 0; i < field.values.length; i++) {
let currentValue = field.values[i];
if (currentValue == null) {
if (ignoreNulls) {
continue;
}
if (nullAsZero) {
currentValue = 0;
}
}
numbers.push(currentValue);
}
numbers.sort((a, b) => a - b);
const mid = Math.floor(numbers.length / 2);
if (numbers.length % 2 === 0) {
return { median: (numbers[mid - 1] + numbers[mid]) / 2 };
} else {
return { median: numbers[mid] };
}
}