How to apply a custom aggregate function on a groupBy property object in Typescript FoO

I want to apply a custom aggregate function after grouping by a property. From the documentation it seems like the aggregate functions usable are limited to: count, average, max, min, sum, cardinality.

I was hoping one could do something like

objectSet.groupBy(group => group.property).map(group => customFunction(group))

1 Like

We did something you propose by writing a function in Typescript.

1 Like

Hi @sstanisor, it depends on which type of calculation you’re trying to achieve. In the past, when faced with a similar situation I’ve used the defined buckets from the groupBy statement to create a map and then build the custom logic from there. This can probably be streamlined a bit, but leaving it all as separate steps for code clarity:

import { Double, Function, FunctionsMap } from "@foundry/functions-api";
import { Objects, YourObjectType} from "@foundry/ontology-api";

export class CustomAggregation {
    @Function()
    public async customGroupByFunction(): Promise<FunctionsMap<string, Double>> {
        // Perform a groupBy operation on the object set
        const result = await Objects.search()
            .yourObjectType() // Replace with your actual object type
            .groupBy(obj => obj.YourGroupingProperty.topValues()) // Replace with the property you want to group by
            .sum(obj => obj.YourAggregationProperty); // Replace with the property you want to aggregate

        // Apply your custom function to the result
        const customResult = new FunctionsMap<string, Double>();
        result.buckets.forEach(bucket => {
            customResult.set(bucket.key, bucket.value * 2) // Example: multiply each value by 2
            });
        return customResult;
    }
}

The example here is quite straightforward as it’s just multiplying the value by 2, but you can use the same type of flow [Grouping → Initial Aggregation → Map → Call map values to do custom logic → output]

Hope this helps!

2 Likes

Hi @sboari , thank you for your reply. I am not sure your method works. Imagine for example you are trying to implement argmin as the aggregate function.

When accessing the buckets, you get access to the keys and the aggregated value (not the list of values the aggregate was computed on).

For argmin, what I ended up doing is something similar to what you are proposing, where you first compute the min and then use a filter.

1 Like

Hi @sstanisor. Glad you got it working!

Indeed, for the argmin example, you wouldn’t have access to all values as the keys would be referring to the aggregated buckets. The example above assumed you wanted to apply custom logic after doing the groupby.

Note that if you need additional control over the granularity of your groupby, you can change the .topValues() to .exactMatch() and include the maximum number of buckets you use via something like:

.groupBy(obj => obj.YourGroupingProperty.topValues({maxBuckets: n}))

where n is the number of desired buckets for your groupby (the usual 10,000 object limitation applies).

Best,