Temporal Aggregation & Disaggregation for Modern Python (Polars-first)
Project description
aggdisagg
Temporal Aggregation & Disaggregation for Modern Python
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
uniformlineardenton/denton-cholettechow-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=Truefor a ready-to-use monthly date column, or callexpand_high_freq_datesyourself 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 onfit_transform(..., extrapolate=...)ordisaggregate_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 y_std when uncertainty was computed). Original context columns are not automatically repeated (this was changed for robustness across Polars/pandas/object dates). 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.
denton,litterman,fernandezimplementations are functional but not as sophisticated as the classic R packages yet.- 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()
# 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aggdisagg-1.6.2.tar.gz.
File metadata
- Download URL: aggdisagg-1.6.2.tar.gz
- Upload date:
- Size: 84.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4468aa66de2e2b6f9cf8fffd6d145a563f073aebc1eebc5bee11d08c4b5149bf
|
|
| MD5 |
00c8354df354fd5ef496e7435d2bba01
|
|
| BLAKE2b-256 |
3bfb3b6aa66ceaa1bfc9809bb86580966d098cfeeb4bf4e8dda2e98e5efcdca1
|
Provenance
The following attestation bundles were made for aggdisagg-1.6.2.tar.gz:
Publisher:
publish.yml on southu/aggdisagg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aggdisagg-1.6.2.tar.gz -
Subject digest:
4468aa66de2e2b6f9cf8fffd6d145a563f073aebc1eebc5bee11d08c4b5149bf - Sigstore transparency entry: 2107493014
- Sigstore integration time:
-
Permalink:
southu/aggdisagg@c4c1106bbaa2c2af84827a83732453ea505c9a90 -
Branch / Tag:
refs/tags/v1.6.2 - Owner: https://github.com/southu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c4c1106bbaa2c2af84827a83732453ea505c9a90 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aggdisagg-1.6.2-py3-none-any.whl.
File metadata
- Download URL: aggdisagg-1.6.2-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6c3bdb3860d4d020a41ae35bf02d7cc040bf6d88a532482b63fde3b8a82e428
|
|
| MD5 |
24a7f537f2f353521f72fba0ee3798eb
|
|
| BLAKE2b-256 |
b59c4f206451e32e49543c7f44fb4ca1ad98568b0d4fcfdd70dbc30858fa923b
|
Provenance
The following attestation bundles were made for aggdisagg-1.6.2-py3-none-any.whl:
Publisher:
publish.yml on southu/aggdisagg
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aggdisagg-1.6.2-py3-none-any.whl -
Subject digest:
c6c3bdb3860d4d020a41ae35bf02d7cc040bf6d88a532482b63fde3b8a82e428 - Sigstore transparency entry: 2107493078
- Sigstore integration time:
-
Permalink:
southu/aggdisagg@c4c1106bbaa2c2af84827a83732453ea505c9a90 -
Branch / Tag:
refs/tags/v1.6.2 - Owner: https://github.com/southu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c4c1106bbaa2c2af84827a83732453ea505c9a90 -
Trigger Event:
release
-
Statement type: