Skip to main content

Temporal Aggregation & Disaggregation for Modern Python (Polars-first)

Project description

aggdisagg

Temporal Aggregation & Disaggregation for Modern Python

Python Polars License: MIT uv PyPI GitHub

aggdisagg social preview

Install & try in 10 seconds:

pip install "aggdisagg[all]"

Note: Requires Python ≥ 3.10. For Excel support (read_excel etc.) use the excel extra or install fastexcel / openpyxl separately (or read with pandas + pl.from_pandas).

import polars as pl
from aggdisagg import TemporalAligner
df = pl.DataFrame({"date": ["2020", "2021"], "y": [100.0, 120.0]})
print(TemporalAligner().fit_transform(df, datetime_col="date", target_col="y"))

aggdisagg is a clean, Polars-first Python library for converting time series between frequencies with perfect aggregation consistency.

  • Disaggregate low → high frequency (with indicators)
  • Aggregate high → low frequency (symmetric)
  • Works with Polars (primary), pandas, and xarray

Installation + Try in 10 Seconds

pip install "aggdisagg[all]" && python -c "
import polars as pl
from datetime import date
from aggdisagg import TemporalAligner
df = pl.DataFrame({'date':[date(2020,1,1),date(2021,1,1)], 'y':[1000.,1200.]})
print(TemporalAligner(method='uniform').fit_transform(df, datetime_col='date', target_col='y'))
"

Quickstart with TemporalAligner

import polars as pl
from datetime import date
from aggdisagg import TemporalAligner

df = pl.DataFrame({
    "date": [date(2020, 1, 1), date(2021, 1, 1), date(2022, 1, 1)],
    "y": [1200.0, 1500.0, 1350.0],      # low-frequency target
    "indicator": [100.0, 125.0, 110.0], # high-frequency indicator
})

aligner = TemporalAligner(
    method="chow-lin-opt",
    target_freq="1mo",
    agg="sum",
    indicator_cols=["indicator"],
)

monthly = aligner.fit_transform(df, datetime_col="date", target_col="y")
print(monthly.head())

# Perfect symmetric aggregation
yearly_back = aligner.aggregate(monthly, freq="1y")
print("Roundtrip OK:", (yearly_back["y_1y"] - df["y"]).abs().sum() < 1e-8)

# Plot (requires plotly)
monthly.plot()  # or use .plot() on the result if extended

Supported Methods

  • uniform
  • linear
  • denton / denton-cholette
  • chow-lin, chow-lin-opt (auto ρ via maxlog/minrss)
  • litterman, fernandez

All methods guarantee C @ y_high ≈ y_low exactly.

Real-World Example: Disaggregating Multiple Quarterly Series to Monthly

When you have a DataFrame with several low-frequency series (e.g. quarterly revenue for multiple companies) and want to convert them all to monthly while preserving the aggregation constraint, use the disaggregate_columns helper:

import polars as pl
from datetime import date
from aggdisagg import TemporalAligner

# Synthetic quarterly data (mimics real company revenue files)
df_q = pl.DataFrame({
    "date": [date(2018, 3, 1), date(2018, 6, 1), date(2018, 9, 1), date(2018, 12, 1)],
    "Krones": [1_020_000_000, 1_028_000_000, 1_032_000_000, 1_328_000_000],
    "JBT":     [409_200_000,   491_300_000,   481_900_000,   537_300_000],
    "GEA":     [1_189_000_000, 1_403_000_000, 1_360_000_000, 1_570_000_000],
})

aligner = TemporalAligner(method="linear", target_freq="1mo", agg="sum")

monthly = aligner.disaggregate_columns(
    df_q,
    datetime_col="date",
    include_dates=True,   # automatically generates proper monthly dates
)

print(monthly.head(6))
# date        Krones        JBT           GEA
# 2018-01-01  ~339.1m      ~131.1m      ~374.3m
# ...
# Round-trip check
reagg = aligner.aggregate(monthly.drop("date"), freq="1q")
print("Sums match original quarters:", 
      (reagg["y_1q"] - df_q["Krones"]).abs().sum() < 1e-6)

Notes

  • The helper automatically detects numeric columns as targets (or pass target_cols=[...]).
  • Date inference now correctly chooses a ratio of 3 for quarterly → monthly (instead of assuming annual).
  • Use include_dates=True for a ready-to-use monthly date column, or call expand_high_freq_dates yourself for custom alignment.
  • All series are disaggregated independently but share the same frequency mapping.
  • New in 1.4.1: extrapolate ("nan" default) controls NaN low-freq input handling. "nan" (default) and "drop" never fabricate values from missing inputs; "hold"/"linear" fill using last anchor when requested. Pass on fit_transform(..., extrapolate=...) or disaggregate_columns(...).

