Skip to content

GroupBy Cumulative

Source: src/memframe/wrappers/analytix/groupby_cumulative.py

GroupByCumulativeWrapper is the public group-wise running-computation interface exposed through a ContextManager. It provides pandas-groupby().cumsum()-like analytics: cumulative sum, product, minimum, maximum, mean, count, standard deviation, and variance, each computed over a window partitioned by the group columns and ordered by a column you choose (or the physical row order by default).

Users normally call group-by cumulative methods through the unified groupby builder on a dataset context returned by an upload operation:

dataset = mf.upload_df(frame)

result = dataset.groupby("region").cumsum("revenue", order_col="month")
result = dataset.groupby("region").cummean("score")
dataset = await mf.aupload_df(frame)

result = await dataset.groupby("region").acumsum("revenue", order_col="month")

Note

A flat dataset.cumsum(...) resolves to the ungrouped CumulativeWrapper.cumsum(column, order_col, target_col) (it is registered first and takes no group columns). The grouped form above is only reachable through the groupby(...) builder or GroupByCumulativeWrapper directly — the same arrangement as grouped event_rate on the Stats page.

The lower-level files are implementation details:

  • src/memframe/core/analytix/groupby_cumulative.py builds and executes backend-specific SQL in a single GroupbyCumulativeOps class (isinstance branches for DuckDB / PostgreSQL / ClickHouse).
  • src/memframe/core/orchestrator/analytix/groupby_cumulative.py resolves the active dataset context and applies @record_call.
  • src/memframe/wrappers/analytix/groupby_cumulative.py exposes the synchronous and asynchronous builder methods plus the direct async API.
  • src/memframe/wrappers/analytix/groupby.py is the unified GroupBy facade that routes ctx.groupby(*columns) calls to the stats, cumulative, and (future) window builders.

Public API

Each operation has a synchronous and asynchronous form. All take (column, order_col=None, target_col=None):

Synchronous Asynchronous Purpose
cumsum(column, order_col=None, target_col=None) await acumsum(...) Group-wise running sum
cumprod(column, order_col=None, target_col=None) await acumprod(...) Group-wise running product
cummax(column, order_col=None, target_col=None) await acummax(...) Group-wise running maximum
cummin(column, order_col=None, target_col=None) await acummin(...) Group-wise running minimum
cummean(column, order_col=None, target_col=None) await acummean(...) Group-wise running mean
cumcount(column, order_col=None, target_col=None) await acumcount(...) Group-wise running non-null count
cumstd(column, order_col=None, target_col=None) await acumstd(...) Group-wise running population stddev
cumvar(column, order_col=None, target_col=None) await acumvar(...) Group-wise running population variance

Public builder methods return the raw operation envelope (see Return Values below). Invalid operations surface is_error envelopes (or raise OperationError once unwrapped via a ContextManager).

Usage Overview

dataset = mf.upload_df(frame)

sample = dataset.groupby("region").cumsum("revenue", order_col="month")
sample = dataset.groupby("region").cummean("score", target_col="running_avg")
sample = dataset.groupby("region", "month").cumcount("user_id")
dataset = await mf.aupload_df(frame)

sample = await dataset.groupby("region").acumsum("revenue", order_col="month")
sample = await dataset.groupby("region").acummean("score")

order_col is optional — omit it to use physical row order:

sample = dataset.groupby("region").cumsum("revenue")
sample = dataset.groupby("region").cummean("score")

Common Parameters

Parameter Type Description
column str Numeric column to accumulate.
order_col str, list[str], or None Row ordering inside each group. None (default) uses physical row order. A list orders by multiple columns.
target_col str or None Name for the result column. Auto-generated as cum_<column>_<op>_by_<groups> with _order_by_<orders> appended when order_col is given (e.g. cum_revenue_sum_by_region_order_by_month).
map_feature bool When True (default False), LEFT JOIN the new feature column back onto the original table — matched on full row identity. No extra result table is created; the envelope gains mapped_table / mapped_columns.

group_cols are fixed at builder creation (groupby("region", "month")) and accept one or more columns.

With no order_col, rows accumulate in physical storage order (PostgreSQL ctid, DuckDB rowid). ClickHouse has no stable row id, so a synthetic _mf_row_num (ROW_NUMBER() OVER()) is generated in a subquery to give the window a deterministic order, then stripped from the output via SELECT * EXCEPT(_mf_row_num) so chained operations stay clean.

Running Sum and Product

cumsum

Running sum within each group via SUM(col) OVER (PARTITION BY … ORDER BY … ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW).

result = dataset.groupby("region").cumsum("revenue", order_col="month")
result = await dataset.groupby("region").acumsum("revenue", order_col="month")

cumprod

Running product within each group via EXP(SUM(LN(NULLIF(col, 0))) OVER ({window_spec})). Zeros are skipped rather than zeroing the tail.

result = dataset.groupby("region").cumprod("growth_factor", order_col="month")

Negative inputs abort the whole operation with is_error True (engines reject LN of a negative). Restrict cumprod to non-negative columns.

