Skip to content

Custom Reducers

paulwilcox edited this page Jul 11, 2021 · 3 revisions

Home
Dataset


To build a custom reducer, create a function that, accepts a 'rowShaper' function as its first or only parameter, and returns an 'aggregating' function as it's output.

The 'rowShaper' is what the user will pass to the reducer. It expects a dataset row as input, and for output reshapes that row in a way that the 'aggregating' function can use.

The 'aggregating' function contains the core logic to aggregate over a dataset. It expects a dataset as input and will return an aggregated value (usually a scalar, but not always). Before aggregating over a row, it executes 'rowShaper' on the row so that it has a consistent structure to work with regardless of the original row structure.

Syntactically, it will look something like this:

let customReducer = rowShaper => 
    data => {  
        let aggregation = someInitialValue; // probably a scalar but not always
        for(let row of data) {
            let shaped = rowShaper(row);
            ... // code that works 'shaped' into 'aggregation';
        }
        return aggregation;
    }  

See the code for cor for an example of a reducer that can accept a second parameter from the user. In that case it permits the user to pass options.

Setup

Below, the fluent-data library is loaded into the variable $$ and a dataset is constructed with an array argument. This datasets is used by the examples in this page.

let $$ = require('./dist/fluent-data.server.js');

let purchases = $$([
    { customerId: 'b', books: 4, time: 16.68, price: 560, rating: 73 },
    { customerId: 'a', books: 1, time: 11.50, price:  80, rating: 95 },
    { customerId: 'a', books: 1, time: 12.03, price: 150, rating: 92 },
    { customerId: 'b', books: 2, time: 14.88, price: 220, rating: 88 },
    { customerId: 'a', books: 3, time: 13.75, price: 340, rating: 90 },
    { customerId: 'b', books: 4, time: 18.11, price: 330, rating: 66 },
    { customerId: 'a', books: 5, time: 21.09, price: 401, rating: 54 },
    { customerId: 'b', books: 5, time: 23.77, price: 589, rating: 31 }
]);

Simple Custom Reducers

The following is an example of a reducer that produces the sum of two columns:

let multiColSum = rowShaper => 
    data => {
        let s = 0;
        for(let row of data) {
            let shaped = rowShaper(row);
            s += shaped[0] + shaped[1]; // 'rowShaper()' must output a two element array
        }
        return s;
    }

purchases
    .reduce({
        mcs: multiColSum(p => [p.time, p.rating])
    })
    .log(); 
{
  mcs: 720.8100000000001
}

Complex Custom Reducers (runEmulators)

Often, the logic of a desired aggregation calls for use of existing aggregations. For instance, an average requires the use of a sum and a count. Below is an example of a more complex reducer that makes use of existing reducers.

let multiColAvg = rowFunc =>
    data => {

        let xSum = $$.sum(row => rowFunc(row)[0])(data);
        let ySum = $$.sum(row => rowFunc(row)[1])(data);
        let xCount = $$.count(row => rowFunc(row)[0])(data);
        let yCount = $$.count(row => rowFunc(row)[1])(data);

        return (xSum + ySum) / (xCount + yCount);

    };

purchases
    .reduce({
        mca: multiColAvg(p => [p.time, p.rating])
    })
    .log(); 

This produces:

{
  mca: 45.050625
}