# Window (Aggregation) Expressions

Canonical page: https://www.adaptabletools.com/docs/adaptable-ql-expression-window

- Window Expressions order a set of rows (via `OVER`) and then look up a value from *another* row in that ordering
- They use the `LAG`, `LEAD`, `FIRST`, `LAST` and `RANK` Expression Functions

Window Expressions order the rows in a set and then reference a value from **another row** in that ordering.

Window Expressions return *per-row* results (unlike regular Aggregations which reduce many rows to one value).

This can be the value of a neighbouring row, the first or last row in the series, or the row's rank.

- This is particularly useful for row-relative comparisons
- e.g. *"the value in the previous row"*, *"the value in the next row"* or *"this row's rank"*

Window Expressions are, like Cumulative and Quantile Expressions, a special case of [Aggregation Expressions](https://www.adaptabletools.com/docs/adaptable-ql-expression-aggregation/index.md).

- **Compare with another row**: previous close, next fixing, first trade of the day - `LAG{[Close],1,OVER([Date])}`
- **1 Line Moves**: day-on-day % change: `PERCENT_DIFF([Close],LAG{[Close],1,GROUP_BY([Ticker]), OVER([Date])})`
- **Rank anything**: rank trades by PnL, or Tickers by Vol each day - `RANK{GROUP_BY([Date]),OVER([Volume],'DESC')}`
- **Order comes from the data**: `OVER` sets the order, so the values stay correct however the Grid is sorted
- **Per group**: add `GROUP_BY` and each Ticker, Book or Desk gets its own series
- **Always live**: edit or add a row and every neighbouring value updates

- By contrast AG Grid's formulas can only reach another row by its fixed row number
- There is no way to request something like "the previous row for this Ticker"

**Example: AdapTableQL: Price Moves with Window Functions**

Day-on-day and since-start price moves using Window Functions

- This demo shows 8 days of closing prices for 3 Tickers
- It contains 4 [Calculated Columns](https://www.adaptabletools.com/docs/handbook-calculated-column-window/index.md):
  - `Prev Close` - the previous day's Close for the same Ticker: `LAG{[close], 1, GROUP_BY([ticker]), OVER([date])}`
  - `Day Change %` - `PERCENT_DIFF([close], LAG{[close], 1, GROUP_BY([ticker]), OVER([date])})`
  - `Since Start %` - `PERCENT_DIFF([close], FIRST{[close], GROUP_BY([ticker]), OVER([date])})`
  - `Volume Rank (Day)` - each Ticker's Volume rank on that day (highest is 1): `RANK{GROUP_BY([date]), OVER([volume], 'DESC')}`
- Format Columns colour `Day Change %` green or red, and highlight any move of more than 3%
- The first day for each Ticker has no previous row, so `Prev Close` and `Day Change %` are blank

- Change a `Close` value - its own `Day Change %` and the next day's both update
- Sort the Grid by `Close` - the values do not change, because `OVER([date])` sets the order, not the Grid sort
- Change a `Volume` and watch `Volume Rank (Day)` re-rank the 3 Tickers on that day

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template]
  columnDefs.ts
  gridOptions.ts
  index.ts             [template]
  onAdaptableReady.ts  [template]
  rowData.ts
  styles.css           [template]
index.html             [template]
package.json           [template]
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {AdaptableOptions} from '@adaptabletools/adaptable';
import {DailyPrice} from './rowData';

export const adaptableOptions: AdaptableOptions<DailyPrice> = {
  primaryKey: 'id',
  adaptableId: 'Window Price Moves',
  initialState: {
    Dashboard: {
      ModuleButtons: ['CalculatedColumn', 'FormatColumn'],
    },
    Theme: {CurrentTheme: 'dark'},
    CalculatedColumn: {
      CalculatedColumns: [
        {
          ColumnId: 'prevClose',
          FriendlyName: 'Prev Close',
          Expression: 'LAG{[close], 1, GROUP_BY([ticker]), OVER([date])}',
          CalculatedColumnSettings: {DataType: 'number'},
        },
        {
          ColumnId: 'dayChange',
          FriendlyName: 'Day Change %',
          Expression:
            'PERCENT_DIFF([close], LAG{[close], 1, GROUP_BY([ticker]), OVER([date])})',
          CalculatedColumnSettings: {DataType: 'number'},
        },
        {
          ColumnId: 'sinceStart',
          FriendlyName: 'Since Start %',
          Expression: 'PERCENT_DIFF([close], FIRST{[close], GROUP_BY([ticker]), OVER([date])})',
          CalculatedColumnSettings: {DataType: 'number'},
        },
        {
          ColumnId: 'volumeRank',
          FriendlyName: 'Volume Rank (Day)',
          Expression: "RANK{GROUP_BY([date]), OVER([volume], 'DESC')}",
          CalculatedColumnSettings: {DataType: 'number'},
        },
      ],
    },
    FormatColumn: {
      FormatColumns: [
        {
          Name: 'big-move',
          Scope: {ColumnIds: ['dayChange']},
          Rule: 'ABS([dayChange]) > 3',
          Style: {BackColor: '#f9a825', ForeColor: 'var(--ab-color-palette-13)', FontWeight: 'Bold'},
        },
        {
          Name: 'up-move',
          Scope: {ColumnIds: ['dayChange']},
          Rule: '[dayChange] > 0',
          Style: {ForeColor: '#66bb6a'},
        },
        {
          Name: 'down-move',
          Scope: {ColumnIds: ['dayChange']},
          Rule: '[dayChange] < 0',
          Style: {ForeColor: '#ef5350'},
        },
        {
          Name: 'two-decimals',
          Scope: {ColumnIds: ['close', 'prevClose', 'dayChange', 'sinceStart']},
          DisplayFormat: {Formatter: 'NumberFormatter', Options: {FractionDigits: 2}},
        },
        {
          Name: 'trade-date',
          Scope: {ColumnIds: ['date']},
          DisplayFormat: {Formatter: 'DateFormatter', Options: {Pattern: 'dd MMM yyyy'}},
        },
      ],
    },
    Layout: {
      CurrentLayout: 'Daily Prices',
      Layouts: [
        {
          Name: 'Daily Prices',
          TableColumns: [
            'date',
            'ticker',
            'close',
            'prevClose',
            'dayChange',
            'sinceStart',
            'volume',
            'volumeRank',
          ],
          ColumnSorts: [
            {ColumnId: 'ticker', SortOrder: 'Asc'},
            {ColumnId: 'date', SortOrder: 'Asc'},
          ],
          AutoSizeColumns: true,
        },
      ],
    },
  },
};
```

**`src/rowData.ts`**

```ts
export interface DailyPrice {
  id: number;
  date: Date;
  ticker: string;
  close: number;
  volume: number;
}

const tradingDays = [14, 15, 16, 17, 18, 21, 22, 23].map((day) => new Date(2026, 8, day));

const closes: Record<string, number[]> = {
  AAPL: [189.2, 191.5, 190.1, 193.8, 192.4, 195.0, 194.2, 197.6],
  MSFT: [415.5, 412.0, 418.3, 421.9, 419.4, 425.7, 430.2, 428.1],
  NVDA: [875.0, 902.3, 889.5, 931.0, 948.2, 921.7, 955.4, 978.9],
};

const volumes: Record<string, number[]> = {
  AAPL: [52.1, 48.7, 61.3, 55.0, 47.2, 58.9, 50.4, 63.8],
  MSFT: [21.4, 25.9, 19.8, 23.1, 27.6, 22.0, 30.5, 24.3],
  NVDA: [44.6, 70.2, 51.8, 66.4, 59.1, 72.7, 48.3, 68.0],
};

// One row per ticker per trading day; volume is in millions of shares
export const rowData: DailyPrice[] = tradingDays.flatMap((date, dayIndex) =>
  Object.keys(closes).map((ticker, tickerIndex) => ({
    id: dayIndex * 10 + tickerIndex + 1,
    date,
    ticker,
    close: closes[ticker][dayIndex],
    volume: volumes[ticker][dayIndex],
  }))
);
```

**`src/columnDefs.ts`**

```ts
import {ColDef} from 'ag-grid-enterprise';
import {DailyPrice} from './rowData';

export const columnDefs: ColDef<DailyPrice>[] = [
  {field: 'id', hide: true, cellDataType: 'number'},
  {field: 'date', headerName: 'Date', cellDataType: 'date'},
  {field: 'ticker', headerName: 'Ticker', cellDataType: 'text', enableRowGroup: true},
  {field: 'close', headerName: 'Close', cellDataType: 'number', type: 'rightAligned'},
  {field: 'volume', headerName: 'Volume (m)', cellDataType: 'number', type: 'rightAligned'},
];
```

**`src/gridOptions.ts`**

```ts
import {GridOptions, themeBalham} from 'ag-grid-enterprise';

import {columnDefs} from './columnDefs';
import {rowData, DailyPrice} from './rowData';

export const gridOptions: GridOptions<DailyPrice> = {
  theme: themeBalham,
  defaultColDef: {
    resizable: true,
    sortable: true,
    editable: true,
    filter: true,
    floatingFilter: true,
  },
  columnDefs: columnDefs,
  rowData: rowData,
  sideBar: true,
  suppressMenuHide: true,
  columnMenu: 'new',
  cellSelection: true,
  statusBar: {
    statusPanels: [
      {statusPanel: 'agTotalRowCountComponent', align: 'left'},
      {statusPanel: 'agFilteredRowCountComponent'},
      {
        key: 'Center Panel',
        statusPanel: 'AdaptableStatusPanel',
        align: 'center',
      },
    ],
  },
};
```

## Window Functions

There are 5 Window Functions available:

| Function | Returns |
| -------- | ------- |
| `LAG`    | The value from a *previous* row (SQL `LAG`)  |
| `LEAD`   | The value from a *later* row (SQL `LEAD`)    |
| `FIRST`  | The value from the *first* row in the ordering |
| `LAST`   | The value from the *last* row in the ordering  |
| `RANK`   | The 1-based rank of the row in the ordering (ties share a rank) |

## Using `OVER`

Every Window Function needs to know **in what order** the rows should be considered.

That order is specified by the `OVER` clause, which takes the name of an [Adaptable Column](https://www.adaptabletools.com/docs/dev-guide-columns-adaptable-column/index.md) as its first parameter.

The `OVER` clause only works with Columns of type `date` or `number`

`LAG` and `LEAD` also take an **offset** (a positive integer) that specifies how many rows back or forward to look:

```ts [[2, 2, "LAG"], [1, 2, "PnL"], [4, 2, "1"], [2, 2, "OVER"], [1, 2, "TradeDate"]]
// Value of PnL from the previous row, ordered by TradeDate
 LAG{[PnL], 1, OVER([TradeDate])};
```

```ts [[2, 2, "LEAD"], [1, 2, "PnL"], [4, 2, "2"], [2, 2, "OVER"], [1, 2, "TradeDate"]]
// Value of PnL from 2 rows later, ordered by TradeDate
 LEAD{[PnL], 2, OVER([TradeDate])};
```

`FIRST` and `LAST` return the value at the start or end of the ordered series:

```ts [[2, 2, "FIRST"], [1, 2, "PnL"], [2, 2, "OVER"], [1, 2, "TradeDate"]]
// Value of PnL in the earliest row, ordered by TradeDate
 FIRST{[PnL], OVER([TradeDate])};
```

`RANK` does not take a value column - it ranks each row by the `OVER` Column itself (ties share a rank, and the next distinct value skips ranks):

```ts [[2, 2, "RANK"], [2, 2, "OVER"], [1, 2, "PnL"]]
// Rank each row by its PnL
 RANK{OVER([PnL])};
```

- There is no need physically to sort the Grid using the Column specified by `OVER`
- Just like Cumulative Expressions, `OVER` *ignores* the current Grid Sort and uses the order in the given Column

### Sort Direction

By default `OVER` orders rows in **ascending** order (smallest or earliest first).

To reverse the order, pass `'DESC'` as an optional second parameter to `OVER` (`'ASC'` is the default and can be omitted).

This is most useful with `RANK`, where rank 1 is usually wanted for the *largest* value:

```ts [[2, 2, "RANK"], [2, 2, "OVER"], [1, 2, "PnL"], [4, 2, "'DESC'"]]
// Rank each row by its PnL - largest PnL is ranked 1
 RANK{OVER([PnL], 'DESC')};
```

It works with every Window Function, e.g. `FIRST` with a descending order returns the value from the *latest* row:

```ts [[2, 2, "FIRST"], [1, 2, "PnL"], [2, 2, "OVER"], [1, 2, "TradeDate"], [4, 2, "'DESC'"]]
// Value of PnL in the most recent row, ordered by TradeDate
 FIRST{[PnL], OVER([TradeDate], 'DESC')};
```

Rows with a blank value in the `OVER` Column are always placed **last** - in both ascending and descending order

**Example: AdapTableQL: Window Functions**

Using Window Expressions

- This demo contains 3 [Calculated Columns](https://www.adaptabletools.com/docs/handbook-calculated-column/index.md) that use Window Expressions:
  - `Previous Stars` - uses `LAG` to show the Stars count of the repo created *immediately before* this one (ordered by `Created`)
  - `Next Stars` - uses `LEAD` to show the Stars count of the repo created *immediately after* this one (ordered by `Created`)
  - `Stars Rank` - uses `RANK` with `'DESC'` to rank every repo by its Stars count (the repo with the most Stars is ranked 1)
- The Grid is sorted by `Created` (ascending) so the `LAG` / `LEAD` lookups line up with the rows above and below

### Expand to see the Window Expressions

The 3 Calculated Columns are defined as follows:

```
CalculatedColumn: {
  CalculatedColumns: [
    {
      ColumnId: 'prev-stars',
      FriendlyName: 'Previous Stars',
      Expression: 'LAG{[github_stars], 1, OVER([created_at])}',
      CalculatedColumnSettings: {
        DataType: 'number'
      },
    },
    {
      ColumnId: 'next-stars',
      FriendlyName: 'Next Stars',
      Expression: 'LEAD{[github_stars], 1, OVER([created_at])}',
      CalculatedColumnSettings: {
        DataType: 'number'
      },
    },
    {
      ColumnId: 'stars-rank',
      FriendlyName: 'Stars Rank',
      Expression: "RANK{OVER([github_stars], 'DESC')}",
      CalculatedColumnSettings: {
        DataType: 'number'
      },
    },
  ]
}
```

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template]
  columnDefs.ts        [template]
  gridOptions.ts       [template]
  index.ts             [template]
  onAdaptableReady.ts  [template]
  rowData.ts           [template]
  styles.css           [template]
index.html             [template]
package.json           [template]
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {AdaptableOptions} from '@adaptabletools/adaptable';
import {WebFramework} from './rowData';

export const adaptableOptions: AdaptableOptions<WebFramework> = {
  primaryKey: 'id',
  adaptableId: 'Window Expression',
  initialState: {
    Dashboard: {
      ModuleButtons: ['CalculatedColumn'],
    },
    StatusBar: {
      StatusBars: [
        {
          Key: 'Center Panel',
          StatusBarPanels: ['Layout', 'CalculatedColumn'],
        },
      ],
    },
    Theme: {CurrentTheme: 'dark'},
    Layout: {
      CurrentLayout: 'Standard Layout',
      Layouts: [
        {
          Name: 'Standard Layout',
          TableColumns: [
            'name',
            'language',
            'created_at',
            'github_stars',
            'prev-stars',
            'next-stars',
            'stars-rank',
          ],
          ColumnSorts: [{ColumnId: 'created_at', SortOrder: 'Asc'}],
        },
      ],
    },
    CalculatedColumn: {
      CalculatedColumns: [
        {
          ColumnId: 'prev-stars',
          FriendlyName: 'Previous Stars',
          Expression: 'LAG{[github_stars], 1, OVER([created_at])}',
          CalculatedColumnSettings: {
            DataType: 'number',
          },
        },
        {
          ColumnId: 'next-stars',
          FriendlyName: 'Next Stars',
          Expression: 'LEAD{[github_stars], 1, OVER([created_at])}',
          CalculatedColumnSettings: {
            DataType: 'number',
          },
        },
        {
          ColumnId: 'stars-rank',
          FriendlyName: 'Stars Rank',
          Expression: "RANK{OVER([github_stars], 'DESC')}",
          CalculatedColumnSettings: {
            DataType: 'number',
          },
        },
      ],
    },
    FormatColumn: {
      FormatColumns: [
        {
          Name: 'formatColumn-created_at',
          Scope: {
            ColumnIds: ['created_at'],
          },
          DisplayFormat: {
            Formatter: 'DateFormatter',
            Options: {
              Pattern: 'yyyy/MM/dd',
            },
          },
        },
      ],
    },
  },
};
```

## Grouping

As with other Aggregation Expressions, you can use the `GROUP_BY` clause.

This will compute the Window Function **within each partition** rather than across the whole Grid.

With `GROUP_BY` the ordering (`OVER`) and the lookup (or rank) are applied *independently within each distinct group*

```ts [[2, 2, "FIRST"], [1, 2, "PnL"], [2, 2, "OVER"], [1, 2, "TradeDate"], [2, 2, "GROUP_BY"], [1, 2, "currency"]]
// First PnL (ordered by TradeDate) within each Currency
 FIRST{[PnL], OVER([TradeDate]), GROUP_BY([currency])};
```

```ts [[2, 2, "RANK"], [2, 2, "OVER"], [1, 2, "PnL"], [2, 2, "GROUP_BY"], [1, 2, "currency"]]
// Rank each row by PnL within each Currency
 RANK{OVER([PnL]), GROUP_BY([currency])};
```

**Example: AdapTableQL: Grouped Window Functions**

Using Window Expressions with Grouping

- This demo contains 3 [Calculated Columns](https://www.adaptabletools.com/docs/handbook-calculated-column/index.md) that use Window Expressions, each partitioned by `Language` using `GROUP_BY`:
  - `First{per Language}` - uses `FIRST` to show the Stars count of the *earliest-created* repo in each Language
  - `Last{per Language}` - uses `LAST` to show the Stars count of the *latest-created* repo in each Language
  - `Rank within Language` - uses `RANK` with `'DESC'` to rank each repo by its Stars count *within* its Language (most Stars is ranked 1)
- The Grid is sorted by `Language` then `Created` to make the partitions easy to see

### Expand to see the Grouped Window Expressions

The 3 Calculated Columns are defined as follows:

```
CalculatedColumn: {
  CalculatedColumns: [
    {
      ColumnId: 'first-lang-stars',
      FriendlyName: 'First{per Language}',
      Expression: 'FIRST{[github_stars], GROUP_BY([language]), OVER([created_at])}',
      CalculatedColumnSettings: {
        DataType: 'number'
      },
    },
    {
      ColumnId: 'last-lang-stars',
      FriendlyName: 'Last{per Language}',
      Expression: 'LAST{[github_stars], GROUP_BY([language]), OVER([created_at])}',
      CalculatedColumnSettings: {
        DataType: 'number'
      },
    },
    {
      ColumnId: 'lang-rank',
      FriendlyName: 'Rank within Language',
      Expression: "RANK{GROUP_BY([language]), OVER([github_stars], 'DESC')}",
      CalculatedColumnSettings: {
        DataType: 'number'
      },
    },
  ]
}
```

Demo project (Vanilla); the files without a marker are shown below:

```text
src/
  adaptableOptions.ts
  agGridModules.ts     [template]
  columnDefs.ts        [template]
  gridOptions.ts       [template]
  index.ts             [template]
  onAdaptableReady.ts  [template]
  rowData.ts           [template]
  styles.css           [template]
index.html             [template]
package.json           [template]
tsconfig.json          [template]
```

Files marked `[template]` are the same in every demo; see the [demo template](https://www.adaptabletools.com/llms/demo-template.md#vanilla).

**`src/adaptableOptions.ts`**

```ts
import {AdaptableOptions} from '@adaptabletools/adaptable';
import {WebFramework} from './rowData';

export const adaptableOptions: AdaptableOptions<WebFramework> = {
  primaryKey: 'id',
  adaptableId: 'Window Grouped Expression',
  initialState: {
    Dashboard: {
      ModuleButtons: ['CalculatedColumn'],
    },
    StatusBar: {
      StatusBars: [
        {
          Key: 'Center Panel',
          StatusBarPanels: ['Layout', 'CalculatedColumn'],
        },
      ],
    },
    Theme: {CurrentTheme: 'dark'},
    Layout: {
      CurrentLayout: 'Standard Layout',
      Layouts: [
        {
          Name: 'Standard Layout',
          TableColumns: [
            'name',
            'language',
            'created_at',
            'github_stars',
            'first-lang-stars',
            'last-lang-stars',
            'lang-rank',
          ],
          ColumnSorts: [
            {ColumnId: 'language', SortOrder: 'Asc'},
            {ColumnId: 'created_at', SortOrder: 'Asc'},
          ],
        },
      ],
    },
    CalculatedColumn: {
      CalculatedColumns: [
        {
          ColumnId: 'first-lang-stars',
          FriendlyName: 'First{per Language}',
          Expression: 'FIRST{[github_stars], GROUP_BY([language]), OVER([created_at])}',
          CalculatedColumnSettings: {
            DataType: 'number',
          },
        },
        {
          ColumnId: 'last-lang-stars',
          FriendlyName: 'Last{per Language}',
          Expression: 'LAST{[github_stars], GROUP_BY([language]), OVER([created_at])}',
          CalculatedColumnSettings: {
            DataType: 'number',
          },
        },
        {
          ColumnId: 'lang-rank',
          FriendlyName: 'Rank within Language',
          Expression: "RANK{GROUP_BY([language]), OVER([github_stars], 'DESC')}",
          CalculatedColumnSettings: {
            DataType: 'number',
          },
        },
      ],
    },
    FormatColumn: {
      FormatColumns: [
        {
          Name: 'formatColumn-created_at',
          Scope: {
            ColumnIds: ['created_at'],
          },
          DisplayFormat: {
            Formatter: 'DateFormatter',
            Options: {
              Pattern: 'yyyy/MM/dd',
            },
          },
        },
      ],
    },
  },
};
```

## Filtering Rows

A `WHERE` clause restricts which rows take part in the ordering - see [WHERE Clause](https://www.adaptabletools.com/docs/adaptable-ql-expression-aggregation/index.md#where-clause) for full details.

Place it after the value column:

```ts [[2, 2, "LAG"], [1, 2, "Price"], [5, 2, "WHERE"], [1, 2, "Side"], [4, 2, "Buy"], [4, 2, "1"], [2, 2, "OVER"], [1, 2, "TradeDate"]]
// Price of the previous Buy trade (Sell trades are ignored)
 LAG{[Price] WHERE [Side] = 'Buy', 1, OVER([TradeDate])};
```

`RANK` has no value column, so add `WHERE` as a separate item (after a comma):

```ts [[2, 2, "RANK"], [2, 2, "OVER"], [1, 2, "PnL"], [4, 2, "'DESC'"], [5, 2, "WHERE"], [1, 2, "Status"], [4, 2, "Open"]]
// Rank Open trades by PnL (largest first)
 RANK{OVER([PnL], 'DESC'), WHERE [Status] = 'Open'};
```

Rows excluded by `WHERE` return a blank value

## Comparing with Another Row

A Window Function returns an ordinary value, so it can be compared with the current row.

The `PERCENT_DIFF` and `ABSOLUTE_DIFF` functions do this directly:

```ts [[2, 2, "PERCENT_DIFF"], [1, 2, "Close", 15], [2, 2, "LAG"], [1, 2, "Close", 28], [2, 2, "GROUP_BY"], [1, 2, "Ticker"], [2, 2, "OVER"], [1, 2, "Date"]]
// Day-on-day % move of each Ticker's Close
 PERCENT_DIFF([Close], LAG{[Close], 1, GROUP_BY([Ticker]), OVER([Date])});
```

```ts [[2, 2, "PERCENT_DIFF"], [1, 2, "Close", 15], [2, 2, "FIRST"], [1, 2, "Close", 30], [2, 2, "GROUP_BY"], [1, 2, "Ticker"], [2, 2, "OVER"], [1, 2, "Date"]]
// % move of each Ticker's Close since its first day
 PERCENT_DIFF([Close], FIRST{[Close], GROUP_BY([Ticker]), OVER([Date])});
```

- Put `GROUP_BY` on the inner Window Function (not on `PERCENT_DIFF`)
- The demo at the top of this page uses both of these Expressions

See [Window Calculated Columns](https://www.adaptabletools.com/docs/handbook-calculated-column-window/index.md) for how to use these Expressions in a Calculated Column
