diff --git a/docs/sources/panels/calculation-types.md b/docs/sources/panels/calculation-types.md index 342c0cc9b1d..8ddf35c788f 100644 --- a/docs/sources/panels/calculation-types.md +++ b/docs/sources/panels/calculation-types.md @@ -10,7 +10,7 @@ weight: 1100 # Reference: Calculation types -Refer to the following list of calculations you can perform in Grafana. You can find these calculations in the **Transform** tab and in the bar gauge, gauge, and stat visualizations. +The following table contains a list of calculations you can perform in Grafana. You can find these calculations in the **Transform** tab and in the bar gauge, gauge, and stat visualizations. | Calculation | Description | | :----------------- | :-------------------------------------------------------- | @@ -25,6 +25,8 @@ Refer to the following list of calculations you can perform in Grafana. You can | First (not null) | First, not null value in a field | | Max | Maximum value of a field | | Mean | Mean value of all values in a field | +| Variance | Variance of all values in a field | +| StdDev | Standard deviation of all values in a field | | Min | Minimum value of a field | | Min (above zero) | Minimum, positive value of a field | | Range | Difference between maximum and minimum values of a field | diff --git a/packages/grafana-data/src/transformations/fieldReducer.test.ts b/packages/grafana-data/src/transformations/fieldReducer.test.ts index 34de7c94f90..34c90c57a64 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.test.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.test.ts @@ -80,11 +80,13 @@ describe('Stats Calculators', () => { it('should get non standard stats', () => { const stats = reduceField({ field: basicTable.fields[0], - reducers: [ReducerID.distinctCount, ReducerID.changeCount], + reducers: [ReducerID.distinctCount, ReducerID.changeCount, ReducerID.variance, ReducerID.stdDev], }); expect(stats.distinctCount).toEqual(2); expect(stats.changeCount).toEqual(1); + expect(stats.variance).toEqual(25); + expect(stats.stdDev).toEqual(5); }); it('should calculate step', () => { diff --git a/packages/grafana-data/src/transformations/fieldReducer.ts b/packages/grafana-data/src/transformations/fieldReducer.ts index c593ac47346..59691aaf81c 100644 --- a/packages/grafana-data/src/transformations/fieldReducer.ts +++ b/packages/grafana-data/src/transformations/fieldReducer.ts @@ -10,6 +10,8 @@ export enum ReducerID { min = 'min', logmin = 'logmin', mean = 'mean', + variance = 'variance', + stdDev = 'stdDev', last = 'last', first = 'first', count = 'count', @@ -152,6 +154,20 @@ export const fieldReducers = new Registry(() => [ { id: ReducerID.min, name: 'Min', description: 'Minimum Value', standard: true }, { id: ReducerID.max, name: 'Max', description: 'Maximum Value', standard: true }, { id: ReducerID.mean, name: 'Mean', description: 'Average Value', standard: true, aliasIds: ['avg'] }, + { + id: ReducerID.variance, + name: 'Variance', + description: 'Variance of all values in a field', + standard: false, + reduce: calculateStdDev, + }, + { + id: ReducerID.stdDev, + name: 'StdDev', + description: 'Standard deviation of all values in a field', + standard: false, + reduce: calculateStdDev, + }, { id: ReducerID.sum, name: 'Total', @@ -419,6 +435,33 @@ function calculateLastNotNull(field: Field, ignoreNulls: boolean, nullAsZero: bo return { lastNotNull: null }; } +/** Calculates standard deviation and variance */ +function calculateStdDev(field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs { + // Only support number fields + if (!(field.type === FieldType.number || field.type === FieldType.time)) { + return { variance: 0, stdDev: 0 }; + } + + let squareSum = 0; + let runningMean = 0; + let runningNonNullCount = 0; + const data = field.values; + for (let i = 0; i < data.length; i++) { + const currentValue = data.get(i); + if (currentValue != null) { + runningNonNullCount++; + let _oldMean = runningMean; + runningMean += (currentValue - _oldMean) / runningNonNullCount; + squareSum += (currentValue - _oldMean) * (currentValue - runningMean); + } + } + if (runningNonNullCount > 0) { + const variance = squareSum / runningNonNullCount; + return { variance, stdDev: Math.sqrt(variance) }; + } + return { variance: 0, stdDev: 0 }; +} + function calculateChangeCount(field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs { const data = field.values; let count = 0;