Reducers: Add in basic Percentile Support (#85554)

pull/85696/head
Tim Levett 1 year ago committed by GitHub
parent 5f0ca1429b
commit c47be316eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 14
      packages/grafana-data/src/transformations/fieldReducer.test.ts
  3. 135
      packages/grafana-data/src/transformations/fieldReducer.ts

@ -177,6 +177,9 @@ exports[`better eslint`] = {
"packages/grafana-data/src/themes/createColors.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-data/src/transformations/fieldReducer.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
"packages/grafana-data/src/transformations/matchers/valueMatchers/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]

@ -251,4 +251,18 @@ describe('Stats Calculators', () => {
expect(reduce(someNulls, ReducerID.count)).toEqual(4);
});
for (let i = 1; i < 100; i++) {
it(`can reduce the ${i}th percentile`, () => {
const preciseStats = reduceField({
field: createField(
'x',
Array.from({ length: 101 }, (_, index) => index)
),
reducers: [(ReducerID as Record<string, ReducerID>)[`p${i}`]],
});
expect(preciseStats[`p${i}`]).toEqual(i);
});
}
});

@ -28,6 +28,105 @@ export enum ReducerID {
allIsNull = 'allIsNull',
allValues = 'allValues',
uniqueValues = 'uniqueValues',
p1 = 'p1',
p2 = 'p2',
p3 = 'p3',
p4 = 'p4',
p5 = 'p5',
p6 = 'p6',
p7 = 'p7',
p8 = 'p8',
p9 = 'p9',
p10 = 'p10',
p11 = 'p11',
p12 = 'p12',
p13 = 'p13',
p14 = 'p14',
p15 = 'p15',
p16 = 'p16',
p17 = 'p17',
p18 = 'p18',
p19 = 'p19',
p20 = 'p20',
p21 = 'p21',
p22 = 'p22',
p23 = 'p23',
p24 = 'p24',
p25 = 'p25',
p26 = 'p26',
p27 = 'p27',
p28 = 'p28',
p29 = 'p29',
p30 = 'p30',
p31 = 'p31',
p32 = 'p32',
p33 = 'p33',
p34 = 'p34',
p35 = 'p35',
p36 = 'p36',
p37 = 'p37',
p38 = 'p38',
p39 = 'p39',
p40 = 'p40',
p41 = 'p41',
p42 = 'p42',
p43 = 'p43',
p44 = 'p44',
p45 = 'p45',
p46 = 'p46',
p47 = 'p47',
p48 = 'p48',
p49 = 'p49',
p50 = 'p50',
p51 = 'p51',
p52 = 'p52',
p53 = 'p53',
p54 = 'p54',
p55 = 'p55',
p56 = 'p56',
p57 = 'p57',
p58 = 'p58',
p59 = 'p59',
p60 = 'p60',
p61 = 'p61',
p62 = 'p62',
p63 = 'p63',
p64 = 'p64',
p65 = 'p65',
p66 = 'p66',
p67 = 'p67',
p68 = 'p68',
p69 = 'p69',
p70 = 'p70',
p71 = 'p71',
p72 = 'p72',
p73 = 'p73',
p74 = 'p74',
p75 = 'p75',
p76 = 'p76',
p77 = 'p77',
p78 = 'p78',
p79 = 'p79',
p80 = 'p80',
p81 = 'p81',
p82 = 'p82',
p83 = 'p83',
p84 = 'p84',
p85 = 'p85',
p86 = 'p86',
p87 = 'p87',
p88 = 'p88',
p89 = 'p89',
p90 = 'p90',
p91 = 'p91',
p92 = 'p92',
p93 = 'p93',
p94 = 'p94',
p95 = 'p95',
p96 = 'p96',
p97 = 'p97',
p98 = 'p98',
p99 = 'p99',
}
export function isReducerID(id: string): id is ReducerID {
@ -305,6 +404,26 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
},
]);
for (let i = 1; i < 100; i++) {
const percentile = i / 100;
const id = `p${i}` as ReducerID;
const nth = (n: number) =>
n > 3 && n < 21 ? 'th' : n % 10 === 1 ? 'st' : n % 10 === 2 ? 'nd' : n % 10 === 3 ? 'rd' : 'th';
const name = `${i}${nth(i)} percentile`;
const description = `${i}${nth(i)} percentile value`;
fieldReducers.register({
id: id,
name: name,
description: description,
standard: false,
reduce: (field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs => {
return { [id]: calculatePercentile(field, percentile, ignoreNulls, nullAsZero) };
},
preservesUnits: true,
});
}
// Used for test cases
export const defaultCalcs: FieldCalcs = {
sum: 0,
@ -325,7 +444,6 @@ export const defaultCalcs: FieldCalcs = {
delta: 0,
step: Number.MAX_VALUE,
diffperc: 0,
// Just used for calculations -- not exposed as a stat
previousDeltaUp: true,
};
@ -553,3 +671,18 @@ function calculateDistinctCount(field: Field, ignoreNulls: boolean, nullAsZero:
}
return { distinctCount: distinct.size };
}
function calculatePercentile(field: Field, percentile: number, ignoreNulls: boolean, nullAsZero: boolean): number {
let data = field.values;
if (ignoreNulls) {
data = data.filter((value) => value !== null);
}
if (nullAsZero) {
data = data.map((value) => (value === null ? 0 : value));
}
const sorted = data.slice().sort((a, b) => a - b);
const index = Math.round((sorted.length - 1) * percentile);
return sorted[index];
}

Loading…
Cancel
Save