See examples/quickstart.py for more patterns.

Why aggdisagg?

  • Polars-native core (lazy-friendly)
  • Perfect consistency by construction (C/D matrices)
  • Sklearn-style + fluent API
  • Real econometric methods (Denton quadratic, Chow-Lin GLS)
  • Excellent pandas / xarray interop
  • Production quality (typed, tested, documented)

First-User Tips & Current Limitations

Recommended starting point

aligner = TemporalAligner(method="chow-lin-opt", target_freq="1mo", agg="sum", indicator_cols=[...])
high = aligner.fit_transform(low_df, datetime_col="date", target_col="y")
back = aligner.aggregate(high, freq="1y")   # should match original low almost exactly

Output shape The returned DataFrame contains y_disaggregated (and _std / _lower / _upper bands only when with_uncertainty=True was passed to disaggregate_columns). Original context columns are not automatically repeated. You can expand dates yourself:

# Example: attach proper high-freq dates (fit_transform itself returns only values)
low_dates = low_df["date"]
high = aligner.fit_transform(low_df, datetime_col="date", target_col="y")
high = high.with_columns(aligner.expand_high_freq_dates(low_dates).alias("date"))

Limitations (as of 1.1.0)

  • Date expansion in the output is basic (low-freq dates are not auto-expanded).
  • Uncertainty is a simple bootstrap and can be noisy or near-zero.
  • All listed methods are fully implemented, produce distinct results, respond to indicators/ρ where applicable, and satisfy the aggregation constraint. (Denton uses quadratic penalty on first/second differences; Chow-Lin family uses GLS with AR(1) or IAR(1) errors.)
  • Only regular frequency ratios are supported.

See the CHANGELOG for the full list of recent robustness and correctness fixes.

  • Negative post-correction + NNLS ensemble
  • sktime / statsforecast compatible wrapper
# Hierarchical
rec = aligner.reconcile_hierarchical([nat_df, reg_df])

# Uncertainty
mean, std = aligner.predict_with_uncertainty()  # real std when uncertainty was requested in disagg (otherwise near-zero)

# Lazy + xarray
lazy_high = aligner.fit_transform(lazy_df)
xa = aligner.to_xarray(high_df)

See examples/quickstart.py for complete gallery.

Development & Publishing

uv sync --all-extras
uv run pytest
uv run python examples/quickstart.py
uv build
# twine or uv publish

License

MIT


Built for data scientists who want temporal frequency conversion that just works.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aggdisagg-1.9.2.tar.gz (93.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aggdisagg-1.9.2-py3-none-any.whl (34.9 kB view details)

Uploaded Python 3

File details

Details for the file aggdisagg-1.9.2.tar.gz.

File metadata

  • Download URL: aggdisagg-1.9.2.tar.gz
  • Upload date:
  • Size: 93.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aggdisagg-1.9.2.tar.gz
Algorithm Hash digest
SHA256 a8b5405c21a0310d276c2f99f5b63c1369447b489b921bae6eb2e043d13376e6
MD5 77ecc3aa2b400a45f840f8a22bb538fc
BLAKE2b-256 dcf13ea0ad641226314b444c405aabe17f563a93b55cff760979c52490356a6c

See more details on using hashes here.

Provenance

The following attestation bundles were made for aggdisagg-1.9.2.tar.gz:

Publisher: publish.yml on southu/aggdisagg

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aggdisagg-1.9.2-py3-none-any.whl.

File metadata

  • Download URL: aggdisagg-1.9.2-py3-none-any.whl
  • Upload date:
  • Size: 34.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aggdisagg-1.9.2-py3-none-any.whl
Algorithm Hash digest
SHA256 102ddd3be4bce17040951abb25e4ac126fa28491169024ed3bf0db43554d29dd
MD5 92e7336aeb70b5bb9a589d4a2b7b3fb3
BLAKE2b-256 4674bb72df916a01152adeb31473f12843225c011f583c1a59fe4324b300d16d

See more details on using hashes here.

Provenance

The following attestation bundles were made for aggdisagg-1.9.2-py3-none-any.whl:

Publisher: publish.yml on southu/aggdisagg

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page