Zopra/Charts
Guide
Getting startedAll chartsWhich chart to use?Domain specific
Base charts7
XY ChartTrendCandlestickHeatmapState TimelineFlame GraphBar Gauge
Derived charts73
Time SeriesHistogramStatus HistoryStatGaugeHoneyCombTracesNode GraphHeikin-AshiRenkoKagiPoint & FigureParetoIshikawaControl ChartBland-AltmanLollipopDumbbellSlopeBumpBack-to-BackWaterfallBulletFunnelMekkoForestBarcodeBeeswarmRidgelineHorizonSpectrogramAttention MapWaffleVennUpSetWarped WaffleWord CloudGanttSankeySankey MatrixTreemap ForceChord DiagramCircosArc DiagramAlluvialSankey FunnelDendrogramIcicleSunburstSpiderNightingale RoseParallel CoordinatesOscilloscopeEfficient FrontierPhase PortraitPoincaré MapBifurcationH-R DiagramRamachandranHarkerRoot LocusRadiation PatternQuiverHodographSmith ChartEye DiagramSequence LogoTernaryTernary ContourPiperStüveTephigramChoropleth

Charts/Distribution charts

Ridgeline

<Ridgeline /> · 10 examples

Stacked, overlapping density curves, one per group, on a shared axis.

Overview

Comparing the distribution of one number across twelve groups is an awkward job. Twelve histograms side by side are too small to read. Twelve overlaid on one set of axes are a spaghetti plate. Twelve box plots show you five numbers each and ask you to imagine the rest. The ridgeline plot's answer is to draw each group as a smooth curve, give each its own row, and let the rows overlap a little so the page is used well.

The most famous ridgeline in the world is on a record sleeve: the cover of Joy Division's Unknown Pleasures shows successive radio pulses from the pulsar CP 1919, stacked in a pile of white lines on black. The picture came out of a 1970 astronomy thesis and became the shorthand for this layout, which is why the chart is also known as a joyplot. Ridgeline is the more neutral name and the one this library uses.

The pleasure of the chart is in the comparison. Because every curve shares the same horizontal axis, you can run an eye down one value and see which groups put weight there. Shifts, spreads and second humps appear as a landscape, not as a table.

Props

API

<Ridgeline data={readings} value={|r: &Reading| r.temperature} group={|r: &Reading| r.month} />
PropTypeDefault
dataas aboverequired
valueFn(&T) -> f64required
groupFn(&T) -> &str: one curve per grouprequired
bandwidthf64: kernel width, in units of valueSilverman's rule, per group
overlapf64: the tallest curve's height, in rows1.6
fill_opacityf320.75
x_axisAxisConfigAxisConfig::new()
tooltip, class

Each curve is a Gaussian kernel density estimate. The first group is at the top. Lower bandwidth shows more detail; higher smooths more.

Run them

10 examples

Press Run to load an example into the live render at the top of the page. Open one to read its code.

How to read it

  1. 01Read the horizontal position of a peak as the typical value for that group.
  2. 02Read the width of a curve as spread: a narrow hump is a consistent group, a wide one is a varied group.
  3. 03Because all curves share a scale, a taller curve is a more concentrated one. Compare heights as well as positions.
  4. 04Look for second humps. They usually mean two populations hiding in one group.
  5. 05Tooltips are off by default, because the picture is about shape. Pass tooltip if you need one.

When to use it, and when not to

Reach for it when

  • You compare the shape of one measure across many groups: salaries by department, temperatures by month, scores by school.
  • The groups have a natural order (months, years, doses), so the stack reads as a story of change.
  • Group sizes are large enough for a smooth estimate: dozens of values or more per group.
  • You want a picture that is compact and a bit beautiful, and you have ten or twenty groups.

Skip it when

  • Groups have very few values. A kernel estimate of five points is a guess in a nice dress. Show the points with a beeswarm or barcode.
  • You care about exact values or counts. The curve is a density, not a tally, and the heights are relative.
  • Your data has hard limits (non-negative amounts, percentages). The kernel spills past the limit and shows density where no value can exist.
  • You have only two or three groups. Overlaid or side-by-side histograms are easier to compare.

The math behind it

A kernel density estimate per group

For a group with values x1,…,xnx_1, \dots, x_nx1​,…,xn​ the curve is a Gaussian kernel density estimate. At every point ttt of the grid it adds a small bell centered on each observation and averages them, which makes the curve a smoothed version of the histogram with no bins to choose.

The curve integrates to 1 over the whole line, so each group contributes the same area regardless of its size. Non-finite values are ignored.

f^(t)=1n h 2π∑i=1nexp⁡ ⁣(−12(t−xih)2)\hat f(t) = \frac{1}{n\,h\,\sqrt{2\pi}} \sum_{i=1}^{n} \exp\!\Bigl(-\tfrac12\bigl(\tfrac{t - x_i}{h}\bigr)^{2}\Bigr)f^​(t)=nh2π​1​i=1∑n​exp(−21​(ht−xi​​)2)

