Blog · Updated
Agents that decide under uncertainty: using forecast quantiles, not just the median
Short answer
A forecast's median is the wrong number for most decisions. If running short costs more than having too much, you should plan to a higher quantile: the critical ratio, underage cost divided by underage plus overage cost, tells you which one. This post explains quantiles plainly and works through three decisions an agent can make from them: a stock level, a capacity alert and a budget range, with Python.
Key facts
- The q quantile of a forecast is a value the actual should fall below with probability q: the 0.9 quantile is exceeded about 1 time in 10.
- The band from the 0.1 to the 0.9 quantile is an 80% prediction interval; if actuals land inside it about 80% of the time, the forecast is calibrated.
- For a one-off stocking decision, the best quantity is the quantile at the critical ratio: underage cost / (underage cost + overage cost). Equal costs give 0.5, the median.
- A bakery loaf that costs $1.50 to make and sells for $4.00, with unsold loaves thrown away, has a critical ratio of 2.50 / (2.50 + 1.50) = 0.625.
- Quantiles do not add up: the sum of seven daily 0.9 quantiles is not the 0.9 quantile of the weekly total. Forecast at the level you decide at.
- Ephemeris accepts up to 21 quantile levels per request, each strictly between 0 and 1, and the price formula has no term for the number of quantiles.
What is a quantile forecast, in plain terms?
A quantile forecast gives you several lines, each with a probability attached, instead of one line.
Say tomorrow's forecast for loaves sold has a 0.1 quantile of 96, a 0.5 quantile of 118 and a 0.9 quantile of 147. That reads as: there is a 10% chance of selling fewer than 96, a 50% chance of fewer than 118, and a 90% chance of fewer than 147. The 0.5 quantile is the median.
The gap between the 0.1 and 0.9 lines is the uncertainty. A narrow band means the model is fairly sure; a wide band means it is not. Both are useful information.
Why does a single-line forecast lead to bad decisions?
Because a single line hides the cost of being wrong, and the two directions of being wrong rarely cost the same.
Run out of bread and you lose the margin on every customer you turn away. Bake too much and you lose only the cost of the extra loaves. If you always bake the median, you will run short half the time, which is a poor trade when running short is the more expensive mistake.
The same holds for servers (an outage costs more than an idle machine), for cash (a missed payroll costs more than idle cash) and for staffing. In each case the right plan sits above or below the median, and only the quantiles tell you how far.
How do I choose a stock level from the quantiles?
Stock the quantile at the critical ratio. This is the newsvendor rule from inventory theory (Wikipedia: newsvendor model).
Define two costs per unit:
- Underage cost: what you lose for each unit you are short. For a product you sell, the lost margin: price minus cost.
- Overage cost: what you lose for each unit left over. For a perishable with no resale, its cost; subtract any salvage value.
The critical ratio is underage / (underage + overage). Stock the forecast quantile at that level.
Worked example. A loaf costs $1.50 to make and sells for $4.00; unsold loaves are thrown away.
- Underage cost: $4.00 - $1.50 = $2.50.
- Overage cost: $1.50.
- Critical ratio: 2.50 / (2.50 + 1.50) = 2.50 / 4.00 = 0.625.
So bake to the 0.625 quantile of tomorrow's demand. If leftover loaves could be sold the next day at $1.00, the overage cost would fall to $1.50 - $1.00 = $0.50, the ratio would rise to 2.50 / 3.00 = 0.833, and you would bake more.
This rule assumes a single period, so forecast exactly that period: one step of daily demand to decide tomorrow's bake.
How do I request the right quantiles?
Ask for the levels your decisions need, in the quantiles field. Ephemeris accepts up to 21 levels, each strictly between 0 and 1.
For the bakery, ask for 0.625 directly rather than reading between 0.6 and 0.7. For general use, a grid such as [0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95] covers most decisions. The tails (very low or very high levels) rest on fewer observed extremes, so check them on a backtest before you rely on them.
The response maps each level, as a decimal string, to horizon values:
{
"forecasts": [
{ "quantiles": { "0.1": [96.2], "0.5": [118.0], "0.625": [126.4], "0.9": [147.1] } }
],
"meta": { "models_used": ["..."], "billing": { "settled_mc": "...", "balance_mc": "..." } }
}Those numbers are hypothetical, for illustration.
What does the code look like?
Here is the bakery decision end to end in Python. It reads the history from a file, requests tomorrow's quantiles and rounds the stock level up to whole loaves.
import json
import math
import os
import requests
URL = "https://ephemeris.cascade.industries/api/v1/forecast"
HEADERS = {"Authorization": f"Bearer {os.environ['EPHEMERIS_API_KEY']}"}
price, unit_cost, salvage = 4.00, 1.50, 0.00
underage = price - unit_cost # 2.50 lost per loaf short
overage = unit_cost - salvage # 1.50 lost per loaf left over
critical_ratio = round(underage / (underage + overage), 3) # 0.625
with open("daily_loaves.json") as f:
history = json.load(f) # list of daily sales, oldest first
resp = requests.post(
URL,
headers=HEADERS,
json={
"mode": "ensemble",
"series": [{"values": history, "freq": "D"}],
"horizon": 1,
"quantiles": [0.1, 0.5, critical_ratio, 0.9],
},
timeout=60,
)
resp.raise_for_status()
# Keys are decimal strings; convert them to floats to look levels up safely.
quantiles = {float(k): v for k, v in resp.json()["forecasts"][0]["quantiles"].items()}
bake = math.ceil(quantiles[critical_ratio][0])
low, mid, high = quantiles[0.1][0], quantiles[0.5][0], quantiles[0.9][0]
print(f"Bake {bake} loaves (median demand {mid:.0f}, 80% range {low:.0f} to {high:.0f})")With the hypothetical response above, the 0.625 quantile is 126.4, so the script bakes 127 loaves, against a median of 118.
ensemble mode is used because calibration matters for this decision; route mode runs fewer models and costs less.
How do I set a capacity alert from a forecast?
Alert when the upper quantile crosses the limit, not when the median does.
Say disk usage is forecast hourly and the limit is 85%. If the 0.9 quantile first reaches 85% at hour 30, the forecast gives at least a 10% chance that usage is above the limit at that hour. That is usually worth a ticket even if the median stays at 70%.
LIMIT = 85.0
p90 = quantiles[0.9] # from a request with "freq": "H" and "horizon": 48
first = next((step for step, value in enumerate(p90, start=1) if value >= LIMIT), None)
if first is not None:
print(f"At least a 10% chance of passing {LIMIT}% in {first} hours")Pick the level from the costs again. If a full disk is very expensive and a false alert is cheap, alert on 0.95 instead. More on ops metrics in capacity planning from ops metrics.
How do I give a budget range instead of a single number?
Report the 0.1 to 0.9 band at the level the budget is set, and say what it means.
Hypothetical example: next month's cloud spend has quantiles 0.1 = $41,000, 0.5 = $46,500 and 0.9 = $54,000. The agent should say: "Most likely around $46,500. There is about an 80% chance it lands between $41,000 and $54,000. The upside is wider than the downside: $7,500 above the median against $5,500 below." Plan to $54,000 if an overrun is costly; plan to $46,500 if overruns are easy to absorb.
Why can't I add up quantiles across days?
Because the 0.9 quantile of a total is not the total of the 0.9 quantiles.
Adding seven daily 0.9 quantiles assumes every day lands high together. Usually some days are high and some low, so the true 0.9 quantile of the week is usually lower than that sum (they match only if the days move perfectly in step, and with very heavy-tailed data the gap can even go the other way). If you decide weekly, forecast a weekly series (aggregate the history to weekly totals and set freq to "W") and read the quantile directly.
FAQ
Which forecast quantile should I use for inventory?
Use the quantile at the critical ratio: underage cost divided by underage plus overage cost. If being short costs $2.50 a unit and a leftover costs $1.50, use the 0.625 quantile.
What is the difference between a quantile and a prediction interval?
A quantile is one line: the value the actual should fall below with a given probability. A prediction interval is the band between two quantiles; the 0.1 and 0.9 quantiles bound an 80% interval.
How many quantiles can I request from Ephemeris?
Up to 21 levels per request, each strictly between 0 and 1. The number of quantiles does not appear in the price formula.
Why not just use the median forecast?
The median is the right plan only when being over and being under cost the same. When running short costs more, the median leaves you short about half the time.
Can I sum daily quantiles to get a weekly range?
No. The sum of daily 0.9 quantiles is not the weekly 0.9 quantile; it usually overstates it. Forecast weekly totals instead.