data(gapminder_2007) + bar * count + x(continent)data(gapminder_2007) + bar * count + x(col.continent)data(gapminder_2007) + bar * count + x(:continent)plot(data(gapminder_2007), layer(bar, count), x(col.continent))You have one column in front of you. What can it tell you? The questions it can answer, and the sentence for each.
A text column’s first question. Counting rows is a derivation, not a different kind of chart, so it is bar with * count:
data(gapminder_2007) + bar * count + x(continent)data(gapminder_2007) + bar * count + x(col.continent)data(gapminder_2007) + bar * count + x(:continent)plot(data(gapminder_2007), layer(bar, count), x(col.continent))“Bars derived by count: x is continent.” No y() is bound because the count is invented, and the axis labels itself.
One atom away. Long category names? Put the categories on y and the bars lie down, same sentence, positions swapped:
data(gapminder_2007) + bar * count + y(continent)data(gapminder_2007) + bar * count + y(col.continent)data(gapminder_2007) + bar * count + y(:continent)plot(data(gapminder_2007), layer(bar, count), y(col.continent))The count still measures along the invented axis, which is now x. Owning chapters: Bar, Transforms.
The continuous column’s first question. bin cuts the axis into intervals and counts rows into each; bar draws the counts touching, because the intervals are adjacent:
data(iris_flowers) + bar * bin + x(petal_length)data(iris_flowers) + bar * bin + x(col.petal_length)data(iris_flowers) + bar * bin + x(:petal_length)plot(data(iris_flowers), layer(bar, bin), x(col.petal_length))One atom away. bin takes a count or a width when the default guesses wrong. bin(30) for thirty bins, or intervals in the data’s own units:
data(iris_flowers) + bar * bin(width = 0.5) + x(petal_length)data(iris_flowers) + bar * bin(width = 0.5) + x(col.petal_length)data(iris_flowers) + bar * bin(width = 0.5) + x(:petal_length)plot(data(iris_flowers), layer(bar, bin({ width: 0.5 })),
x(col.petal_length))A line * bin draws the same counts as a frequency polygon. Owning chapters: Bar, Transforms.
The binned view depends on where the bin edges fall; the density estimate smooths that decision away:
data(gapminder_2007) + line * density + x(life)data(gapminder_2007) + line * density + x(col.life)data(gapminder_2007) + line * density + x(:life)plot(data(gapminder_2007), layer(line, density), x(col.life))“A line derived by density: x is life.” Same question as the histogram, one derivation over. Use both when the bumps matter. One atom tunes the smoothing: density(0.5) sharpens the bumps, density(2) irons them out, and density(bandwidth = 1) fixes the kernel width in years.
The third rendering of bin: an unfilled staircase silhouette, tracing bin tops without any fill. It is most useful when several distributions share an axis, see Many groups, but reads cleanly alone too:
data(gapminder_2007) + step * bin + x(life)data(gapminder_2007) + step * bin + x(col.life)data(gapminder_2007) + step * bin + x(:life)plot(data(gapminder_2007), layer(step, bin), x(col.life))Three marks (bar, line, step), one bin. The transform never changed; only the geometry drawing its output did. That is the No Exceptions law doing its quiet work.
Every answer above summarizes: bin needs a bin width, density needs a bandwidth, and both choices show more when there are fewer rows to average over. With thirty numbers you can skip the choice and draw all thirty. Add stack and a point piles one dot per observation:
data(gm_europe) + point * bin * stack + x(life) +
x_label("Life expectancy (years)") + title("Thirty countries, thirty dots")(data(gm_europe) + point * bin * stack + x(col.life) +
x_label("Life expectancy (years)") + title("Thirty countries, thirty dots"))data(gm_europe) + point * bin * stack + x(:life) +
x_label("Life expectancy (years)") +
title("Thirty countries, thirty dots")plot(data(gm_europe), layer(point, bin, stack), x(col.life),
x_label("Life expectancy (years)"),
title("Thirty countries, thirty dots"))“Points derived by bin and stack: x is life.” The pile’s height is a count, so the tallest column can be read off the axis or simply counted, and no observation has been averaged into anything. Compare the same thirty as bars, where a single bin edge moving would redraw the shape:
data(gm_europe) + bar * bin + x(life) +
x_label("Life expectancy (years)") + title("The same thirty, summarized")(data(gm_europe) + bar * bin + x(col.life) +
x_label("Life expectancy (years)") + title("The same thirty, summarized"))data(gm_europe) + bar * bin + x(:life) +
x_label("Life expectancy (years)") +
title("The same thirty, summarized")plot(data(gm_europe), layer(bar, bin), x(col.life),
x_label("Life expectancy (years)"),
title("The same thirty, summarized"))One atom away. A categorical column instead: point * count * stack piles one dot per row in each category. Owning chapter: Transforms.
A distribution question needs a continuous column. Ask density about a category and the refusal names the type the estimate needs. The categorical version of this question is the share recipe above:
data(gapminder_2007) + line * density + x(continent)Error:
! gog: `density` estimates a continuous distribution, and `x(continent)` is categorical — there is no number line for the curve to spread along. For the share of rows in each category, that is `bar * proportion`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.