12 Distributions
Histograms, density plots, boxplots, violins, and ridgelines for exploring the shape and spread of data.
(ns plotje-book.distributions
(:require
;; Rdatasets -- standard datasets
[scicloj.metamorph.ml.rdatasets :as rdatasets]
;; Kindly -- notebook rendering protocol
[scicloj.kindly.v4.kind :as kind]
;; Plotje -- composable plotting
[scicloj.plotje.api :as pj]))Histogram
Distribution of sepal length across all species.
(-> (rdatasets/datasets-iris)
(pj/lay-histogram :sepal-length))Colored Histogram
Split by species β each group gets its own color.
(-> (rdatasets/datasets-iris)
(pj/lay-histogram :sepal-length {:color :species}))Petal Width Histogram
Petal width has a bimodal distribution.
(-> (rdatasets/datasets-iris)
(pj/lay-histogram :petal-width))Histogram with Custom Title
(-> (rdatasets/reshape2-tips)
(pj/lay-histogram :total-bill)
(pj/options {:title "Distribution of Total Bill"
:x-label "Amount ($)"}))Density-Normalized Histogram
Pass {:normalize :density} so the y-axis shows probability density instead of raw counts. This makes the histogram directly comparable with a density curve overlay.
(-> (rdatasets/datasets-iris)
(pj/lay-histogram :sepal-length {:normalize :density :alpha 0.5})
pj/lay-density)Log-Scale Histogram
When bin counts span orders of magnitude, the smallest bars disappear next to the largest on a linear y-axis. A log y-scale lets every bar register. The data below doubles the count of each successive bin (1, 2, 4, β¦, 512); on a log axis the doubling shows as a uniform staircase β each bar a fixed step above the previous, the same step every time.
The lower bound under log comes from the smallest positive bin count, not from the visual zero baseline β log scales have no zero. Empty bins emit no bar and do not pull the axis down.
(-> {:x (mapcat (fn [i] (repeat (long (Math/pow 2 i)) i)) (range 10))}
(pj/lay-histogram {:bins 10})
(pj/scale :y :log)
(pj/options {:title "Log Y on Histogram"}))Density Plot
A smooth curve estimating the probability density function. Less sensitive to bin width than histograms.
(-> (rdatasets/datasets-iris)
(pj/lay-density :sepal-length))Grouped Density
Per-species density curves with automatic color mapping.
(-> (rdatasets/datasets-iris)
(pj/lay-density :sepal-length {:color :species}))Each curve is estimated across every speciesβ values, not just its own, so all three share one interval and each falls away to nothing at both ends. Pass {:trim true} to estimate each species over its own values instead. That shows where each groupβs data actually lies, at the cost of cutting each curve off at its extremes.
(-> (rdatasets/datasets-iris)
(pj/lay-density :sepal-length {:color :species :trim true}))Density with Custom Bandwidth
Bandwidth sets how heavily the curve is smoothed. Left alone it follows the rule Rβs density() uses, which for this column works out near 0.27. A narrower bandwidth reveals more detail β the curve below rises higher and breaks into bumps the default smooths away β while a wider one flattens it further.
(-> (rdatasets/datasets-iris)
(pj/lay-density :sepal-length {:bandwidth 0.1}))Density with Fill and Outline
By default a density fills the area under the curve in the mapped color. To make the curveβs shape read more crisply, add an outline with :stroke. The fill still comes from :color, and the outline traces only the top curve, not the baseline. :stroke-width sets the outline thickness.
(-> (rdatasets/datasets-iris)
(pj/lay-density :sepal-length {:color "lightblue" :stroke "black" :stroke-width 2}))One filled polygon plus one outline line, and the render carries both the light-blue fill and the black outline color.
Rug
A rug shows the raw data positions as short tick marks along the axis. Layered with a density curve, it shows the smooth shape and the underlying observations together.
The curve and the rug start and stop together. A density estimate is defined everywhere, not only where the data is, so drawing one means choosing an interval to estimate it over, and Plotje uses the observed values. Both therefore stop a little short of the axis ends, which carry the same padding every numeric axis gets. This matches what ggplot2βs geom_density() draws for the same data.
(-> (rdatasets/datasets-iris)
(pj/lay-density :sepal-length)
pj/lay-rug)Strip Plot (Jitter)
When plotting a numeric column against a categorical column, points stack on the same band positions. :jitter true spreads them with small random offsets along the categorical axis.
(-> (rdatasets/datasets-iris)
(pj/lay-point :species :sepal-width {:jitter true}))Pass a number to control the jitter amount in drawing units.
(-> (rdatasets/datasets-iris)
(pj/lay-point :species :sepal-width {:jitter 10 :alpha 0.5}))Boxplot
Median, quartiles, whiskers at 1.5 times the IQR (interquartile range), and outlier points.
(-> (rdatasets/datasets-iris)
(pj/lay-boxplot :species :sepal-width))The 1.5-times-IQR claim is structural: each whisker stays within the Tukey fence [Q1 - 1.5*IQR, Q3 + 1.5*IQR], and every outlier falls outside it.
Grouped Boxplot
Side-by-side boxplots colored by a grouping variable.
(-> (rdatasets/reshape2-tips)
(pj/lay-boxplot :day :total-bill {:color :smoker}))Each color group gets a distinct dodge offset, visible as side-by-side boxes within each day.
Horizontal Boxplot
Flipped coordinate for horizontal orientation.
(-> (rdatasets/datasets-iris)
(pj/lay-boxplot :species :sepal-width)
(pj/coord :flip))Violin Plot
A violin shows the full density shape per category β more informative than a boxplot for multimodal distributions.
(-> (rdatasets/reshape2-tips)
(pj/lay-violin :day :total-bill))A violin takes the opposite default to a density: each body is estimated over its own categoryβs values, so it ends where that dayβs bills end rather than tapering past them. Pass {:trim false} to let the tails fall away instead.
Grouped Violin
Color splits each category into side-by-side violins.
(-> (rdatasets/reshape2-tips)
(pj/lay-violin :day :total-bill {:color :smoker}))Each color group gets a distinct dodge offset, visible as side-by-side violins within each day.
Horizontal Violin
(-> (rdatasets/datasets-iris)
(pj/lay-violin :species :petal-length)
(pj/coord :flip))Ridgeline Plot
Overlapping density curves stacked vertically by category β good for comparing distribution shapes across many groups.
(-> (rdatasets/datasets-iris)
(pj/lay-ridgeline :species :sepal-length))Colored Ridgeline
Map color to the same categorical column for distinct curves.
(-> (rdatasets/datasets-iris)
(pj/lay-ridgeline :species :sepal-length {:color :species}))Comparing Multiple Columns
Pass a vector of column names to pj/lay-histogram (or any lay-* function) to create one panel per column. This is useful for comparing the shape of different variables side by side.
(pj/lay-histogram (rdatasets/datasets-iris) [:sepal-length :sepal-width :petal-length])Combine with :color to see group differences within each column.
(pj/lay-density (rdatasets/datasets-iris) [:sepal-length :sepal-width :petal-length] {:color :species})The multi-column vector works with any lay-* function β histograms, density curves, boxplots, violin plots, and more.
See Also
- Core Concepts β mappings and aesthetics referenced throughout
- Relationships β two-distribution comparisons via heatmap, contour, and SPLOM