Topic 6.5
Ordinal, Quantize, Quantile and Threshold Scales
In one line
Discrete scales return one of a few fixed answers, usually colours. Ordinal scales turn categories into colours. Quantize, quantile and threshold scales turn numbers into classes, and each draws the class borders in a different place, which changes what the chart says.
Think of it like this
A school's grade bands. A mark of 87 becomes "B". Someone decided the borders: 90 and up is A, 80 to 89 is B. Another school could set the borders so that the top fifth of the class gets A, whatever the marks. Same marks, different grades. Class scales are those rules for charts.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- discrete scale
- A scale that returns one of a short list of results, not any number in between.
- ordinal scale
- A scale that maps each category to the next value of its list: category to colour.
- class
- One group of numbers that share a colour.
- quantile
- A border that splits sorted values into equal-count parts: the median is the 50th percentile.
- threshold
- A border you choose. A value on the border goes to the class above.
Step by step
01Colours by order of arrival
The first example uses an ordinal scale without a domain. It colours the districts in the order it meets them, then meets a typo and gives that its own colour: the domain has grown by one. With a 3-colour list and 4 districts the fourth reuses the first colour.
02Three ways to cut 365 days
The second example puts the same 365 daily totals through three scales. Quantize cuts the range 439 to 4,380 into four equal slices: classes of 34, 142, 100 and 89 days. Quantile gives 91, 91, 91 and 92. Threshold with borders at 1,500, 2,500 and 3,500 gives 49, 134, 105 and 77.
03What one outlier does
The example then adds one day of 90,000 rides to 100 real days. Quantize puts 100 of the 101 into the first class and one in the last: the 100 real days now look identical. Quantile still gives about 25 per class: it hides that the 90,000 is not like the others.
04Look at the borders
quantize.thresholds() and quantile.quantiles() return the borders. Print them before you draw: the borders are what the legend must say.
Try it yourself
- 1
Move a threshold border
In the second example change 2500 to 2000 in the threshold scale. The threshold line now prints
49 / 85 / 154 / 77instead of49 / 134 / 105 / 77: only the middle two classes moved, because only that border moved. Threshold counts depend on your numbers, not on the data's shape. Print the counts before you draw. - 2
Ask for 5 classes
Give the quantile scale 5 colours:
.range([...blues, "#08306b"]).quantiles()now prints four borders, the 20th, 40th, 60th and 80th percentiles:[ 1590.8, 2119.6, 2869.6, 3522.4 ], and the quantile line shows73 / 73 / 73 / 73 / 73(365 divided by 5). The extra class shows up in the count, because the borders follow the data.
Code & diagrams
A fixed domain keeps every district on its own colour.
Preview
Console
Nothing logged yet.Expected console output
districts: Harbour, Old Town, University, Riverside, Hillcrest, Westpark
#264653 #2a9d8f #e9c46a domain: [ 'Harbour', 'Old Town', 'University' ]
4th district: #264653 (the first colour again) a typo: #2a9d8f domain now: 5 names
known: #4e79a7 typo: #999 domain stays: 6 names
dots: 25 legend rows: 6Wednesday 1 January is day 0, so the first column is shifted by three days (k + 3) to line weeks up from Sunday.
Preview
Console
Nothing logged yet.Expected console output
quantize borders: [ 1424.3, 2409.5, 3394.8 ]
quantile borders: [ 1702, 2484, 3365 ]
days per class, quantize: 34 / 142 / 100 / 89
days per class, quantile: 91 / 91 / 91 / 92
days per class, threshold: 49 / 134 / 105 / 77
1,499 -> 0 1,500 -> 1 NaN -> undefined too few colours: b
invertExtent of the third class: [ 2409.5, 3394.8 ]
with one 90,000 day, quantize: 0:100 3:1
with one 90,000 day, quantile: 0:25 1:25 2:25 3:26Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Give the threshold scale one colour too few
In the second example change the threshold scale to .range([blues[0], blues[1], blues[2]]) (three colours for three borders).
Myth vs fact
Myth
Quantile classes are the fair way to split data.
Fact
They give each colour the same number of items, so the chart always looks balanced, even if the data has one extreme value. Say "top quarter" in the legend, not "high".
Myth
More classes make a more accurate chart.
Fact
Beyond about 7 classes the eye cannot tell the colours apart. Four or five fits most charts.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
scaleQuantizeandscaleQuantileare the same shape asscaleThreshold: they compute their borders from a domain and then call it.quantize.thresholds()returns the computed borders, andscaleThreshold().domain(quantile.quantiles())gives you the same scale with borders you can edit. - ▸
Equal-count quantiles on tied data can produce empty or duplicate classes: with seven 1s and one 100, all three borders of a four-class scale are 1, so three classes can never be used. Compare
quantiles()to the data before trusting the legend.
Remember this
- 1
The problem this solves: a map or a heat strip of 365 days cannot use 365 colours. You group the numbers into four or five classes, each with one colour. The group borders decide the story, so you must pick them on purpose and tell the reader.
- 2
**
scaleOrdinal: category to colour.** (Recall from Topic 6.4: a band scale also takes a list of names, and returnsundefinedfor a name it does not know.)d3.scaleOrdinal(d3.schemeTableau10)hands out colours in the order it first sees categories. Without a fixed domain, any new name (even a typo) is added to the domain and gets the next colour. If there are more categories than colours it starts again from the first colour, so two districts can share one. Fix the domain with.domain([...])and set.unknown("#999")for everything else. - 3
**
scaleQuantize: equal-width classes.**d3.scaleQuantize().domain([439, 4380]).range([4 colours])cuts the domain into four equal ranges (borders 1,424, 2,409.5 and 3,395) and returns the colour of the one the value falls into. Values outside the domain go to the first or last class without warning. One huge outlier stretches the domain, and everything else lands in the first class. - 4
**
scaleQuantile: equal-count classes.**d3.scaleQuantile().domain(allValues).range([4 colours])sorts the values and puts about a quarter in each class: 91, 91, 91 and 92 of the 365 days. The borders are the 25th, 50th and 75th percentiles (1,702, 2,484 and 3,365). Every colour is used equally, so the map looks varied whatever the data. It also hides outliers: 90,000 and 4,000 can share a class. - 5
**
scaleThreshold: borders you choose.**d3.scaleThreshold().domain([1500, 2500, 3500]).range(["quiet", "normal", "busy", "peak"]). The range needs one more entry than the domain. A value equal to a border goes to the higher class (1,500 is "normal"). Use it when the borders mean something: a safety level, a pass mark, "fewer than 1,500 rides is a quiet day". - 6
Missing values.
NaNandundefinedgo to the scale'sunknownvalue, which isundefinedfor quantize, quantile and threshold: set.unknown("#ccc")on a colour scale so a missing day shows as grey, not as no fill. A threshold range with too few entries reuses the last one for every higher class, without a warning.
Explain it without notes
How do quantize and quantile cut the same data differently?
Why must a threshold scale's range have one more entry than its domain?
What happens when an ordinal scale meets a name it does not know, and how do you control it?
Practice
Write a function classOf(rides) that returns "quiet", "normal", "busy" or "peak" for the borders 1,500, 2,500 and 3,500, using a scale. What is classOf(2500)?
Using the daily rides from the second example (rides), print the 75th percentile (the top border of a four-class quantile scale) and the share of days with more than 3,000 rides.
Trade-offs
- ↔
Equal-count classes look balanced but hide how far apart the values are. Equal-width classes show the spread but can waste classes on gaps. Hand-picked borders carry meaning, but they are your opinion.
Done when you can
I can build an ordinal colour scale with a fixed domain and an unknown colour.
I can say how quantize, quantile and threshold choose their borders.
I can print a scale's borders for a legend.
I know what a threshold scale does with a value on a border and with too few colours.