From acaaa65bf746adf8647705a5cef9cf42bc196120 Mon Sep 17 00:00:00 2001 From: Liang Xu Date: Fri, 28 Aug 2026 11:18:54 +0800 Subject: [PATCH] feat(table-core): add mode aggregation function Fixes #5864 Signed-off-by: Liang Xu --- .../row-aggregation/aggregationFns.ts | 34 +++++++++++++++++++ .../tests/unit/fns/aggregationFns.test.ts | 17 ++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/table-core/src/features/row-aggregation/aggregationFns.ts b/packages/table-core/src/features/row-aggregation/aggregationFns.ts index 22fa2e716f..524dd2808a 100755 --- a/packages/table-core/src/features/row-aggregation/aggregationFns.ts +++ b/packages/table-core/src/features/row-aggregation/aggregationFns.ts @@ -282,6 +282,39 @@ export const aggregationFn_median = constructAggregationFn< }, }) +/** + * Computes the statistical mode (most frequent value) of the row values. + * If multiple values have the same maximum frequency, returns the first one encountered. + * Returns `undefined` when no rows are present. + */ +export const aggregationFn_mode = constructAggregationFn< + any, + any, + unknown, + unknown +>({ + aggregate: (context) => { + const rows = context.rows + if (!rows.length) return undefined + + let maxCount = 0 + let modeValue: unknown = undefined + const counts = new Map() + + for (let i = 0; i < rows.length; i++) { + const value = context.getValue(rows[i]!) + const count = (counts.get(value) ?? 0) + 1 + counts.set(value, count) + if (count > maxCount) { + maxCount = count + modeValue = value + } + } + + return modeValue + }, +}) + /** Collects distinct row values using JavaScript `Set` semantics. */ export const aggregationFn_unique = constructAggregationFn< any, @@ -373,6 +406,7 @@ export const aggregationFns = { extent: aggregationFn_extent, mean: aggregationFn_mean, median: aggregationFn_median, + mode: aggregationFn_mode, unique: aggregationFn_unique, uniqueCount: aggregationFn_uniqueCount, count: aggregationFn_count, diff --git a/packages/table-core/tests/unit/fns/aggregationFns.test.ts b/packages/table-core/tests/unit/fns/aggregationFns.test.ts index 44e125ad67..0770488be0 100644 --- a/packages/table-core/tests/unit/fns/aggregationFns.test.ts +++ b/packages/table-core/tests/unit/fns/aggregationFns.test.ts @@ -8,6 +8,7 @@ import { aggregationFn_mean, aggregationFn_median, aggregationFn_min, + aggregationFn_mode, aggregationFn_sum, aggregationFn_unique, aggregationFn_uniqueCount, @@ -119,6 +120,22 @@ describe('aggregation function definitions', () => { ).toBeUndefined() }) + it('calculates mode and returns first encountered value on tie', () => { + expect( + aggregationFn_mode.aggregate(context(['a', 'b', 'a', 'c', 'b', 'a'])), + ).toBe('a') + expect( + aggregationFn_mode.aggregate(context(['a', 'b', 'a', 'b', 'c'])), + ).toBe('a') + expect( + aggregationFn_mode.aggregate(context([1, 2, 2, 3, 2, 1])), + ).toBe(2) + expect( + aggregationFn_mode.aggregate(context([null, undefined, null, 'x'])), + ).toBeNull() + expect(aggregationFn_mode.aggregate(context([]))).toBeUndefined() + }) + it('preserves custom definition result inference', () => { const joined = constructAggregationFn({ aggregate: ({ rows }) => rows.map((row) => row.id).join(','),