# Grouper

```ts
import { Grouper } from 'cx/data';
```


`Grouper` groups records by key fields and computes aggregate values like sum, average, count, min, and max.

```tsx
import { Grouper, createModel, GroupResult } from "cx/data";
import { Controller } from "cx/ui";
import { Grid } from "cx/widgets";

interface Sale {
  region: string;
  product: string;
  amount: number;
  quantity: number;
}

interface Model {
  sales: Sale[];
  grouped: GroupResult[];
}

const m = createModel<Model>();

class PageController extends Controller {
  onInit() {
    this.store.set(m.sales, [
      { region: "North", product: "Widget", amount: 100, quantity: 5 },
      { region: "North", product: "Gadget", amount: 200, quantity: 3 },
      { region: "South", product: "Widget", amount: 150, quantity: 7 },
      { region: "South", product: "Gadget", amount: 80, quantity: 2 },
      { region: "East", product: "Widget", amount: 120, quantity: 4 },
      { region: "East", product: "Gadget", amount: 90, quantity: 6 },
    ]);
    this.computeGroups();
  }

  computeGroups() {
    const sales = this.store.get(m.sales);

    const grouper = new Grouper(
      { region: (s: Sale) => s.region },
      {
        totalAmount: { type: "sum", value: (s: Sale) => s.amount },
        totalQty: { type: "sum", value: (s: Sale) => s.quantity },
        avgAmount: { type: "avg", value: (s: Sale) => s.amount },
      },
    );

    grouper.processAll(sales);
    this.store.set(m.grouped, grouper.getResults());
  }
}

export default (
  <div controller={PageController}>
    <h4 style="margin: 0 0 8px">Sales Data</h4>
    <Grid
      records={m.sales}
      columns={[
        { header: "Region", field: "region" },
        { header: "Product", field: "product" },
        { header: "Amount", field: "amount", format: "currency", align: "right" },
        { header: "Qty", field: "quantity", align: "right" },
      ]}
    />

    <h4 style="margin: 16px 0 8px">Grouped by Region</h4>
    <Grid
      records={m.grouped}
      columns={[
        { header: "Region", field: "key.region" },
        { header: "Total Amount", field: "aggregates.totalAmount", format: "currency", align: "right" },
        { header: "Total Qty", field: "aggregates.totalQty", align: "right" },
        { header: "Avg Amount", field: "aggregates.avgAmount", format: "currency", align: "right" },
        { header: "Records", field: "records.length", align: "right" },
      ]}
    />
  </div>
);

```

## Constructor

```ts
new Grouper(key, aggregates?, dataGetter?, nameGetter?)
```

## Parameters

| Parameter    | Type       | Description                                    |
| ------------ | ---------- | ---------------------------------------------- |
| `key`        | `object`   | Object mapping field names to value selectors. |
| `aggregates` | `object`   | Optional. Aggregate definitions.               |
| `dataGetter` | `function` | Optional. Extract data from record.            |
| `nameGetter` | `function` | Optional. Extract display name for group.      |

## Aggregate Types

| Type       | Description               |
| ---------- | ------------------------- |
| `sum`      | Sum of values.            |
| `avg`      | Average of values.        |
| `count`    | Count of records.         |
| `min`      | Minimum value.            |
| `max`      | Maximum value.            |
| `distinct` | Count of distinct values. |

## Methods

| Method                   | Description                  |
| ------------------------ | ---------------------------- |
| `process(record, index)` | Process a single record.     |
| `processAll(records)`    | Process all records at once. |
| `getResults()`           | Get array of group results.  |
| `reset()`                | Reset the grouper for reuse. |

## Examples

### Basic grouping

```ts
const grouper = new Grouper({
  category: (item) => item.category,
});

grouper.processAll(items);
const groups = grouper.getResults();
// [{ key: { category: "A" }, records: [...], indexes: [...] }, ...]
```

### With aggregates

```ts
const grouper = new Grouper(
  { region: (s) => s.region },
  {
    total: { type: "sum", value: (s) => s.amount },
    average: { type: "avg", value: (s) => s.amount },
    count: { type: "count" },
    minAmount: { type: "min", value: (s) => s.amount },
    maxAmount: { type: "max", value: (s) => s.amount },
  },
);

grouper.processAll(sales);
const results = grouper.getResults();
// [{
//   key: { region: "North" },
//   records: [...],
//   aggregates: { total: 300, average: 150, count: 2, minAmount: 100, maxAmount: 200 }
// }, ...]
```

### Multi-level grouping

```ts
const grouper = new Grouper({
  year: (s) => s.date.getFullYear(),
  month: (s) => s.date.getMonth() + 1,
});
```

### Weighted average

```ts
const grouper = new Grouper(
  { category: (item) => item.category },
  {
    weightedAvg: {
      type: "avg",
      value: (item) => item.price,
      weight: (item) => item.quantity,
    },
  },
);
```

## Result Structure

```ts
interface GroupResult {
  key: object; // Group key values
  name?: any; // Optional display name
  records: any[]; // Records in this group
  indexes: number[]; // Original indexes
  aggregates?: object; // Computed aggregates
}
```

## See Also

- [Grid Grouping](/docs/tables/grouping) - Grouping in Grid
- [computable](/docs/utilities/computable) - Derived values