Models
PostForecasts.jl currently provides four models for handling postprocessing of point predictions into probabilistic forecasts:
- Normal error distribution
- Conformal Prediction (and Historical Simulation)
- Isotonic Distributional Regression
- Quantile Regression
Every model belongs to the PostModel
supertype. Models that work exclusively with a single point forecast as a regressor are of type UniPostModel
, while models that support multiple regressors are of type MultiPostModel
.
Normal error distribution
The naive model for probabilistic forecasting, which assumes normally distributed errors of point forecasts.
The predictive distribution conditional on point forecast $\hat{y}$ is a Gaussian $\mathcal{N}(\hat{y} + \mu, \sigma)$, where $\mu$ and $\sigma$ are mean and sample standard deviation of errors in the training window.
The $\tau$-th quantile conditional on $\hat{y}$ of such parameterized distribution can be obtained via an analytic expression:
\[\hat{q}_{\tau|\hat{y}} = \hat{y} + \mu + \sigma \sqrt{2} \cdot \text{erf}^{-1} (2\tau - 1).\]
PostForecasts.Normal
— TypeNormal([type::Type{F}=Float64]; zeromean::Bool=false) where {F<:AbstractFloat}
Creates a Normal{F}<:UniPostModel{F}<:PostModel{F}
model for normal error distribution. Optional keyword argument zeromean
specifies whether to assume a zero mean.
PostForecasts.getmean
— Functiongetmean(m::Normal)
Return the mean of the distribution from model m
.
PostForecasts.getstd
— Functiongetstd(m::Normal)
Return the standard deviation of the distribution from model m
.
Conformal prediction and historical simulation
Conformal Prediction (CP) is a machine learning framework for computing prediction intervals based on the outputs of an arbitrary point forecasting model. The implemented method corresponds to inductive conformal prediction (Papadopoulos et al., 2002) and is analogous to the approach of Kath and Ziel (2021).
In the training step, the non-conformity scores $\lambda_i$ are calculated on the training set $(\hat{y}_i, y_i)_{i\in\text{training window}}$ as $\lambda_i := |\hat{y}_i - y_i|$.
In the prediction step, the $\tau$-th quantile conditional on $\hat{y_t}$ is obtained by shifting the prediction by an appropriate sample quantile of non-conformity scores:
\[\hat{q}_{\tau|\hat{y}} = \hat{y} - \mathbf{1}_{\{\tau < 0.5\}} Q_{1 - 2\tau}(\lambda) + \mathbf{1}_{\{\tau > 0.5\}} Q_{2\tau - 1}(\lambda),\]
where $Q_{\alpha}(\lambda)$ is the $\alpha$-th sample quantile of non-conformity scores from the training window. Although the intervals in the form of $[\hat{y} - Q_{\alpha}(\lambda), \hat{y} +Q_{\alpha}(\lambda)]$ are valid $\alpha$ prediction intervals without any requirements on the underlying distribution, translating them into quantiles requires the assumption of symmetrically distributed errors.
However, it is also possible to use conformal prediction to obtain non-symmetric distributions, by using non-absolute errors $\lambda_i := \hat{y}_i - y_i$. Then, in the prediction step $\tau$-th quantile conditional on $\hat{y_t}$ is computed as:
\[\hat{q}_{\tau|\hat{y}} = \hat{y} + Q_{\tau}(\lambda),\]
the method that predates conformal prediction and is widely known as Historical Simulation (HS) (Hendricks, 1996).
PostForecasts.CP
— TypeCP([type::Type{F}=Float64,] n::Integer[; abs::Bool=true]) where {F<:AbstractFloat}
Creates a CP{F}<:UniPostModel{F}<:PostModel{F}
model for conformal prediction that stores the non-conformity scores of n
observations. Optional keyword argument abs
specifies whether to use absolute errors.
PostForecasts.getscores
— Functiongetscores(m::CP)
Return a vector of non-conformity score values from model m
.
Isotonic distributional regression
Isotonic Distributional Regression (IDR; Henzi et al., 2021) has been recently introduced as a novel nonparametric method for estimating distributions that are isotonic in the regressed variable, which means that the quantiles of such distributions are non-decreasing w.r.t the regressor. In the training step, $n$ observations $(\hat{y}_i, y_i)_{i \in \text{training window}}$ are first sorted to be ascending in $\hat{y}_i$. Then, $n$ conditional distributions $\hat{F_i}(z) = \hat{F}(z|x_i)$ are obtained by solving the following min-max problem via abridged pool-adjacent-violators algorithm:
\[\hat{F}_i(z) = \min_{k=1,...,i} \max_{j=k,..,n} \frac{1}{j-k+1}\sum_{l=k}^{j} \mathbb{1}\{y_{l} < z\},\]
where $z \in (y_i)_{i \in \text{training window}}$ To obtain conditional distribution for any $\hat{y}\in\mathbb{R}$, the obtained distribution functions are interpolated
\[\hat{F}(z|\hat{y}) = \frac{\hat{y}-\hat{y}_i}{\hat{y}_{i+1} - \hat{y}_i}\hat{F}_i(z) + \frac{\hat{y}_{i+1} - \hat{y}}{\hat{y}_{i+1} - \hat{y}_i} \hat{F}_{i+1}(z),\]
If $\hat{y} < \hat{y}_1$ or $\hat{y} > \hat{y}_n$, we set $\hat{F}(z|\hat{y})$ to $\hat{F}_1(z)$ or $\hat{F}_n(z)$, respectively. Finally, since ProbcastSeries stores predictive distributions in the form of quantiles, we determine quantiles at specified levels as
\[\hat{q}_{\tau|\hat{y}} = \min\{z : \hat{F}(z|\hat{y}) \geq \tau\}.\]
The multivariate version of IDR is not supported, but ForecastSeries containing multiple forecasts can be used as input for computing ProbcastSeries. In such a case, multiple univariate IDR models are estimated and the resulting distributions functions $\hat{F}(z)$ are averaged. Since $z$ is limited to true values of the timeseries in training window, the distributions resulting from estimated IDRs are defined at the exact same points, which allows to efficiently and precisely compute the average across probability.
The implemented IDR estimation uses abridged pool-adjacent-violators algorithm introduced by Henzi et al. (2022).
PostForecasts.IDR
— TypeIDR([type::Type{F}=Float64,] n::Integer, r::Integer) where {F<:AbstractFloat}
Creates an IDR{F}<:MultiPostModel{F}<:PostModel{F}
model for isotonic distributional regression to be trained on n
observations with r
forecasts (regressors).
PostForecasts.getcdf
— Functiongetcdf(m::IDR [, r])
Return a vector of cumulative distribution function values from model m
. Optional argument r::Integer = 1
corresponds to the regressor index.
PostForecasts.getx
— Functiongetx(m::IDR [, r])
Return a vector of regressor values from model m
on which cumulative distribution function is defined. Optional argument r::Integer = 1
corresponds to the regressor index.
PostForecasts.gety
— Functiongety(m::IDR)
Return a vector of response values from model m
on which cumulative distribution function is defined.
Quantile regression
Quantile Regression Averaging (QRA; Nowotarski and Weron, 2014) is a well-established method in for obtaining probabilistic forecasts of electricity prices and load. It learns conditional quantiles as linear combination of $m$ point forecasts:
\[\hat{q}_{\tau|\hat{y}^{(1)}, ..., \hat{y}^{(m)}} = \beta^{(\tau)}_0 + \beta^{(\tau)}_1\hat{y}^{(1)} + ... + \beta^{(\tau)}_m\hat{y}^{(m)}.\]
The coefficients $\beta^{(\tau)}_{0...m}$ are selected to minimize the pinball loss on the training window and estimated by solving a linear programming problem. For this task, Probcasts.jl employs JuMP.jl and HiGHS.jl packages. Different LP solvers compatible with JuMP can be used, but the constructor defaults to an open source HiGHS.
Apart from the standard QRA introduced by Nowotarski and Weron (2014), PostForecasts.jl allows to readily compute Quantile Regression Machine (QRM; Marcjasz et al., 2020) and Quantile Regression with probability or quantile averaging (Uniejewski et al., 2019). See Different flavors of quantile regression for details.
PostForecasts.QR
— TypeQR([type::Type{F}=Float64,] n::Integer, r::Integer, prob::Union{AbstractFloat, AbstractVector{<:AbstractFloat}}) where {F<:AbstractFloat}
Creates a QR{F}<:MultiPostModel{F}<:PostModel{F}
model for quantile regression to be trained on n
observations with r
forecasts (regressors), fitting quantiles at probabilities specified by prob
.
PostForecasts.getweights
— Functiongetweights(m::QR)
Return a copy of the weight matrix from model m
.
PostForecasts.getquantprob
— Functiongetquantprob(m::QR)
Return a copy of the vector of probabilities corresponding to the quantiles from model m
.
Regularized quantile regressions
PostForecasts.iQR
— FunctioniQR(args...)
Creates an isotonic quantile regression model, constraining the weights to be non-negative. The arguments args...
are the same as for QR
.
PostForecasts.LassoQR
— TypeLassoQR([type::Type{F}=Float64,] n::Integer, r::Integer, prob::Union{AbstractFloat, AbstractVector{<:AbstractFloat}}, lambda::Union{AbstractFloat, AbstractVector{<:AbstractFloat}}) where {F<:AbstractFloat}
Creates a LassoQR{F}<:MultiPostModel{F}<:PostModel{F}
model for lasso quantile regression with regularization strength lambda
to be trained on n
observations with r
forecasts (regressors), fitting quantiles at probabilities specified by prob
.
If lambda
is a vector, the optimal regularization strength will be selected using the Bayesian Information Criterion (BIC) during every training, separately for each quantile.
By default, lambda
is specified by the package constant LAMBDA = [0.001, 0.01, 0.1, 1.0, 10.0]
. It can be modified with the setlambda
method.
PostForecasts.setlambda
— FunctionSet the values of the package constant LAMBDA
to be equal to lambda
.
PostForecasts.getlambda
— FunctionGet the values stored in the package constant `LAMBDA`.
Training and prediction
PostForecasts.train
— Functiontrain(m, X::AbstractVecOrMat{<:Number}, Y::AbstractVector{<:Number})
Calibrate the model m
on the covariates X
and responses Y
.
In general, X
should be a matrix, which columns correspond to respective regressors. The number of regressors must match the specification of the model.
For m::UniPostModel
, X
can be a vector, if it is a matrix with multiple columns, they will be averaged before training.
PostForecasts.predict
— Functionpredict(m, input, quantiles)
Predict the specified quantiles
of the predictive distribution from model m::PostModel{F}
conditional on input
.
Argument types
input
can be of typeNumber
orAbstractVector{<:Number}
quantiles
can be of typeAbstractFloat
(to return a single prediction) orAbstractVector{<:AbstractFloat}
(to return a vector of predictions)
Note
For m::QR
, omitting quantiles
argument defaults to using all levels specified in model m
, which is recommended. Calling predict(m::QR, input, quantiles)
will first match each quantile to the levels specified in m
, leading to worse performance. The quantile predictions from QR
are sorted to avoid crossing (sorting only includes the levels for which predict
was called).
PostForecasts.predict!
— Functionpredict!(m, output, input, quantiles)
In-place version of predict
that stores the results in the output::AbstractVector{<:AbstractFloat}
vector.
Argument types
input
can be of typeNumber
orAbstractVector{<:Number}
quantiles
needs to be of typeAbstractVector{<:AbstractFloat}
Note
For m::QR
, quantiles
argument is ignored and can be ommited in the function call. The quantile predictions from QR
are automatically sorted to avoid crossing.