Panel Data Layout

This page explains how PanelMMM expects panel rows to be organised in X. For the column-level contract, see Input Data Requirements.

What “panel” means in AMMM3

In AMMM3, a panel dataset repeats the same time axis across one or more categorical dimensions in dims.

The released FE and CRE presets are more specific one-unit panel contracts. They declare estimator.type: fe or estimator.type: cre and estimator.unit: <column> instead of dimensions.panel. Both require the same date set for every unit, so their released surfaces use a balanced unit-date panel. See Choose an Estimator, Fixed-effects Estimator, and Correlated-random-effects Estimator.

Each row represents:

  • one date_column value
  • one combination of dims values, if any
  • one set of channel and optional control values for that slice

With no extra panel dims, each date appears once. With dims=("geo",), each date appears once per geo. With dims=("geo", "brand"), each date appears once per geo + brand combination.

How dims work

Pass panel dimensions when you construct the model:

from abacus.mmm import GeometricAdstock, LogisticSaturation
from abacus.mmm.panel import PanelMMM

mmm = PanelMMM(
    date_column="date",
    channel_columns=["tv", "search"],
    target_column="sales",
    dims=("geo", "brand"),
    adstock=GeometricAdstock(l_max=8),
    saturation=LogisticSaturation(),
)

dims columns stay in X. They are not moved into y.

AMMM3 reserves these names for internal coordinates, so do not use them in dims:

  • date
  • channel
  • control
  • fourier_mode
  • cre_control
  • within_contrast

No extra panel dims

If dims=(), X should have one row per date.

datetvsearchsales
2025-01-0612040820
2025-01-1312542835
2025-01-2013045850

Internally, AMMM3 reshapes this into:

  • channels: (date, channel)
  • target: (date,)
  • controls, if present: (date, control)

Single panel dim example: geo

If dims=("geo",), each date should appear once for each geo value.

dategeotvsearchsales
2025-01-06UK12040820
2025-01-06US15055910
2025-01-13UK12542835
2025-01-13US15258925

Internally, AMMM3 reshapes this into:

  • channels: (date, geo, channel)
  • target: (date, geo)
  • controls, if present: (date, geo, control)

For a named FE or CRE model, keep the same row layout but declare the unit through estimator.unit rather than dimensions.panel:

estimator:
  type: fe  # or cre
  unit: geo

Do not declare both estimator and dimensions.panel. AMMM3 rejects the mixed configuration.

Multiple panel dims example: geo and brand

If dims=("geo", "brand"), each row identifies one date, one geo, and one brand.

import pandas as pd

X = pd.DataFrame(
    {
        "date": pd.to_datetime(
            [
                "2025-01-06",
                "2025-01-06",
                "2025-01-06",
                "2025-01-06",
                "2025-01-13",
                "2025-01-13",
                "2025-01-13",
                "2025-01-13",
            ]
        ),
        "geo": ["UK", "UK", "US", "US", "UK", "UK", "US", "US"],
        "brand": ["A", "B", "A", "B", "A", "B", "A", "B"],
        "tv": [80.0, 55.0, 92.0, 60.0, 82.0, 58.0, 95.0, 63.0],
        "search": [20.0, 18.0, 24.0, 19.0, 21.0, 18.5, 25.0, 20.0],
    }
)

y = pd.Series(
    [510.0, 370.0, 590.0, 405.0, 520.0, 380.0, 605.0, 418.0],
    name="sales",
)

For a rectangular panel, the row count is:

n_dates * n_geo * n_brand

Internal reshape

AMMM3 converts the pandas inputs into xarray datasets before building the PyMC model.

Input roleInternal variablexarray dims
X[channel_columns]_channel(date, *dims, channel)
X[control_columns]_control(date, *dims, control)
y_target(date, *dims)

The channel and control dimensions come from the configured column names, not from row values.

Rectangularity, duplicates, and missing rows

AMMM3 builds xarray coordinates from the unique values it sees in:

  • date_column
  • each configured dimension column
  • the configured channel or control names

That has three practical consequences:

  • Keep the panel rectangular. Provide one row for every expected date_column + dims combination.
  • Use explicit zeroes for structural no-spend or no-activity rows.
  • Keep declared channel, control, and target values observed within those rows. AMMM3 rejects missing metric cells instead of silently converting them to zeroes.
  • Do not use missing rows to mean “unknown”. AMMM3 validates panel shape before reshape and raises an error if panel cells are missing.

AMMM3 also requires each date_column + dims combination to appear exactly once. It does not aggregate duplicates for you. If you have duplicate rows, deduplicate or aggregate them before fitting or posterior prediction.

Sorting and uniqueness

Sort your data before fitting:

  • first by date_column
  • then by each entry in dims

AMMM3 keeps dates in the order they appear in X, and time-varying features infer time resolution from adjacent rows. A sorted dataset makes the time axis deterministic and easier to reason about.

Also make sure that each date_column + dims combination appears once in the prepared table, and that every expected panel slice is present for every date.

DataFrame versus MultiIndex handling

For normal fitting:

  • use a regular DataFrame for X
  • keep date_column and any dims as columns in that DataFrame
  • use a row-aligned Series for y

AMMM3 does have internal helpers that can align a MultiIndex target Series indexed by [date_column, *dims], but that is not the main user-facing data preparation pattern for fit().

Practical checklist

  • One row per date_column + dims combination
  • No duplicate rows for the same panel cell
  • Same set of dates for every panel slice
  • Explicit zeroes for true zero activity
  • No missing observed channel, control, or target values
  • Sorted rows before fitting
  • For FE or CRE, exactly one declared unit column with at least two units and two dates
  • For FE or CRE, non-zero within-unit temporal variation in the target and each estimable transformed predictor
  • For CRE, enough units to estimate the active centred summaries while retaining at least two between-unit residual degrees of freedom

For scaling choices once the layout is correct, see Scaling and Preprocessing.