⚡️ tempdisagg
Temporal Disaggregation Models in Python
High-Frequency Estimation from Low-Frequency Data — Modular · Robust · Ready for Production
tempdisagg is a production-ready Python library for temporal disaggregation of time series — transforming low-frequency data into high-frequency estimates while preserving consistency.
It supports all major classical methods — Chow-Lin, Litterman, Denton, Fernández, Uniform — and provides a clean modular architecture inspired by R's
tempdisagg, with modern additions:
- 📈 Regression + autoregressive adjustment
- 📉 Differencing & smoothing interpolators
- 🤖 Ensemble prediction engine
- 🧠 Intelligent padding & interpolation
- 🔧 Post-estimation fix for negative values
- 🔄 Optional retropolarization via regression
🔍 Why Temporal Disaggregation?
Official indicators often come in low frequency (e.g. yearly GDP), while economic analysis or forecasting needs monthly or quarterly granularity. tempdisagg fills this gap using consistent econometric techniques to create granular estimates that respect original aggregates.
📚 Methods Implemented
| Method(s) | Description |
|---|---|
ols |
Ordinary Least Squares (baseline) |
denton |
Denton interpolation with differencing |
denton-cholette |
Cholette smoother variant from Dagum & Cholette |
chow-lin, chow-lin-opt, chow-lin-ecotrim, chow-lin-quilis |
Regression + AR(1) residual modeling |
litterman, litterman-opt |
Random walk / AR(1) prior models |
fernandez |
Fixed-ρ Litterman (ρ = 0) |
fast |
Fast approximation of Denton-Cholette |
uniform |
Even distribution across subperiods |
💾 Installation
pip install tempdisagg
🚀 Quick Example
from tempdisagg import TempDisaggModel
import pandas as pd
import numpy as np
# Sample input data (monthly disaggregation of yearly total)
df = pd.DataFrame({
"Index": [2020]*12 + [2021]*12,
"Grain": list(range(1, 13)) * 2,
"y": [1200] + [np.nan]*11 + [1500] + [np.nan]*11,
"X": np.linspace(100, 200, 24)
})
# Fit model
model = TempDisaggModel(method="chow-lin-opt", conversion="sum")
model.fit(df)
# Predict high-frequency series
y_hat = model.predict()
# Adjust negatives (if any; OPTIONAL)
y_adj = model.adjust_output()
# Show results
model.summary()
model.plot()
⚡ Example with Real Data
import statsmodels.api as sm
from tempdisagg import TempDisaggModel
# Load macroeconomic dataset (quarterly)
macro = sm.datasets.macrodata.load_pandas().data
macro["Index"] = macro["year"].astype(int)
macro["Grain"] = macro["quarter"].astype(int)
macro["X"] = macro["realcons"]
# Aggregate GDP to annual level
gdp_annual = macro.groupby("Index")["realgdp"].mean().reset_index()
gdp_annual.columns = ["Index", "y"]
# Merge back into full frame
df = macro.merge(gdp_annual, on="Index", how="left")[["Index", "Grain", "y", "X"]]
# Fit model and predict
model = TempDisaggModel(method="chow-lin-opt", conversion="average")
model.fit(df)
# Get high-frequency estimates
y_hat = model.predict(full=False)
# Optional: post-estimation adjustment
y_adj = model.adjust_output(full=False)
# Summary and plot
model.summary()
model.plot()
🤖 Ensemble Prediction
Run all models and let the library find the optimal weighted combination.
model = TempDisaggModel(method="ensemble", conversion="sum")
model.fit(df)
model.summary()
model.plot()
Behind the scenes:
- Each method is fitted separately.
- Error metrics (e.g. MAE) are computed.
- Weights are optimized to minimize global error.
- Final prediction is a weighted average across models.
🚫 Negative Value Adjustment
When disaggregation outputs negatives (due to smoothing or regression noise), tempdisagg can correct them without violating consistency.
model.fit(df)
y_hat = model.adjust_output()
Internally:
- Detects negatives in each group.
- Redistributes values proportionally.
- Ensures aggregate values match original data.
🧠 Retropolarizer: Smart Interpolation
For missing values in the target (y), you can activate the Retropolarizer: a module that imputes via regression, proportions, or exponential smoothing.
from tempdisagg import Retropolarizer
retro = Retropolarizer(df = data, new_col = "new", old_col = "old")
df["y_imputed"] = retro.retropolarize(method='proportion')
Or use it inside any model:
model = TempDisaggModel(
method="chow-lin",
use_retropolarizer=True,
retro_method="linear_regression"
)
model.fit(df)
Available methods:
- 'proportion'
- 'linear_regression'
- 'polynomial_regression'
- 'exponential_smoothing'
- 'mlp_regression'
Note: The Retropolarizer is only used to impute missing values in the
ycolumn. It is not intended for interpolating theX(indicator) variable.
📘 Input Format
Your data must be in long format:
| Column | Meaning |
|---|---|
Index |
Low-frequency group ID (e.g., year) |
Grain |
High-frequency unit (e.g., month number) |
y |
Target variable (repeated within group) |
X |
Indicator variable at high frequency |
Index | Grain | y | X
------|-------|-------|-----
2020 | 1 | 1000 | 10.1
2020 | 2 | 1000 | 11.3
2020 | 3 | 1000 | 12.5
... | ... | ... | ...
🧩 Modular Design
| Component | Role |
|---|---|
TempDisaggModel |
High-level interface |
DisaggInputPreparer |
Input validation + padding + interpolation |
ModelsHandler |
Implements disaggregation methods |
RhoOptimizer |
Optimizes AR(1) parameter |
PostEstimation |
Adjusts negative values |
EnsemblePrediction |
Combines multiple models |
Retropolarizer |
Regression-based imputer for y |
🧪 Testing & Reliability
- ✅ Full test coverage
- ✅ Input validation & fallbacks
- ✅ Padding & missing data supported
- ✅ Consistency validation
C @ y_hat ≈ y_l
🔍 API Overview
| Method | Description |
|---|---|
.fit(df) |
Fit the model |
.predict(full=True) |
Predict disaggregated values |
.adjust_output(full=True) |
Fix negative predictions |
.summary(metric="mae") |
Print coefficients, rho, and errors |
.plot(use_adjusted=False) |
Visualize predictions |
.get_params() / .set_params() |
Get/set model config |
.to_dict() |
Export results |
📦 Dependencies
tempdisagg relies on the following Python libraries:
pandas– data manipulationnumpy– numerical operationsmatplotlib– plottingscipyandstatsmodels– regression and optimizationscikit-learn– used inRetropolarizer(e.g., MLP imputation)
These packages are automatically installed with
pip install tempdisagg.
📄 Cite this work
This project is accompanied by a scientific publication available on arXiv:
Jaime A. Jaramillo-Vera. (2025). tempdisagg: A Python Library for Temporal Disaggregation of Time Series. arXiv:2503.22054 [econ.EM].
🔗 View on arXiv
If you use this library in your research, please cite the paper to support the development and visibility of the project.
@misc{verajaramillo2025tempdisagg,
title = {tempdisagg: A Python Library for Temporal Disaggregation of Time Series},
author = {Jaime A. Jaramillo-Vera},
year = {2025},
eprint = {2503.22054},
archivePrefix = {arXiv},
primaryClass = {econ.EM},
url = {https://arxiv.org/abs/2503.22054}
}
📚 References
- Dagum & Cholette (2006), Benchmarking, Temporal Distribution, and Reconciliation Methods
- Denton (1971), Adjustment of Monthly or Quarterly Series
- Chow & Lin (1971), Best Linear Unbiased Estimation of Missing Observations
- Fernández (1981), Methodological Note on a Monthly Indicator
- Litterman (1983), A Random Walk, Markov Model for Forecasting
- tempdisagg (R package)
📃 License
MIT License — See LICENSE for details.
Developed and maintained by Jaime Vera-Jaramillo — Contributions are welcome ❤️.
Metadata
Release files for tempdisagg 0.2.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| tempdisagg-0.2.13.tar.gz | 38.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tempdisagg-0.2.13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.5 kB
Release files / tempdisagg-0.2.13.tar.gz
| Download URL | tempdisagg-0.2.13.tar.gz |
|---|---|
| Size | 38.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fa2f028d2cffb95f3885953ecf74c7f1d5c2bc133edbafe31029f784b890ab87
|
|
BLAKE2b-256 checksum How to use checksums |
476c9dd1973ad74c96abc465a3e9139202db81e96eef0054a6306d4af596622a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.10.11
|
Release files / tempdisagg-0.2.13-py3-none-any.whl
| Download URL | tempdisagg-0.2.13-py3-none-any.whl |
|---|---|
| Size | 47.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bae2c00b2b30dc9d6b1ad614095147c545128c73cf5408c9d9a1d5fd3bff7733
|
|
BLAKE2b-256 checksum How to use checksums |
0d25f17a759b4467ff7cceaa6b672c1230fd7f7da1390199ffbe06fd89a216ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.10.11
|
Security for tempdisagg 0.2.13
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.