Full Distribution Forecast

A full distribution describes the complete probability law of a random variable rather than a point summary or a handful of quantiles. Any real-valued \(X\) is characterized by its cumulative distribution function \(F(x) = P(X \leq x)\); a continuous variable can also be written as a density \(f(x)\), and a discrete one as a mass function \(p(x) = P(X = x)\).

A full distribution forecast reports that object for a future outcome. From it you can derive, when the relevant quantities exist: the mean, the median and any other quantile, prediction intervals, event probabilities such as \(P(Y > 100)\), and risk measures such as value-at-risk and expected shortfall.

The contrast is with two weaker outputs. A point forecast gives one value and does not express its own uncertainty. A quantile forecast gives selected cut points — the 10th, 50th and 90th, say — and describes only part of the distribution. That phrase is usually left as a caveat, so it is worth making it exact.

What the whole object buys, and what three cut points lose

import numpy as np
from scipy.stats import lognorm

mu, sigma = 6.2, 0.3
F = lognorm(s=sigma, scale=np.exp(mu))
N = 2_000_000
u = (np.arange(N) + 0.5) / N
"""A full predictive distribution for next week's demand. Every number below
   is read off this one object, by integrating its quantile function."""

print(f"  LogNormal(mu={mu}, sigma={sigma}) on the log scale:")
print(f"    mean                 {F.mean():>10.2f}")
print(f"    median               {F.median():>10.2f}")
print(f"    mode                 {np.exp(mu - sigma ** 2):>10.2f}")
print(f"    90th percentile      {F.ppf(0.90):>10.2f}")
print(f"    P(demand > 700)      {F.sf(700):>10.4f}")
print(f"    95% value at risk    {F.ppf(0.95):>10.2f}")
print(f"    expected shortfall   {F.ppf(u[u > 0.95]).mean():>10.2f}   (the mean beyond it)")

cut = 0.95
base = F.ppf(cut)
def heavy(u):
    out = F.ppf(u)
    t = u > cut
    v = (u[t] - cut) / (1 - cut)
    out[t] = base * (1 + 9 * v ** 3)
    return out
"""A second distribution identical to the first below its 95th percentile
   and heavier above it. Nothing a 10/50/90 forecast reports can see it."""

H = heavy(u)
G = F.ppf(u)
print(f"\n  a second distribution, identical below the 95th percentile:")
print(f"{'':>22}{'10th':>9}{'50th':>9}{'90th':>9}{'P(>700)':>10}{'mean':>10}"
      f"{'ES(95%)':>11}{'P(>2000)':>11}")
for name, s in (("the lognormal", G), ("the heavy-tailed one", H)):
    print(f"{name:>22}{np.quantile(s, 0.10):>9.2f}{np.quantile(s, 0.50):>9.2f}"
          f"{np.quantile(s, 0.90):>9.2f}{np.mean(s > 700):>10.4f}{s.mean():>10.2f}"
          f"{s[s > np.quantile(s, 0.95)].mean():>11.2f}{np.mean(s > 2000):>11.4f}")
print("  the three quantiles agree to the second decimal, and so does P(>700).")
print(f"  The mean differs by {H.mean() - G.mean():.2f} units, the expected shortfall by a factor"
      f" of {H[H > np.quantile(H, 0.95)].mean() / G[G > np.quantile(G, 0.95)].mean():.2f},")
print("  and the lognormal probability past 2000 is positive but rounds to zero")

print(f"\n  which is the point. From a 10/50/90 forecast you cannot compute:")
print("    the mean            - it is an integral over the whole line")
print("    any tail risk       - the region past the last quantile is unreported")
print("    P(Y > 900)          - 900 is past the last cut point, so all the")
print("                          forecast says is that the answer is under 0.10")
print("  From the distribution, all of them are one function call")
#   LogNormal(mu=6.2, sigma=0.3) on the log scale:
#     mean                     515.43
#     median                   492.75
#     mode                     450.34
#     90th percentile          723.76
#     P(demand > 700)          0.1209
#     95% value at risk        807.11
#     expected shortfall       920.93   (the mean beyond it)
#
#   a second distribution, identical below the 95th percentile:
#                            10th     50th     90th   P(>700)      mean    ES(95%)   P(>2000)
#          the lognormal   335.47   492.75   723.76    0.1209    515.43     920.93     0.0000
#   the heavy-tailed one   335.47   492.75   723.76    0.1209    600.54    2623.10     0.0226
#   the three quantiles agree to the second decimal, and so does P(>700).
#   The mean differs by 85.11 units, the expected shortfall by a factor of 2.85,
#   and the lognormal probability past 2000 is positive but rounds to zero
#
#   which is the point. From a 10/50/90 forecast you cannot compute:
#     the mean            - it is an integral over the whole line
#     any tail risk       - the region past the last quantile is unreported
#     P(Y > 900)          - 900 is past the last cut point, so all the
#                           forecast says is that the answer is under 0.10
#   From the distribution, all of them are one function call

Reading the two halves

The first block is the retail example in full. Next week’s demand is LogNormal with \(\mu = 6.2\) and \(\sigma = 0.3\) on the log scale, and from that one statement come a mean of about 516 units, a 90th percentile near 724, and \(P(\text{demand} > 700) \approx 0.12\) — none of which had to be forecast separately. The same holds for weather: “tomorrow’s temperature is Normal with \(\mu = 25\) and \(\sigma = 2\)” contains the point forecast of 25 °C and the 10th and 90th percentiles of roughly 22.4 and 27.6 as consequences.

The second block is what “only part of the distribution” means when you try to use it. Two distributions agreeing exactly below the 95th percentile report the same 10th, 50th and 90th, and the same \(P(Y > 700)\), while their means differ by 85 units and their expected shortfalls by a factor of 2.85. A quantile forecast is silent about everything past its last cut point, and tail risk lives entirely in that region.

The mean is the cleaner demonstration, because it is not a tail quantity at all. It is an integral over the whole line, so no finite set of quantiles determines it. Any pipeline that reports quantiles and then needs an expected value is silently assuming a shape.

How they are produced, and what they cost

Parametric models assume a family and forecast its parameters. Ensemble and simulation methods generate many plausible paths and use the empirical distribution of the results. Among neural approaches, DeepAR models the parameters of a chosen predictive likelihood, mixture density networks predict the parameters of a mixture, normalizing flows learn flexible densities directly, and temporal fusion transformers commonly produce quantile forecasts — a full predictive distribution only when paired with a distributional output layer.

Evaluation has to cover calibration and sharpness together, which is what CRPS and the logarithmic score do. Both are proper, so their expected values are optimized by reporting your true predictive distribution. Alongside them, check whether predicted intervals contain the outcome at their stated rates over many forecasts.

The cost is the modeling choice itself. Requiring a whole distribution demands a stronger assumption than requiring a mean, it is harder to estimate, and the part that matters most — the tail — is the part the data constrains least. The retail example also carries a modeling caveat worth keeping: if demand has to be whole units, a discrete count distribution fits the quantity better than a continuous LogNormal, however well the LogNormal describes the shape.

References


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.