Topic 7.4
Histograms and Distributions
In one line
A histogram counts how many values fall in each range of numbers, so you see the shape of the data instead of single values. d3.bin makes the ranges. The width of the bins can change the story, so you check more than one.
Think of it like this
Sorting coins into a stack of jars labelled 0–10, 10–20, 20–30 and so on. When you are done, the height of each stack tells you how many coins fall in that range. The jars are the bins and the stacks are the bars.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- distribution
- How the values of one measurement are spread: where most of them are, and how far they go.
- bin
- One range of values in a histogram, such as 40 up to (not including) 50.
- threshold
- The edge between two bins.
- histogram
- Bars whose heights are the number of values in each bin.
- quartile
- A value that splits the sorted data: 25% of values lie below the first quartile, 75% below the third.
- median
- The middle value of the sorted data: half the values are below it and half above.
- whisker and outlier
- In a box plot, a whisker is the line out to the furthest value that is not far from the box. An outlier is a value beyond the whisker limits, drawn as its own mark.
Step by step
01Bin the batteries and print the edges
The first example prints the bins for the 120 e-bikes. By default: 0-10:4 10-20:1 20-30:3 30-40:11 … up to 100-110:11. The scale of the chart must go to 110 even though no battery is above 100.
With the domain fixed to [0, 100], the bins end at 100 and the last one is 90-100:23. The count there jumped from 12 to 23 because the 11 full batteries moved into it.
02Know the empty cases
d3.bin() on no values gives one empty bin, not an empty array, so check the total before drawing. One value or equal values give a single bin from the value to itself.
03Change the bin width
The three small histograms use bins of 5, 10 and 20. The tallest bar is 13, 23 or 37 depending on the width. All three charts use the same y axis (0 to 40), so the heights can be compared. Every bar for a battery under 10% is red. In the 20-wide chart there is no red bar at all, because the first bin (0–20) holds 5 bikes and is not entirely under 10.
04Draw the box and the dots
The second example draws every e-bike as a dot (the wobble up and down is only for visibility, and uses a seeded random source so the picture is the same each time) and a box plot below. The box runs from 49 to 83.25, the line in it is the median 65, and the whiskers reach from 3 to 100. No dot is outside them, but the four orange dots on the left are the bikes to charge.
Try it yourself
- 1
Let D3 choose the number
In the first example, in the
0-100line, replaced3.range(10, 100, 10)by25. The console prints20 bins: the hint is rounded to a 1-2-5 step (steps of 5), exactly as in thethresholds hint 25line. - 2
Give the full batteries their own bin
In the
0-100line changed3.range(10, 100, 10)tod3.range(10, 101, 10). The threshold 100 now exists, and the last two bins are90-100:12 100-100:11: the 11 full batteries are separated from the rest.
Code & diagrams
Orange bars are bins that end at 10% or lower. The pedal bikes have `battery: null`, so the example keeps the e-bikes only, and `d3.bin` would skip the nulls without any message anyway.
Preview
Console
Nothing logged yet.Expected console output
default: 11 bins -> 0-10:4 10-20:1 20-30:3 30-40:11 40-50:12 50-60:18 60-70:20 70-80:14 80-90:14 90-100:12 100-110:11
0-100 : 10 bins -> 0-10:4 10-20:1 20-30:3 30-40:11 40-50:12 50-60:18 60-70:20 70-80:14 80-90:14 90-100:23
counted from all 320 bikes: 120
thresholds hint 25 gives 20 bins (D3 rounds to 1-2-5 steps)
e-bikes under 10%: 4 | at exactly 100%: 11The box plot's whiskers reach from 3 to 100 and no point is an outlier, so the box alone would hide the four low bikes. The dots keep them visible.
Preview
Console
Nothing logged yet.Expected console output
quartiles: 49 65 83.25 | mean: 64.5
whiskers: 3 to 100 | outliers: none
no values [[null,null,0]]
one value [[7,7,1]]
all equal [[4,4,3]]
with undefined [[0,5,1],[5,10,1]]Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Bin the objects instead of the numbers
In the first example's histogram function replace the bins line by const bins = d3.bin()(bikes);: the 320 bike objects, with no .value(...).
Myth vs fact
Myth
The average battery level tells you which bikes need charging.
Fact
The mean is 64.5% and says nothing about the 4 bikes under 10%. Only the shape shows them.
Myth
A box plot shows everything about a distribution.
Fact
It shows five numbers. Here it hides the four low bikes, because none counts as an outlier by the 1.5 rule.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
d3.binusesd3.ticksfor numeric thresholds, which is why the hint is rounded to a nice step. When the thresholds are evenly spaced it finds each value's bin by arithmetic instead of searching (see the "quantization" comment ind3-array/src/bin.js), which is faster on large arrays and gives the same result. A threshold equal to the domain's end is kept, which is what makes the100-100bin in the second Try it possible. - ▸
d3.thresholdSturges,ScottandFreedmanDiaconispick the number of bins by a formula. On these 120 values they give 11, 6 and 6 bins. A formula is a starting point: check the picture against the question.
Remember this
- 1
The problem this solves: Priya wants to know how charged the e-bikes are, and an average (64.5%) hides the interesting part: are most half-full, or are some almost empty? A histogram shows how the 120 batteries are spread, and where the few problem bikes are.
- 2
**
d3.binreturns an array of bins** (recall Topic 4.5:binis one of the summarising tools of d3-array). Each bin is an array of the data items that fall in it, with two extra numbers:x0(the start, included) andx1(the end, excluded). The bin's count is itslength. A bar is a rectangle fromx(x0)tox(x1)with the height oflength. - 3
Thresholds are the edges between bins. You can pass a number (a hint: D3 rounds it to 1-2-5 steps, so 25 gives 20 bins in the first example), an array of exact edges such as
d3.range(10, 100, 10), or a rule liked3.thresholdSturges. By default D3 uses a "nice" domain that runs beyond the data: for batteries from 3 to 100 it makes 11 bins from 0 to 110, and the last one holds the 11 bikes at exactly 100%. - 4
The last bin includes its end. With the fixed domain
[0, 100]and ten bins the last bin is 90 to 100 and it holds 12 bikes under 100% plus 11 at exactly 100%: 23. The bar looks like "bikes that are 90 to 99% charged", but it is really "90 to 100% inclusive". A bin edge always needs a rule for the edge value, and you should write it on the chart. - 5
Bin width changes the story. The second example draws the same 120 values with bins 5, 10 and 20 wide. With 5-wide bins you see the pile at 100% and the four low bikes in the first two bins. With 10-wide bins the low ones are one red bar of 4. With 20-wide bins they melt into a bar of 5 and look like every other bin. Try several widths and keep the one that does not hide what the question needs.
- 6
Other views of a distribution. A box plot shows the middle (median 65) and the spread (quartiles 49 and 83.25) in one small shape, but it hid the four problem bikes: by the usual rule none of the 120 is an outlier. A dot strip draws every value as a dot, so nothing is hidden. Use a histogram for shape, a box to compare many groups, and dots for a few hundred points.
- 7
Edge cases. No values gives one empty bin
[null, null, 0]. One value gives a single bin from 7 to 7. All equal values do the same.null,undefinedandNaNare skipped silently: the pedal bikes havebattery: null, so binning all 320 bikes counts only 120. Always know how many values you did not count.
Explain it without notes
What is in a bin that d3.bin returns?
Why does the default bin range run to 110 when no battery is above 100?
Why can the width of the bins change what you conclude?
Practice
Write histogram(values, width) that returns the bins of values from 0 to 100 in steps of width, and prints how many values fell outside 0–100.
Draw the bikes' km_total as a histogram with 8 bins. Hint: look at d3.extent(bikes, (d) => d.km_total) first.
Trade-offs
- ↔
Wide bins give a calm shape and hide small groups. Narrow bins show detail and noise. Show two widths when the small group matters.
Done when you can
I can bin data with
d3.bin().value(...)and readx0,x1andlength.I can say how the first and last edge behave and write the rule on a chart.
I can show that a different bin width changes the picture.
I know what
d3.bindoes withnull, empty and equal values.