Most quartile calculators answer one question and hide the argument. Ask two courses what Q1 means and you can get two different numbers from the same sorted list. TI-83/84 and AP Statistics usually exclude the median when splitting an odd-sized dataset. Tukey’s hinges include it. Excel’s QUARTILE.INC and R’s type-7 quantile use linear interpolation at position (n − 1)p.
That disagreement is not a bug to paper over. It is the lesson. I built WorkedStats as a browser-only statistics workbench where the conventions sit side by side, the dataset is shared across tools, and the working is part of the result. The median calculator is the front door; the implementation underneath is ordinary TypeScript, a little algebra, and inline SVG.
The three quartile functions
The input is always sorted first. The exclusive method removes the middle value for odd n:
if (method === "exclusive") {
const lower = xs.slice(0, middle);
const upper = n % 2 === 1 ? xs.slice(middle + 1) : xs.slice(middle);
return { q1: medianOf(lower), q3: medianOf(upper) };
}Tukey’s inclusive method keeps the median in both halves:
const lower = xs.slice(0, n % 2 === 1 ? middle + 1 : middle);
const upper = xs.slice(middle);The linear method does not split at all. It interpolates:
const position = (xs.length - 1) * probability;
const lo = Math.floor(position);
const hi = Math.ceil(position);
const t = position - lo;
return xs[lo] + (xs[hi] - xs[lo]) * t;For the nine values 12, 15, 18, 20, 22, 25, 28, 31, 35, the exclusive convention gives Q1 = 16.5 and Q3 = 29.5, while both inclusive and linear give Q1 = 18 and Q3 = 28. The IQR calculator renders all three cards at once, then updates the 1.5 × IQR fences and the box plot to the convention you choose.
The point is not to declare one method correct. It is to make the choice explicit, because a student who cannot name the convention cannot defend the answer.
Worked steps are data, not prose
A calculator that only returns s = 8.946756 is hard to learn from. The standard deviation calculator builds a table from the actual dataset: each xᵢ, its signed deviation xᵢ − x̄, and the squared deviation. The footer is the sum of squares. Then the page substitutes that number into both formulas:
s = √(Σ(xᵢ − x̄)² / (n − 1))
s = √((Σxᵢ² − n·x̄²) / (n − 1))
The two formulas are algebraically identical, which is exactly why showing both is useful: they should land on the same answer, and when they do not, the arithmetic is wrong somewhere. A sample/population switch changes the denominator from n − 1 to N and updates the table’s result without touching the raw data. The mean calculator shows the same sum-and-divide substitution for Σx / n.
The average calculator goes one step further. Arithmetic mean is only one member of the average family, so it adds geometric mean, harmonic mean, midrange and mode in an “All Averages Breakdown” table. That gives the page a different job from the mean page instead of being a second URL for the same result.
One dataset across six pages
The sticky dataset is sessionStorage, not a server session. The key is deliberately small:
const STORAGE_KEY = "workedstats.dataset.v1";The parser accepts commas, spaces, tabs, newlines and a pasted Excel column. It extracts numeric tokens and ignores blank lines, so a stray label does not become zero. Every calculator page reads the same key on mount. Paste a dataset in the z-score calculator, navigate to the median page, and the list is already there.
This is a small UX decision with a large effect on how the tools are used. Comparing the mean with the median, then checking the IQR, then standardizing one value is one investigation, not four independent visits. The shared dataset makes that flow possible without an account or an upload.
The charts are SVG, not a chart library
The box plot is a handful of <line>, <rect> and <circle> elements. The scale is linear from the dataset minimum to its maximum. The box spans Q1 to Q3; the median is a vertical line; the whiskers stop at the most extreme non-outlier. Values outside the fences get their own red circles and a <title>.
The normal curve is generated from the standard normal PDF. For 160 samples between −4 and 4, the code builds a polyline; the selected tail is filled with one or two area paths. The CDF uses the Abramowitz and Stegun error-function approximation, accurate to roughly 1.5 × 10⁻⁷:
export function normalCdf(z: number): number {
return 0.5 * (1 + erf(z / Math.SQRT2));
}No chart package, no canvas, no WebGL, no layout shift. The SVG is in the DOM, which means it is searchable, screenshot-friendly and accessible to a screen reader through <title> and <desc>.
Why this shape matters
Every calculation is a pure function in src/lib/stats.ts. The UI calls those functions and formats the result. The Playwright suite drives all six pages in a real browser: odd and even medians, the sample/population switch, the three quartile cards, the outlier dot, the shaded bell curve, the shared dataset and the console. There is no API call to mock, because there is no API.
The trade-off is honest: the browser is not a render farm, and the normal approximation is an approximation. But for a homework-sized dataset, the whole thing loads once and runs entirely on the visitor’s machine. The dataset never leaves the tab.