Module 5 — Position and boxplot
Data Description | Part II — Summarising one variable
The business question. “The Wandering Fork” has 120 days of daily revenue data per franchise. The owner wants to know: what is a “typical” day, how spread out are the days, and are there any wildly atypical days? This module teaches you to locate specific points within a distribution — the median, quartiles, and percentiles — and to build the boxplot, the single most information-dense chart in descriptive statistics.
Learning objectives
By the end of this module, you will be able to:
- Compute the median, quartiles, and any percentile of raw or grouped data.
- Apply linear interpolation to find a quantile in grouped (class-based) continuous data.
- Build a boxplot from the five-number summary and detect outliers with the fence rule.
- Use the interactive demo below to see how changing the quartiles reshapes a boxplot.
Time plan
| Block | Minutes | What you do |
|---|---|---|
| Theory | 60 | Median, quartiles, percentiles, IQR, boxplot, outlier fences, grouped-data interpolation |
| Excel lab | 60 | QUARTILE.EXC, PERCENTILE.EXC, MIN, MAX, box-and-whisker chart |
| Exercises | 60 | Three by-hand and three Excel exercises |
| Total | 180 |
Theory
Median
The median \(Me\) is the value that splits an ordered series into two equal halves: at least 50% of observations are \(\leq Me\) and at least 50% are \(\geq Me\). For \(n\) ordered values, if \(n\) is odd the median is the value at position \((n+1)/2\); if \(n\) is even, it is the average of the two values at positions \(n/2\) and \(n/2+1\).
Quartiles (exclusive method)
The three quartiles \(Q_1, Q_2 (=Me), Q_3\) split an ordered series into four equal quarters. Following the exclusive convention used throughout this course (Excel’s QUARTILE.EXC), the position of \(Q_p\) (\(p=0.25, 0.5, 0.75\)) in the ordered data is \[
\text{rank}(Q_p) = p\,(n+1),
\] interpolating linearly between the two surrounding observations when the rank is not a whole number.
In plain words: to find \(Q_1\), compute \(0.25 \times (n+1)\); if the result is 7.75, take the value 75% of the way between the 7th and 8th smallest observations. This is exactly the position formula from OpenStax §2.2, and it matches Excel’s QUARTILE.EXC.
Interquartile range and outlier fences
\[ IQR = Q_3 - Q_1, \qquad \text{lower fence} = Q_1 - 1.5\,IQR, \qquad \text{upper fence} = Q_3 + 1.5\,IQR. \] Any observation outside \([\text{lower fence}, \text{upper fence}]\) is flagged as a statistical outlier on the boxplot.
In plain words: the IQR measures the spread of the central 50% of the data. The fences extend 1.5 IQRs beyond the box — anything further out is an outlier.
Boxplot
A boxplot draws the five-number summary (\(\min, Q_1, Me, Q_3, \max\), or the fences if there are outliers) as a box (from \(Q_1\) to \(Q_3\), split by a line at \(Me\)) with whiskers extending to the most extreme non-outlier values; outliers are marked individually.
Percentiles
For any percentile \(P_p\) (\(0 < p < 1\)), the position in the ordered data is \(\text{rank}(P_p) = p\,(n+1)\), interpolating when the rank is not an integer. In Excel: =PERCENTILE.EXC(range, p).
Grouped-data interpolation
For a continuous variable grouped into classes, we cannot read \(Q_p\) directly from a list of raw values — we interpolate within the class that contains the target cumulative frequency. If class \([L, L+h[\) has lower cumulative count \(N_{\text{before}}\) (from all earlier classes) and count \(n_{\text{class}}\) of its own, and we seek the value at cumulative position \(p\cdot n\), linear interpolation across the class width \(h\) gives \[ Q_p = L + \frac{p\,n - N_{\text{before}}}{n_{\text{class}}}\; h. \]
In plain words: find which class contains the target position, then interpolate proportionally across that class’s width.
Worked example
Using “The Wandering Fork”’s daily_revenue_eur (\(n=30\)), verified in data-model.md:
| Statistic | Value |
|---|---|
| Minimum | 407.59 |
| \(Q_1\) (exclusive) | 506.61 |
| \(Q_2 = Me\) | 643.63 |
| \(Q_3\) (exclusive) | 729.99 |
| Maximum | 886.56 |
| \(IQR = Q_3-Q_1\) | 223.39 |
| Lower fence | 171.52 |
| Upper fence | 1065.07 |
Step 1 — locate \(Q_1\). With \(n=30\), rank\((Q_1) = 0.25\times 31 =
7.75\): interpolate 75% of the way between the 7th and 8th smallest values of the ordered daily_revenue_eur series: \(500.02 + 0.75\times(508.80-500.02) = 506.605 \approx 506.61\).
Step 2 — locate \(Q_3\). rank\((Q_3) = 0.75\times 31 = 23.25\): interpolate between the 23rd and 24th smallest values: \(725.66 + 0.25\times(742.99-725.66) = 729.9925 \approx 729.99\).
Step 3 — fences and outliers. Keeping the unrounded quartiles, \(IQR = 729.9925-506.605 = 223.3875 \approx 223.39\); lower fence \(= 506.605 - 1.5(223.3875) \approx 171.52\); upper fence \(= 729.9925+1.5(223.3875) \approx 1065.07\). The observed minimum (407.59) and maximum (886.56) both lie inside the fences — “The Wandering Fork” has zero outliers in its daily revenue, itself a useful finding: 30 days of trading, however variable, never produced a wildly atypical day.
Step 4 — draw the boxplot. Box from 506.61 to 729.99, median line at 643.63, whiskers extending to the true min (407.59) and max (886.56) since neither is an outlier.
Step 5 — grouped-data check. Using the class table from Module 3 to estimate \(Q_1\) (target cumulative position \(0.25\times30=7.5\)): cumulative count reaches 6 after [400,500[ and 12 after [500,600[, so \(Q_1\) lies in [500,600[ with \(N_{\text{before}}=6\), \(n_{\text{class}}=6\), \(h=100\): \[ Q_1 \approx 500 + \frac{7.5-6}{6}\times100 = 525. \] This is close to, but not identical to, the raw-data value of 506.61 — the difference is the same class-grouping approximation error discussed in Module 4.
Using Excel
The sheet harbour_30d of wandering-fork.xlsx has the data. The complete list of function names, with their French equivalents, is on the Excel functions page.
| Concept | Excel function / steps |
|---|---|
| Median | =MEDIAN(range) |
| Quartiles (exclusive) | =QUARTILE.EXC(range, 1) / (range, 2) / (range, 3) |
| Arbitrary percentile | =PERCENTILE.EXC(range, p), e.g. p=0.9 for \(P_{90}\) |
| IQR | =QUARTILE.EXC(range,3)-QUARTILE.EXC(range,1) |
| Boxplot | Select the data → Insert → Statistical Chart → Box and Whisker |
Why
QUARTILE.EXC? The course follows the \((n+1)\times p\) position formula (OpenStax §2.2), which matches Excel’s exclusive method.QUARTILE.INC(inclusive) gives different values on small samples — do not mix them.
For the full table (120 days per franchise) use the Excel Table tblFork and add the franchise as one more condition, for example =QUARTILE.EXC(IF(tblFork[franchise]="Harbour",tblFork[daily_revenue_eur]),1) (entered as an array formula in older Excel, or use FILTER in newer versions).
Proof / derivation
For a continuous variable grouped into classes, we cannot read \(Q_p\) directly from a list of raw values — we interpolate within the class that contains the target cumulative frequency. If class \([L, L+h[\) has lower cumulative count \(N_{\text{before}}\) (from all earlier classes) and count \(n_{\text{class}}\) of its own, and we seek the value at cumulative position \(p\cdot n\), linear interpolation across the class width \(h\) gives \[ Q_p = L + \frac{p\,n - N_{\text{before}}}{n_{\text{class}}}\; h. \]
Sanity check with our class table (Module 3): to locate the median (\(p=0.5\), target position \(0.5\times30=15\)) in daily_revenue_eur’s classes, the cumulative count reaches 12 after [500,600[ and 20 after [600,700[, so the median lies in [600,700[ with \(N_{\text{before}}=12\), \(n_{\text{class}}=8\), \(h=100\): \[
Me \approx 600 + \frac{15-12}{8}\times 100 = 637.5,
\] close to (though not identical to, because this formula uses grouped midpoint approximations while the true value uses the raw 30 observations) the raw-data median of \(643.63\) computed above — the small gap is exactly the information lost when data is grouped into classes.
Visual intuition
The boxplot is the five-number summary drawn as a picture: the box’s width is the IQR (how spread out the central 50% of days are), the line’s position inside the box shows whether the median sits closer to \(Q_1\) or to \(Q_3\) (a first hint of asymmetry, formalised in Module 7), and the whisker lengths show how far the more extreme (but non-outlier) days stretch beyond the box.
Interactive demo — boxplot from quartiles
Drag each slider to set the five-number summary and watch the boxplot redraw. Starting values are “The Wandering Fork”’s verified statistics.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 560
from shiny import App, render, ui
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
BG = '#1C2E22'
FG = '#D2CCC0'
app_ui = ui.page_fluid(
ui.tags.style(f"""
body {{ background-color: {BG}; color: {FG}; padding: 12px; font-family: sans-serif; margin:0; }}
.form-label {{ color: {FG} !important; }}
.form-range {{ accent-color: steelblue; width: 100%; }}
.info-box {{
background: #152119; border-radius: 6px; padding: 10px 14px;
margin: 8px 0; border-left: 3px solid steelblue;
font-family: monospace; font-size: 0.9em;
}}
"""),
ui.h5("📦 Boxplot from the five-number summary", style=f"color:{FG}"),
ui.p(
"Move the sliders (kept in min ≤ Q1 ≤ median ≤ Q3 ≤ max order) "
"to see the boxplot and IQR/fences update live.",
style=f"color:{FG}; font-size:0.88em; margin-bottom:6px;",
),
ui.input_slider("vmin", "Minimum:", min=300, max=900, value=407.59, step=1),
ui.input_slider("q1", "Q1:", min=300, max=900, value=506.61, step=1),
ui.input_slider("med", "Median:", min=300, max=900, value=643.63, step=1),
ui.input_slider("q3", "Q3:", min=300, max=900, value=729.99, step=1),
ui.input_slider("vmax", "Maximum:", min=300, max=900, value=886.56, step=1),
ui.output_ui("info"),
ui.output_plot("plot", height="300px"),
)
def server(input, output, session):
def ordered():
vals = sorted([input.vmin(), input.q1(), input.med(), input.q3(), input.vmax()])
return vals
@output
@render.ui
def info():
vmin, q1, med, q3, vmax = ordered()
iqr = q3 - q1
lo_fence = q1 - 1.5 * iqr
hi_fence = q3 + 1.5 * iqr
outlier = vmin < lo_fence or vmax > hi_fence
tag = "⚠️ min/max beyond fences — would be plotted as outliers" if outlier else "✅ no outliers"
return ui.HTML(f"""
<div class='info-box'>
IQR = {iqr:.2f} |
Lower fence = {lo_fence:.2f} |
Upper fence = {hi_fence:.2f}<br>{tag}
</div>
""")
@output
@render.plot
def plot():
vmin, q1, med, q3, vmax = ordered()
fig, ax = plt.subplots(figsize=(7, 2.6))
fig.patch.set_facecolor(BG)
ax.set_facecolor(BG)
for spine in ax.spines.values():
spine.set_edgecolor(FG)
ax.tick_params(colors=FG)
box = ax.bxp(
[{
'med': med, 'q1': q1, 'q3': q3,
'whislo': vmin, 'whishi': vmax,
'fliers': [],
}],
vert=False, widths=0.5, patch_artist=True,
)
for patch in box['boxes']:
patch.set_facecolor('steelblue')
patch.set_edgecolor(FG)
for line_group in ('whiskers', 'caps', 'medians'):
for line in box[line_group]:
line.set_color(FG)
ax.set_yticks([])
ax.set_xlabel("Value", color=FG)
ax.set_title("Boxplot from the five-number summary", color=FG)
ax.grid(alpha=0.15, color=FG, axis='x')
plt.tight_layout()
return fig
app = App(app_ui, server)
Exercises
For an ordered series of \(n=24\) values, at which position(s) do you read the median?
Solution. \(n=24\) is even, so the median is the average of the values at positions \(n/2=12\) and \(n/2+1=13\).
A single unusually bad day of \(\text{daily\_revenue\_eur} = 100\) is added to “The Wandering Fork”’s data. Using the fences computed above (171.52 / 1065.07, before recomputing the quartiles for the new \(n=31\) sample), would this new day be flagged as an outlier?
Solution. Yes: \(100 < 171.52\) (the lower fence), so it would be flagged as a low outlier — a day that badly underperformed relative to the rest of the trading period.
Using the class table from Module 3, estimate \(Q_1\) for daily_revenue_eur (target cumulative position \(0.25\times30=7.5\)).
Solution. Cumulative count reaches 6 after [400,500[ and 12 after [500,600[, so \(Q_1\) lies in [500,600[ with \(N_{\text{before}}=6\), \(n_{\text{class}}=6\), \(h=100\): \[ Q_1 \approx 500 + \frac{7.5-6}{6}\times100 = 525. \] This is close to, but not identical to, the raw-data value of 506.61 — the difference is the same class-grouping approximation error discussed in this module’s proof.
In wandering-fork.xlsx, filter franchise to “Campus” and build a boxplot for daily_revenue_eur (120 days). What are the five-number summary and fences? Are there outliers?
Solution. Campus 120-day: min = 23.85, Q1 = 236.07, median = 390.39, Q3 = 598.60, max = 1407.59. IQR = 362.54. Lower fence = -307.74, upper fence = 1142.40. Three days exceed the upper fence (1335.48, 1362.93 and 1407.59), so there are three high outliers and no low one — consistent with Campus’s right-skewed revenue (skewness = 1.00).
Compute \(P_{90}\) for Harbour’s 120-day daily_revenue_eur using PERCENTILE.EXC. What does this value mean?
Solution. A filter on the sheet does not change what PERCENTILE.EXC sees, so restrict the range with FILTER:
=PERCENTILE.EXC(FILTER(tblFork[daily_revenue_eur],
tblFork[franchise]="Harbour"), 0.9)
This gives approximately 699.63. It means 90% of Harbour trading days earned €699.63 or less; only the top 10% of days exceeded this amount.
Create side-by-side boxplots for Harbour and Campus daily_revenue_eur (120 days each). What visual differences do you see?
Solution. The two medians are almost level (~391 vs ~390), but Harbour’s box is narrower (IQR ~299 vs ~363) and Harbour has a single high outlier (€1,050.00, day 80, above its upper fence of 1016.45). Campus’s box is wider, with a long upper whisker and three high outliers (€1,335.48 to €1,407.59) — visually confirming the right skew and higher variability seen in the numbers.
Common mistakes
- Using
QUARTILE.INCinstead ofQUARTILE.EXC. The course standard is the exclusive method (position \((n+1)p\)); mixing them gives inconsistent quartiles, especially on small samples. - Forgetting that percentiles need \(0 < p < 1\) for
PERCENTILE.EXC.p=0orp=1will error — useMIN/MAXfor the extremes. - Confusing the fence rule with standard deviations. The 1.5×IQR rule is for boxplots; the “2 standard deviations” rule is for normal distributions — they are different tools.
- Reading the median position as \(n/2\) instead of \((n+1)/2\). For odd \(n\), \((n+1)/2\) gives the exact middle position; \(n/2\) would be off by 0.5.
- Using grouped-data interpolation when raw data is available. The grouped formula is an approximation; prefer the raw-data quantile when you have the full dataset.
Further reading
| Source | Where | What it adds | Time | Verified |
|---|---|---|---|---|
| OpenStax IBS 2e | §2.2 Measures of the Location of the Data (book pages 65–74) | Quartiles, percentiles, IQR, boxplot | ~9 pages | checked against the PDF contents (T046) |
| Khan Academy | Unit Summarizing quantitative data in the Statistics and probability course; look for lessons on quartiles, percentiles, and boxplots | Short videos and practice on position indicators and boxplots | ~30–45 min (estimate) | link check pending (T048) |
OpenStax IBS 2e: Alexander Holmes, Barbara Illowsky and Susan Dean, Introductory Business Statistics 2e, OpenStax, Rice University, https://openstax.org/details/books/introductory-business-statistics-2e, licensed under CC BY-NC-SA 4.0. Sections are cited, not reproduced.