Aggregated Custom Expression Functions

Summary

  • Aggregated Custom Expression Functions work on an array of data and return a single value
  • This allows users to create Functions that operate on multiple rows or an entire column

Developers are able to provide Custom Aggregation Expression Functions to AdapTableQL.

These are functions which operate against an array of data and return a single value.

Note

The array of data provided to the Expression is often a column name, but this does not have to be the case

Custom Expression Functions are defined in the customAggregatedFunctions property in Expression Options.

customAggregatedFunctions

Record<string, AggregatedExpressionFunction>
Custom Aggregated Expression Functions available in AdapTableQL
Deep Dive

Anatomy of an Custom Aggregated Expression Function

Developer Guide

Defining a Custom Aggregated Expression Function

In this Guide we create 4 Custom Aggregated Expression Functions:

  • PRODUCT - a straightforwrd function that returns the product of a series of numbers
  • SUMSQ - a straightforwrd function that Mimics SUMSQL in Excel, returning sum of squares of numbers
  • RANK - returns the rank of a number in a list - leverages more advanced properties available in the Function definition
  • LARGE - returns the Kth biggest number in a list (where K is an input) - also leverages advanced Function definition properties
expressionOptions: {
  customAggregatedFunctions: {
    PRODUCT: {
      initialValue: 1,
      reducer: (context: AggregatedExpressionReducerContext) => {
        return context.accumulator * context.currentValue;
      },
      description: 'Returns product of an array of values',
      signatures: ['PRODUCT(numericVals: number[])'],
      examples: ['PRODUCT([price])'],
    },
   SUMSQ: {
      initialValue: 1,
      reducer: (context: AggregatedExpressionReducerContext) => {
        return context.accumulator + context.currentValue * 2;
      },
      description:
        'Mimics SUMSQL in Excel - returning sum of squares of arguments',
      signatures: ['SUMSQ(numericVals: number[])'],
      examples: ['SUMSQ([price])'],
    },
    RANK: {
      initialValue: [],
      reducer: (context: AggregatedExpressionReducerContext) => {
        if (context.currentValue != undefined) {
          context.accumulator.push(context.currentValue);
        }
        return context.accumulator;
      },
      processAggregatedValue: (
        context: AggregatedExpressionProcessAggValueContext
      ) => {
        const sortedDataCollection = context.aggregatedValue.sort(
          (firstValue: any, secondValue: any) => firstValue - secondValue
        );
        const rankMap = new Map<any, number>();
        sortedDataCollection.forEach((value: any, index: any) => {
          rankMap.set(value, index + 1);
        });
        return rankMap;
      },
      prepareRowValue: (
        context: AggregatedExpressionPrepareRowValueContext
      ) => {
        const aggregatedColumnId = context.aggColumnId;
        const rowValue = context.adaptableApi.gridApi.getRawValueFromRowNode(
          context.rowNode,
          aggregatedColumnId
        );
        return context.aggregatedValue.get(rowValue) as number;
      },
      description: 'Mimics RANK in Excel; returns rank of a number in given list',
      signatures: ['RANK([amount])'],
      examples: ['RANK([amount])'],    
    },
    LARGE: {
      initialValue: [],
      reducer: (context: AggregatedExpressionReducerContext) => {
        if (context.currentValue != undefined) {
          context.accumulator.push(context.currentValue);
          context.accumulator.sort((a: any, b: any) => b - a);
        }
        return context.accumulator;
      },
      processAggregatedValue: (
        context: AggregatedExpressionProcessAggValueContext
      ) => {
        const k = context.args[0];
        if (k == undefined || k <= 0 || k > context.rowNodes.length) {
          console.log('Invalid K value for LARGE function: ', k);
          return;
        }
        return context.aggregatedValue[k - 1];
      },
      description: 'Mimics LARGE in Excel; returns Kth largest number in list',
      signatures: ['LARGE([price], K: number)'],
      examples: ['LARGE([price], 4)'],
    },
}
1
Add function to Expression Options and specify a name

Add the Custom Expression Function definition to the customAggregatedFunctions section in Expression Options

Each entry is of type Record with the key and value as follows:

  • key is the custom Function's name (by convention, it is in CAPITAL LETTERS - to make it more readable when used in an Expression)
  • value is the Function's definition (which is of type: AggregatedExpressionFunction)
2
Provide the Initial Value (mandatory)

Use the initialValue property to set the start value for the aggregation

3
Provide the reducer function (mandatory)

This function will be called for each value (typically a row) in the Column Data

The context object provided to the function includes:

  • accumulator - value returned by the previous call to the reducer function, or the initialValue if this is the first call. You return the new accumulator value from this function
  • currentValue - current value being processed in Column Data
  • index - index of current value in the Column Data
4
Provide a processAggregatedValue function

Called after the reducer has processed all values / rows

Can be used to change result of reducer based on the values array (e.g. average = aggregated value / count)

The function context includes:

  • aggregatedValue - result of the reducer
  • rowNodes - array of row nodes (if aggregation was grouped, this will be array of row nodes for the group)
5
Provide a prepareRowValue function

Called for each row after the reducer and processAggregatedValue functions.

Can be used when each row has a different aggregated value (e.g. percentage = row value / aggregated value)

The function context inlucdes the current row node.

6
Provide Help for run-time Users

There are 3 properties that should be provided in order to help Users accessing the Function in the Expression Editor:

  • description: Explains what the Function does
  • signatures: Shows how the Function should be called
  • examples: Small examples of Function in action
AdapTableQL: Custom Aggregation Functions
Fork
  • This example creates the 4 Custom Aggregation Expression Functions in the Developer Steps above, and references them in various Calculated Columns:
    • PRODUCT: multiplies set of values - used in Total Rating (product of Rating Column) & Ccy Rating (product of Rating grouped by Currency)
    • SUMSQ: return sum of squares of values - used in Sum Sq Rating (operates on Rating Column)
    • LARGE: returns the K-th largest value in a data set - used in 2nd largest amount (2nd largest value in Amount Column)
    • RANK: returns the rank of a number in a list of numbers - used in Amt Rank (ranks the Amount Column)
  • Note: both LARGE and RANK require data sorting; however, RANK outperforms LARGE by leveraging Adaptable's provided hooks to sort the data only once and then using a map for quick lookups, resulting in a significant performance boost

Expand to see the Custom Aggregation Functions provided

Try It Out
  • Open the Expression Editor for the Calculated Columns to see the Expressions and the Aggregated Expression Functions
  • Sort the Amount column to and note how the 2nd Large Amt and Ranking of Amount are both correct