PyAF: automatic time series forecasting without model selection
PyAF is an Open Source Python library for Automatic Time Series Forecasting built on top of popular pydata modules.
At a glance
- What is it?
- PyAF is a BSD-3-Clause Python library that turns a pandas time series into a forecast by searching over transformations, trends, periodic components and AR models, then picking the decomposition that performs best on a held-out slice. It is convenient when you want a working baseline quickly and less convenient when you need control over the model that produced the number.
- Who is it for?
- Adopt PyAF if you have a pandas data frame with a time column and a signal column and you want a defensible baseline forecast without hand-building a model. Do not adopt it if you need to reason about the fitted coefficients, if your horizon is long relative to the history, or if you need a probabilistic model whose assumptions you can state.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 81 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem PyAF removes is model selection, not forecasting
Most forecasting work in Python starts the same way: you have a pandas data frame with a date column and a value column, and you need numbers for the next N periods. The hard part is rarely the fitting call. It is deciding whether the series needs a log transform, whether the trend is linear or piecewise, how many seasonal harmonics to include, and whether an autoregressive term earns its place. PyAF's answer is to stop deciding. According to the README, the library works as a competition between a comprehensive set of possible signal transformations and linear decompositions. For each transformed signal it generates a set of possible trends, periodic components and AR models, estimates all the combinations, and keeps the decomposition with the best performance on data that was not used for estimation. The intended user is someone who wants a reasonable forecast without running that search by hand. It is not aimed at someone who needs to explain each coefficient to a reviewer.
How the search works: transform, decompose, validate
The pipeline has three visible stages. First, the signal is transformed. The README states that four transformations are supported by default and that others such as Box-Cox are available. Second, the transformed signal is decomposed as the sum of a trend, a periodic component and an AR component, which is the classical decomposition referenced in the documentation. Third, every candidate combination is estimated and scored. The estimation is not bespoke: trend regressions and AR/ARX models are fitted with scikit-learn linear regression models, and the README lists L1, RMSE, MAPE, MedAE and LnQ among the performance measures. The selection signal is a holdout. The README says the signal is cut into estimation and validation parts at 80% and 20%, and that time-series cross-validation is also available. That split matters more than it looks. Because the model is chosen on a slice the fit never saw, the reported error is not simply the training residual, though a single 20% holdout on a short series is still a thin basis for choosing among many candidates.
Getting a forecast: the cForecastEngine calls
The README example is short enough to quote in structure. You build a pandas frame with a Date column and a Signal column, instantiate the engine with autof.cForecastEngine(), then call lEngine.train(iInputDS=df_train, iTime='Date', iSignal='Signal', iHorizon=7). The keyword names are the API: iInputDS for the training frame, iTime for the time column, iSignal for the value column, iHorizon for how far ahead to predict. Training is followed by lEngine.getModelInfo(), which in the example prints a relative error of 7% MAPE, and then by df_forecast = lEngine.forecast(iInputDS=df_train, iHorizon=7). The return value is itself a pandas data frame. The README shows that df_forecast['Date'].tail(7).values holds the future dates and df_forecast['Signal_Forecast'].tail(7).values holds the predicted values, so the forecast is aligned with the input by construction rather than by a separate index you have to reconstruct. The same example is available as a notebook at docs/sample_code.ipynb. One thing the README does not state is how the engine behaves when train() is called twice on the same object or how much state it retains; that is outside the supplied material.
Frequency inference is convenient and is also the weakest link
PyAF reads the time column and infers a frequency rather than requiring one. The README says the frequency is computed as the mean duration between consecutive observations by default, expressed as a pandas DateOffset, and that it is then used to generate future dates automatically. Minute, hour, day, week and month frequencies are named as natural cases, and irregular spacings such as every 3.2 days or every 17 minutes are said to be supported if the data are recorded that way. Real and integer valued dates are handled similarly. The trade-off is stated plainly in the documentation itself: PyAF does its best when dates are not regularly observed, and the time frequency is approximate in that case. That approximation propagates. If the mean gap between observations is not the gap you care about, the generated future dates drift, and a forecast that is correct in value but placed on the wrong dates is wrong in practice. Anyone with gappy or event-driven timestamps should verify the inferred offset before trusting the output frame.
Exogenous variables and hierarchies extend the model, with conditions
Exogenous inputs are supplied as a separate pandas data frame that PyAF merges with the training frame, and they enter the model through their past values in an ARX formulation. The README states that exogenous variables can be numeric, string, date or object, that non-numeric types are dummified and numeric types are standardized. The past-values detail is the constraint that matters. An exogenous series only helps if its history is available at forecast time in the same alignment as the training data, so a variable you only learn about at the moment of prediction cannot be used this way. Hierarchical forecasting is the other extension. The README credits the approach in the Hyndman and Athanasopoulos book and says both hierarchies and grouped time series are supported, with spatial components aggregated into a total. That is a genuine capability, but the material does not describe the reconciliation method or how the engine reports accuracy at each level of the hierarchy, so a reader with a multi-level problem should inspect the source or the notebook rather than assume the behaviour.
Where PyAF is the wrong tool
The design that makes PyAF easy to start with is the same design that makes it hard to audit. Model selection happens inside train(), and the chosen decomposition is reported as a score rather than as a specification you wrote. If your process requires a named model, an interpretable coefficient, or a documented assumption about the error distribution, the automatic search works against you. The 80/20 split is a second limit. On a series with a few hundred points and a strong seasonal cycle, a single holdout can reward a decomposition that happens to fit that particular validation window, and the README does not state how many candidates are compared or how ties are broken. Third, the horizon is a training argument, so the model is selected with a specific forecast distance in mind; asking the same fitted engine for a much longer horizon is not a scenario the documentation covers. Finally, the release cadence is visible in the repository metadata: 3.0 in July 2021, 4.0 in July 2022, 5.0 in July 2023. A project on an annual release rhythm is not abandoned, but it is also not moving quickly, and you should expect to read the source when behaviour is undocumented.
The alternative most teams already have installed
The obvious comparison is statsmodels, which is probably already in the environment alongside pandas. The difference is in who chooses the model. With statsmodels you write the specification: an ARIMA order, an exponential smoothing configuration, an explicit seasonal period, and you inspect the fitted parameters and diagnostics. PyAF inverts that. You supply columns and a horizon, and the library searches transformations, trends, periodic terms and AR structures, then reports the winner by validation error. Neither approach dominates. If you need to justify a specific lag structure or produce residual diagnostics for a report, statsmodels gives you the objects to do it and PyAF gives you a score. If you have many series and no time to specify each one, the search is the point. PyAF also sits closer to scikit-learn in its estimation, since the README states that trend regressions and AR/ARX models are estimated with scikit-learn linear regression, which means the dependency footprint overlaps with a stack you may already maintain.
Licence, maintenance and what to check before adopting
PyAF is distributed under the 3-Clause BSD license, which the README links to the tldrlegal summary of. That is a permissive licence and it does not carry the copyleft obligations of a GPL-style licence, but the terms that apply to your distribution are a question for your own legal review, not something a README settles. On maintenance, the material supports only a narrow statement: the repository is not archived, the default branch is master, the last push is dated 2026-06-26, and the most recent release listed is 5.0 from July 2023. The README also states the library was developed, tested and benchmarked on Python 3.x, so a Python 2 environment is out of scope. Upgrade cost is hard to estimate from this material alone because no changelog is supplied; the jump from 4.0 to 5.0 is a major version number, which conventionally signals breaking changes, but the README does not document any. Before adopting, pin the version you test against, run the README example on one of your own series, and compare the MAPE that getModelInfo() reports against a naive seasonal baseline you compute yourself. If PyAF does not beat that baseline on your data, the search has not earned its place.
Editorial conclusion
Adopt PyAF if you have a pandas data frame with a time column and a signal column and you want a defensible baseline forecast without hand-building a model. Do not adopt it if you need to reason about the fitted coefficients, if your horizon is long relative to the history, or if you need a probabilistic model whose assumptions you can state. Before committing, run the README example on your own data and check two things: what getModelInfo() reports as the relative error, and whether the inferred frequency in the forecast frame matches the spacing your data actually has. If the frequency is wrong, the future dates are wrong, and every downstream number inherits that error.
Community notes