Running Min, Max, and Mean

cummax, cummin

Running extrema within each group via MAX(col) / MIN(col) over the same partitioned window frame.

result = dataset.groupby("region").cummax("price", order_col="day")
result = dataset.groupby("region").cummin("price", order_col="day")

cummean

Running mean within each group via AVG(col).

result = dataset.groupby("region").cummean("score", target_col="running_avg")

Running Count, Stddev, and Variance

cumcount

Running non-null count within each group via COUNT(col).

result = dataset.groupby("region").cumcount("user_id", order_col="signup_date")

The result column is BIGINT on PostgreSQL/DuckDB and Int64 on ClickHouse (pass target_type through the direct API to override).

cumstd, cumvar

Running population (not sample) statistics within each group: STDDEV_POP(col) / VAR_POP(col) on PostgreSQL and DuckDB, stddevPop(col) / varPop(col) on ClickHouse.

result = dataset.groupby("region").cumstd("score", order_col="month")
result = dataset.groupby("region").cumvar("score", order_col="month")

Map Feature Back (map_feature)

With map_feature=True, the new running-value column is LEFT JOINed back onto the original table — every original row gains its own running value (one output row per input row, matched on full row identity). Only the feature column is added and no extra result table is created. Only the group table is recorded by @record_call.

result = dataset.groupby("region").cumsum("revenue", map_feature=True)
# original table now has: region, revenue, cum_revenue_sum_by_region

Rules:

  • A repeated identical call re-runs cleanly (its own column is dropped and re-added). This also holds in non-deep mode, which saves nothing.
  • If the column name already exists from anything else, the call fails with an error — drop or rename it first.
  • Backend swaps keep the original table name: DuckDB CREATE OR REPLACE, PostgreSQL create-swap-rename, ClickHouse atomic EXCHANGE TABLES.
  • Rows with NULL key columns match nothing (NULL = NULL is false in SQL) and keep a NULL feature value.

Return Values and Errors

Calling the builder (or the wrapper class) directly returns the raw operation envelope:

{
    "is_error": False,
    "message": "Cumulative sum of 'revenue' grouped by [region] ...",
    "result": <pandas.DataFrame>,
    "new_table": "ab12CD__op_3",
    "new_columns": ["cum_revenue_sum_by_region"],
    "group_cols": ["region"],
    "operation": "sum",
}

Invalid operations return is_error envelopes (and raise OperationError once unwrapped via a ContextManager): a missing column, a missing backend/data_id, or an unsupported backend.

Generated Tables

Every group-by cumulative operation is non-destructive to the source upload table. It creates a new transient table holding the source rows plus the result column, then returns a preview of it:

  • PostgreSQL / DuckDB: CREATE TABLE … AS SELECT *, <window> AS <target> FROM … with PARTITION BY + physical-row fallback ordering.
  • ClickHouse: the same single-pass shape with ENGINE = MergeTree() ORDER BY tuple(); without order_col the source is wrapped in a ROW_NUMBER() subquery whose helper column is excluded from the output.

Operations apply @record_call, so repeat calls are recorded in the transient registry.

Backend Behavior

Group-by cumulative supports DuckDB, PostgreSQL, and ClickHouse:

  • Identifiers are sanitized and quoted before SQL is generated.
  • group_cols may be a single column or a list; at least one is required.
  • order_col may be a single column, a list, or omitted (physical order; synthetic _mf_row_num on ClickHouse).
  • STDDEV_POP/VAR_POP map to ClickHouse-native stddevPop/varPop.

Errors

Group-by cumulative methods surface is_error envelopes (or raise OperationError once unwrapped) for validation or backend failures:

  • Missing or renamed column / order_col / group column.
  • cumprod on a column containing negatives fails the whole operation.
  • Missing backend / data_id at the ops layer, or an unsupported backend.

API Reference

memframe.wrappers.analytix.groupby_cumulative.GroupByCumulativeWrapper

Bases: GroupByCumulativeOrchestrator

Wrapper around GroupByCumulativeOrchestrator fluent APIs.

groupby(*columns)

Create a cumulative builder scoped to one or more group columns.

memframe.wrappers.analytix.groupby_cumulative.GroupByCumulativeBuilderWrapper

Bases: GroupByCumulative

Fluent group-by cumulative builder with async/sync methods.

acumsum(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative sum.

cumsum(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative sum.

acumprod(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative product.

cumprod(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative product.

acummax(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative maximum.

cummax(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative maximum.

acummin(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative minimum.

cummin(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative minimum.

acummean(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative mean.

cummean(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative mean.

acumcount(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative count.

cumcount(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative count.

acumstd(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative standard deviation.

cumstd(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative standard deviation.

acumvar(column, order_col=None, target_col=None, map_feature=False) async

Asynchronously compute group-wise cumulative variance.

cumvar(column, order_col=None, target_col=None, map_feature=False) async

Synchronously compute group-wise cumulative variance.