ahead (Time Series Forecasting with uncertainty quantification) gets a lot faster to install: most dependencies are now optional

[This article was first published on T. Moudiki's Webpage - R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

If you’ve used ahead before, you may remember install.packages("ahead") or pip install ahead taking a while, since it dragged in two dozen modeling packages up front regardless of which one you actually planned to use. That’s fixed now.

The problem: one function’s dependency, everyone’s install time

ahead supports a lot of forecasting methods, and each one can lean on a different modeling package: forecast, randomForest, e1071, glmnet, gam, quantreg, vars, fGarch, VineCopula, mboost, ranger, ForecastComb. Historically, most of these were plain Imports in DESCRIPTION, which means R installs all of them before you can even load the package — regardless of whether you plan to use ahead::dynrmf with a random forest or ahead::ridge2f for a multivariate model with none of the above.

The fix: (almost) everything becomes a Suggests

The new DESCRIPTION keeps only what the package’s core code actually needs at load time:

Imports:
    Rcpp (>= 1.0.6),
    foreach,
    tseries
Depends: R (>= 3.5.0)
Suggests:
    caret, cclust, dfoptim, doSNOW, doParallel, knitr, rmarkdown,
    testthat, fpp2, glmnet, e1071, gam, quantreg, randomForest,
    spatial, vars, roxygen2, ForecastComb, ranger, mboost, misc,
    Mcomp, fGarch, VineCopula, forecast (>= 8.0),
    ggplot2 (>= 3.0.0), randtoolbox (>= 1.17), simulatetimeseries

Everything else — randomForest, e1071, forecast, glmnet, vars, fGarch, VineCopula, and the rest — moved to Suggests. That means R installs in seconds, because Rcpp, foreach, and tseries are the only hard requirements.

Installing the missing piece, exactly when you need it

Moving packages to Suggests only helps if the package still works smoothly when you actually call a function that needs one of them. That’s what a small internal helper, check_suggested(), takes care of:

check_suggested <- function(pkg, ask = interactive()) {
  if (requireNamespace(pkg, quietly = TRUE)) {
    return(invisible(TRUE))
  }
  do_install <- TRUE
  if (ask) {
    do_install <- utils::askYesNo(
      sprintf("Package '%s' is required but not installed. Install it now?", pkg)
    )
    do_install <- isTRUE(do_install)
  }
  if (do_install) {
    utils::install.packages(
      pkg,
      repos = c("https://techtonique.r-universe.dev", "https://cloud.r-project.org")
    )
  }
  if (!requireNamespace(pkg, quietly = TRUE)) {
    stop(
      sprintf(
        "Package '%s' is required. Install it with install.packages('%s', repos = c('https://techtonique.r-universe.dev', 'https://cloud.r-project.org')).",
        pkg, pkg
      ),
      call. = FALSE
    )
  }
  invisible(TRUE)
}

Every function (hopefully all of them!) that relies on a suggested package now calls check_suggested("that_package") first. In practice, this means:

  • If the package is already installed, nothing changes — no prompt, no delay.
  • If it’s missing and you’re in an interactive session, you get asked before anything is installed on your behalf.
  • If it’s missing and the session isn’t interactive (a script, a CI job), it installs automatically.
  • If the install still fails, you get a clear error with the exact command to run, pointing at both CRAN and Techtonique’s r-universe repository — useful since a couple of these dependencies (like ForecastComb and misc) aren’t on CRAN at all.

So install.packages("ahead") is fast, and the first time you call dynrmf(..., fit_func = randomForest::randomForest), randomForest gets installed for you, once, and never again after that.

Same idea, carried into the Python wrapper

ahead’s Python package is a wrapper around the R package (via rpy2), so the first call to any forecaster has always installed R-side dependencies on demand — that’s the “might take some time, but ONLY the 1st time it’s called” comment you’ll see next to DynamicRegressor or Ridge2Regressor in the examples. With the R package’s own install now lean, that first-call overhead on the Python side shrinks too: there’s less to pull in before check_suggested() even gets to the package your chosen method needs.

import os
import numpy as np
import pandas as pd
from ahead import DynamicRegressor, EAT
from time import time

# Forecasting horizon
h = 25

# Data frame containing the time series
df = pd.read_csv("https://raw.githubusercontent.com/Techtonique/datasets/refs/heads/main/time_series/univariate/AirPassengers.csv").set_index('date')
df.index = pd.DatetimeIndex(df.index)
print(df)

# univariate ts forecasting
print("Example 1 -----")
d1 = DynamicRegressor(h=h, date_formatting="ms")
print(d1.__module__)

start = time()
d1.forecast(df)
print(f"Elapsed: {time()-start} \n")
print("averages: \n")
print(d1.averages_)
print("\n")
print("ranges: \n")
print(d1.ranges_)
print("\n")

print("Example 2 -----")
d2 = DynamicRegressor(h=h, type_pi="T", date_formatting="original")
start = time()
d2.forecast(df)
print(f"Elapsed: {time()-start} \n")
print("averages: \n")
print(d2.averages_)
print("\n")
print("ranges: \n")
print(d2.ranges_)
print("\n")

d2.plot()

The first call to DynamicRegressor.forecast() in a fresh environment is the one that pays the (now much smaller) one-time cost of installing the R ahead package; every call after that — including d2 above, in the same session — runs at full speed.

Why this matters in practice

  • Faster CI and Docker builds — you’re not compiling gam, fGarch, VineCopula, and a dozen others just to run a couple of unit tests that use two of them.
  • Less fragile installs — a slow-to-compile or awkward-to-build modeling package no longer stands between you and a working ahead install if you don’t even need that method.
  • Smaller footprint for the methods you actually use — if you only ever call ridge2f, you never install the GARCH or copula-based dependencies at all.

If you want everything available up front (for example, to prepare an offline environment), you can still install every suggested package yourself:

install.packages(
  c("caret", "cclust", "dfoptim", "doSNOW", "doParallel", "fpp2",
    "glmnet", "e1071", "gam", "quantreg", "randomForest", "spatial",
    "vars", "ranger", "mboost", "Mcomp", "fGarch", "VineCopula",
    "forecast", "ggplot2", "randtoolbox", "simulatetimeseries"),
  repos = c("https://techtonique.r-universe.dev", "https://cloud.r-project.org")
)

Get it

  • R: see the README for the Techtonique repo setup, version 0.38.1.
  • Python: pip install ahead --verbose, from ahead_python.

As always, issues and contributions are welcome on both repos.

image-title-here

To leave a comment for the author, please follow the link and comment on their blog: T. Moudiki's Webpage - R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)