Contents
, where , , and are the sorted values
Deep Breakdown
What it tells me (Situation Awareness):
The quantile function answers the inverse of the CDF question: instead of "what fraction of values are below ?", it asks "below which value lies a fraction of the data?". A percentile is the same thing on a 0 to 100 scale (), quartiles split at , deciles at multiples of 0.1, and the median is . Because real samples are discrete, the sample quantile needs an interpolation rule; the default in NumPy, R (type 7) and Excel PERCENTILE.INC places the -quantile at fractional position (0-based) and interpolates linearly between the two neighbouring sorted values. Quantiles are robust, distribution-free descriptors of spread and tails, which is exactly what matters for SLAs and risk. In DA/DS: p50/p95/p99 latency SLOs for APIs and LLM inference; the interquartile range (IQR) and Tukey fences for outlier detection and box plots; quantile binning of features (pd.qcut); Value-at-Risk in finance; quantile regression and prediction intervals; calibrating thresholds ("flag the top 5% riskiest transactions").
Flowchart
Real-world use
Where it is used
API and LLM serving dashboards (p50, p95, p99 latency, time-to-first-token); box plots and Tukey outlier rules; income and price reports (deciles); feature engineering with quantile bins and QuantileTransformer; risk metrics (VaR = loss quantile); quantile regression and conformal prediction intervals; threshold setting for fraud or anomaly scores.
Problem it solves
Means hide tails. An API with mean latency 277 ms can still make one user in ten wait half a second. Business commitments (SLAs, risk limits, capacity planning) are about "how bad does it get for most users", which is a quantile question, not an average question.
How it solves the problem
By sorting the data and reading off the value at a chosen rank, quantiles describe any part of the distribution directly without assuming a shape (Normal or otherwise). Linear interpolation makes the estimate continuous in and well defined for any sample size.
Example in a business
An ML platform team serves an LLM endpoint. Ten request latencies (ms): . Mean ms, median ms, but p95 ms. The SLO "p95 under 500 ms" is violated even though the mean looks healthy, so the team investigates the long-tail requests (very long prompts) and adds a prompt-length based routing rule.
The formula, piece by piece
- : the cumulative distribution function.
- : the quantile function, the generalized inverse of the CDF; handles flat parts and jumps of .
- : target probability (0.95 for p95, 0.25 for Q1).
- : sample size; : the -th smallest value (1-based).
- : fractional 0-based position of the quantile in the sorted sample.
- : integer part, selecting the lower neighbour .
- : fractional part, the weight given to the gap up to the next value.
- : the gap between the two neighbours.
- IQR : spread of the middle 50%.
- Tukey fences: and ; points outside are flagged as outliers.
The formula in words
"Sort the data, walk a fraction of the way from the smallest to the largest position, and if you land between two values, take the proportional point between them." Tiny example: latencies above, : , so the lower neighbour is and the upper is ; ms.
Critical Concepts
- Many definitions exist: Hyndman and Fan list nine sample-quantile rules. NumPy default
method="linear"equals R type 7 and ExcelPERCENTILE.INC; other tools (some SQLPERCENTILE_DISC, ExcelPERCENTILE.EXC) give different answers on small samples. Always state the method when comparing dashboards. - Tail quantiles need data: p99 from 100 requests is essentially the maximum. A rule of thumb: you need well over observations (thousands for p99) for a stable tail estimate; bootstrap the CI.
- Not averageable: the average of hourly p95s is not the daily p95. Aggregate from raw data or from mergeable sketches (t-digest, HDR histograms, KLL).
- Equivariance: for any increasing transform , , so the quantile of log-latency is the log of the latency quantile (the mean has no such property).
- IQR and the Normal: for Normal data , giving the robust scale estimate .
- Inverse-transform link: with has distribution ; this is how random samples are generated from arbitrary distributions.
Step-by-step calculation (tiny example)
Data (sorted, ms): , .
Step 1: Q1 () . Neighbours , . .
Step 2: Median () . Neighbours , . .
Step 3: Q3 () . Neighbours , . .
Step 4: p95 . Neighbours , . .
Step 5: IQR and fences . Upper fence ; lower fence . Only the 900 ms request is an outlier.
Code examples
Beginner example (Python)
import numpy as np
latency_ms = np.array([100, 120, 130, 150, 180, 200, 240, 300, 450, 900])
for p in [25, 50, 75, 90, 95]:
print(f"p{p}: {np.percentile(latency_ms, p):.1f} ms")
# p25 135.0 | p50 190.0 | p75 285.0 | p90 495.0 | p95 697.5
q1, q3 = np.percentile(latency_ms, [25, 75])
iqr = q3 - q1
upper_fence = q3 + 1.5 * iqr
print(f"Mean: {latency_ms.mean():.1f} ms") # 277.0
print(f"IQR: {iqr:.1f}, upper fence: {upper_fence:.1f}") # 150.0, 510.0
print("Outliers:", latency_ms[latency_ms > upper_fence]) # [900]Beginner example (R)
latency_ms <- c(100, 120, 130, 150, 180, 200, 240, 300, 450, 900)
print(quantile(latency_ms, c(0.25, 0.5, 0.75, 0.9, 0.95), type = 7))
# 135 190 285 495 697.5
iqr <- IQR(latency_ms) # 150
upper_fence <- quantile(latency_ms, 0.75) + 1.5 * iqr
print(latency_ms[latency_ms > upper_fence]) # 900Professional example (Python)
import numpy as np
def manual_quantile(values: np.ndarray, p: float) -> float:
"""Linear-interpolation quantile (NumPy 'linear' / R type 7), written out explicitly."""
x = np.sort(values)
h = (x.size - 1) * p
lo = int(np.floor(h))
hi = min(lo + 1, x.size - 1)
return float(x[lo] + (h - lo) * (x[hi] - x[lo]))
def slo_report(latency: np.ndarray, slo_ms: float, p: float = 0.95,
n_boot: int = 1000, seed: int = 0) -> dict[str, float]:
"""Tail-latency SLO check with a bootstrap CI for the quantile."""
rng = np.random.default_rng(seed)
q_hat = manual_quantile(latency, p)
boots = np.array([
manual_quantile(rng.choice(latency, latency.size, replace=True), p)
for _ in range(n_boot)
])
lo, hi = np.percentile(boots, [2.5, 97.5])
return {"p": p, "q_hat_ms": q_hat, "ci_low_ms": float(lo), "ci_high_ms": float(hi),
"slo_ms": slo_ms, "violated": float(q_hat > slo_ms)}
rng = np.random.default_rng(7)
# LLM endpoint: mostly fast, plus a heavy tail from long prompts
latency = np.concatenate([rng.lognormal(np.log(180), 0.35, 1900),
rng.lognormal(np.log(900), 0.40, 100)])
assert np.isclose(manual_quantile(latency, 0.95), np.percentile(latency, 95))
print(f"mean = {latency.mean():.1f} ms, p50 = {np.median(latency):.1f} ms")
for key, val in slo_report(latency, slo_ms=500).items():
print(f"{key:>10}: {val:,.2f}")Source: domain knowledge