---
title: "Register a Feature Aggregate"
description: "Register a Feature Aggregate to roll up feature values across records."
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

<div style="padding: 14px 16px; border: 0.5px solid #AFA9EC; border-radius: 12px; background: #EEEDFE; margin-bottom: 1.5rem;">
  <div style="display: flex; align-items: center; gap: 7px; margin-bottom: 8px;">
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
      <path d="M9 2L4 9h4l-1 5 5-7H8l1-5z" stroke="#7f77dd" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
    <span style="font-size: 11px; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; color: #534AB7;">TL;DR</span>
  </div>
  <div style="font-size: 13px; color: #3C3489; line-height: 1.65;">
    Roll up many rows from a lower entity level into one value at this Feature's entity (like each Customer's <code>max_account_balance</code> across their Accounts). Tick <strong>Is Aggregated</strong>, write the logic in Python, Pandas, or Spark, and create. This page covers the aggregated case; for a transformation at the same entity level, see <a href="/user-guide/how-tos/register-a-feature/">Feature</a>.
  </div>
</div>

## What is it?

Every registered object has an **entity**, the level it is computed at, such as Application, Account, or Customer. A regular [Feature](/user-guide/how-tos/register-a-feature/) works with data already at its own entity, one row at a time. A Feature Aggregate works across levels: it takes many rows from a lower entity and rolls them up into a single value at the Feature's entity. For example, a Customer has many Accounts, and a Feature Aggregate can turn those Account rows into one number per Customer, like their highest balance.

A Feature Aggregate is not a separate object type. It is simply a [Feature](/user-guide/how-tos/register-a-feature/) with **Is Aggregated** ticked, which switches the Formula section into aggregation mode. Reach for it whenever your output needs to summarize child rows, like totaling a Customer's balances across all their Accounts.

<table class="attr-table">
  <thead>
    <tr>
      <th style="width: 18%;"></th>
      <th>Feature</th>
      <th>Feature Aggregate (this page)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="field-cell">Computes from</td>
      <td>Inputs already at the Feature's entity level</td>
      <td>Multiple rows at a <em>lower</em> entity level, rolled up to this Feature's entity</td>
    </tr>
    <tr>
      <td class="field-cell">Use when</td>
      <td>The output depends only on inputs already at the same entity</td>
      <td>You need to summarize across child rows</td>
    </tr>
    <tr>
      <td class="field-cell">Definition language</td>
      <td>Python only</td>
      <td>Python, Pandas, or Spark</td>
    </tr>
    <tr>
      <td class="field-cell">Example</td>
      <td><code>risk_tier</code> from an Application's <code>fico</code></td>
      <td><code>max_account_balance</code> for each Customer</td>
    </tr>
  </tbody>
</table>

## How to Register

Follow these steps to register a Feature Aggregate.

<details open class="step">
<summary>

#### Open the Create Form

</summary>

On the **Feature Engineering → Feature** page, click **+ New Feature**.

A name prompt opens first. Enter a descriptive name for the Feature (for example, `Max Account Balance`) and confirm to land on the full Feature form.

</details>

<details open class="step">
<summary>

#### Fill in Attributes

</summary>

Tick **Is Aggregated** first. This is what turns the Formula section into aggregation mode.

Set the **Type** <span style="color: #E5484D;">\*</span> - the **output type of your aggregation**, not the source column type (summing or taking a max → `Numerical`, returning a list → an `Array` type, a flag or label → `String` or `Boolean`).

Set the **Entity** <span style="color: #E5484D;">\*</span> to the entity you are **rolling up to** (for example, `Customer`); inputs must come from this entity or a lower one.

Fill in the **Alias**. See [Common Registration Info](/user-guide/how-tos/common-registration-info/).

</details>

<details open class="step">
<summary>

#### Fill in Formula

</summary>

Under **Input**, pick the inputs the aggregation reads, from this Feature's entity or a lower one. With **Entity = Customer**, you can pick Account-level inputs (for example, `account`); each becomes a variable in the editor, referenced by its alias.

Then write the aggregation in **Python**, **Pandas**, or **Spark**:

- **Python and Pandas** receive **one entity's rows**. The platform groups the table by your Feature's entity and hands your code only the rows for the current entity (one Customer's Accounts). You compute and `return` a **single value** for that entity. No `groupBy` needed.
- **Spark** receives the **whole, ungrouped table** across every entity. You do the `groupBy` yourself and `return` a **DataFrame keyed by the entity**, plus an **Output Dataframe Key**.

:::note[Output Dataframe Key]
**Spark only.** The column in your returned DataFrame that holds the entity key (for example, `customer_id`). The platform uses it to join each aggregated value back to the right Customer.
:::

The example below computes each Customer's maximum balance across their open Accounts (**Entity = Customer**, input = `account`).

<Tabs>
  <TabItem label="Python">

Each input table is exposed as a **dictionary of lists**, sliced to the current entity (one Customer's Accounts). Return a single value.

```python
# `account` is a data dictionary of one Customer's Accounts
open_balances = [
    bal for bal, status in zip(
        account['current_balance'], account['status']
    )
    if status == 'open' and bal is not None
]
if not open_balances:
    return None
return max(open_balances)
```

  </TabItem>
  <TabItem label="Pandas">

Each input table is exposed as a **pandas DataFrame**, sliced to the current entity (one Customer's Accounts). Return a single value.

```python
# `account` is a pandas DataFrame of one Customer's Accounts
open_accounts = account[account['status'] == 'open']
if open_accounts.empty:
    return None
return open_accounts['current_balance'].max()
```

  </TabItem>
  <TabItem label="Spark">

The input is the **full, ungrouped** Account table across every Customer. Do the `groupBy('customer_id')` yourself, return a 2-column DataFrame (the entity key + the value), and set the **Output Dataframe Key** to `customer_id`.

```python
import pyspark.sql.functions as F

# `account` is the full Account table across all Customers
open_accounts = account.filter(F.col('status') == 'open')
return open_accounts.groupBy('customer_id').agg(
    F.max('current_balance').alias('max_account_balance')
)
```

  </TabItem>
</Tabs>

</details>

<details open class="step">
<summary>

#### Fill in Properties and Create

</summary>

Fill in the [Properties](/user-guide/how-tos/common-registration-info/#properties) (Description, Permissible Purpose, Group, and Keywords), then click **Create** at the bottom right. The Feature is saved as a draft and you land on its details page.

</details>

## What's Next

Once registered, the Feature Aggregate can be used downstream in [Features](/user-guide/how-tos/register-a-feature/), [Models](/user-guide/how-tos/register-a-model-definition/), and [Policies](/user-guide/how-tos/register-policy-decision-workflow/), just like any Feature. You can also [run a simulation](/user-guide/how-tos/run-a-simulation/) on it and trace its lineage to every downstream object.

Next steps:

- Build a [Model](/user-guide/how-tos/register-a-model-definition/) or [Policy](/user-guide/how-tos/register-policy-decision-workflow/) that uses this Feature Aggregate.
- Send it for [approval](/user-guide/how-tos/approve-an-object/) so it can be used outside your draft workspace.
- Computing at the same entity level instead? Register a regular [Feature](/user-guide/how-tos/register-a-feature/).