Bandwidth

The bandwidth hhh is the width of each little bell, and it is the one decision that changes the picture. By default every group gets its own, from Silverman's rule of thumb, using the sample standard deviation sss of that group: h=1.06 s n−1/5h = 1.06\, s\, n^{-1/5}h=1.06sn−1/5. If a group has fewer than two values, or all values equal, the fallback is h=1h = 1h=1.

That rule is tuned for bell-shaped data. For data with several peaks it oversmooths and the humps merge. Pass a smaller bandwidth (one value, applied to every group) to separate them. Too small and each point becomes its own spike.

h=1.06 s n−1/5h = 1.06\, s\, n^{-1/5}h=1.06sn−1/5

From curves to rows

All curves are evaluated on one grid of 128 points that runs from the smallest value minus 3hmax⁡3h_{\max}3hmax​ to the largest value plus 3hmax⁡3h_{\max}3hmax​, where hmax⁡h_{\max}hmax​ is the largest bandwidth in use; that margin is where the tails fall to zero. The curves are then divided by the single tallest density across all groups, and multiplied by overlap (default 1.6).

The consequence is that all groups share one vertical scale: a curve twice as tall holds twice the density, not just a bigger sample. The baseline of group ggg of GGG is at height G−1−gG - 1 - gG−1−g, so the first group in your data is the top row, and the tallest curve reaches 1.6 rows above its baseline and into the row above it.

yg(t)=(G−1−g)+overlap⋅f^g(t)max⁡g,tf^g(t)y_g(t) = (G - 1 - g) + \text{overlap}\cdot\frac{\hat f_g(t)}{\max_{g,t} \hat f_g(t)}yg​(t)=(G−1−g)+overlap⋅maxg,t​f^​g​(t)f^​g​(t)​

Real life usage

Nine months of Wednesday

Lena worked on the data team of a bike-share system in a hilly city, and she had been asked a question that sounded simple: do trips get longer in summer? Her manager wanted one chart for a quarterly review, and had said, hopefully, that it could be "something nice".

She had 600,000 trips across nine months. A line of average duration by month was easy to make and told her nothing. It moved by about 40 seconds and the manager would conclude that nothing happened. A histogram for each month was accurate and nine panels tall.

The thing she suspected was not a shift in the average at all. She thought the shape was changing: more short commuter hops in winter, and a second, longer group of leisure rides arriving in spring. Averages cannot show that, and she could not show it with nine histograms in a slide that would be on screen for forty seconds.

She put a sample of 2,000 trips per month into a <Ridgeline> from zopra-charts, grouped by month in calendar order, and left the bandwidth alone. One chart, one row per month, one shared axis from 0 to 60 minutes.

January through March were a single sharp peak at around 8 minutes. In April a low shoulder appeared at 25 minutes. By June it was a proper second hump, and in July it was almost as tall as the first. It looked like a mountain range being born, which several people in the review mentioned, and one of them asked whether it was the Joy Division cover. It was not, but she did not argue.

The finding changed a decision. The ops team had been sizing summer rebalancing by average trip length, and so was leaving the stations near the lakefront empty by mid-afternoon. They re-planned around the second hump, and the number of empty-dock alerts at those stations dropped from 310 in July to 120 in August.

Lena's manager said the chart was nice. This was the first time in her career that nice and useful had been the same word.

Pitfalls

  • Oversmoothing. The default bandwidth assumes one bell. If a group has two peaks, pass a smaller bandwidth, otherwise the second one is melted away.
  • Smoothing past a boundary. A distribution that cannot go below zero will show curve to the left of zero. The kernel does not know about your limits.
  • Reading overlap as data. Curves reach into the row above because overlap says so. A curve that covers part of its neighbour is not hiding anything underneath, but a tall one can hide a short one: lower overlap when it does.
  • Tiny groups look as confident as large ones. Every curve is a smooth shape whatever the sample size. Show group sizes in the labels if they differ a lot.

From around the web

Watch and read

YouTubeHow to create a ridgeline plot in R with ggridges in RStudio (CC226)Riffomonas ProjectBuilds a ridgeline step by step and discusses overlap and bandwidth, the two settings that matter.
YouTubeRidgeline Plot in PythonBhavesh BhattA short Python version for comparing the distribution of one number across several groups.

Keep going

Related charts

HistogramWhen counts per bucket are what you need, and for one or two groups.BeeswarmWhen groups are small and every observation should be visible.HorizonWhen the thing that changes across rows is a time series, not a distribution.
PreviousBeeswarmNextHorizon

On this page

  • Live
  • Overview
  • API
  • Examples
  • How to read it
  • When to use it
  • The math
  • Story
  • Pitfalls
  • Watch and read
  • Related