Lollipop Chart in R: A Cleaner Alternative to Bar Charts
A lollipop chart is a bar chart stripped down to its essentials — a thin line (stem) topped with a dot — built in ggplot2 with geom_segment() and geom_point().
Introduction
Bar charts are workhorses. They communicate magnitude clearly. But when you have 15-25 categories, a dense forest of filled bars becomes visually noisy — the bars dominate the ink budget, and the actual values (the tops of the bars) are lost in the clutter.
A lollipop chart solves this. By replacing the filled bar with a thin line and a single dot, you reduce visual noise and make the data points themselves the focal element. The reader's eye goes directly to the dot, not to a wall of colored rectangles.
The tradeoff is that lollipops are slightly less precise than bars for length comparison — humans are better at comparing bar lengths than dot positions along a line. But when you have many categories and want a clean ranked visualization, lollipops are often the better choice.
How do you create a basic lollipop chart in R?
A lollipop chart needs two geoms: geom_segment() for the stem (from 0 to the value) and geom_point() for the dot at the top.
geom_segment() takes four coordinates: x, xend (same category on both ends), y = 0 (baseline), and yend = temp (the value). geom_point() then places a dot at (month, temp).
Try it: Change geom_point(color = "steelblue", size = 4) to geom_point(shape = 21, fill = "steelblue", color = "white", size = 5, stroke = 1.5). Shape 21 is a hollow circle you can fill — the white stroke creates a clean border.
How do you order a lollipop chart by value?
When categories have no natural order (months, countries, products), ordering by value turns a random-looking plot into a clear ranking.
reorder(month, temp) reorders the factor levels of month by ascending temp. The chart now reads left-to-right from coldest to warmest.
Try it: Change reorder(df_ordered$month, df_ordered$temp) to reorder(df_ordered$month, -df_ordered$temp) to sort descending (highest first).
How do you make a horizontal lollipop chart?
When category names are long — country names, product names, multi-word labels — flip the axes so labels run horizontally and are readable without tilting your head.
coord_flip() swaps x and y axes — country names now run vertically on the left, values extend horizontally. Note that y = 0.9 in geom_segment() sets the baseline at 0.9 (not 0), since all HDI values are close to 1. Starting from 0 would make tiny differences invisible.
Try it: Change the baseline from y = 0.9 to y = 0 to see how a zero baseline changes the apparent differences between countries.
How do you color lollipops above and below a threshold?
Coloring by a condition (above/below average, above/below zero, pass/fail threshold) adds a semantic layer that helps readers immediately see which categories are "good" vs "bad".
The stems now start at avg_temp (not 0) and extend to each value. Red dots are above average, blue are below — the diverging stems from the reference line make the pattern immediately visible.
Try it: Change the reference from avg_temp to 15 (a round number) and see how choosing a different threshold changes the story.
How do you create a diverging lollipop chart?
A diverging lollipop encodes positive values pointing right (or up) and negative values pointing left (or down). It's perfect for showing change, deviation, or gain/loss.
Value labels are placed to the right of positive dots and to the left of negative dots using hjust = ifelse(change >= 0, -0.3, 1.3). This keeps labels from overlapping the stems.
Try it: Remove + theme(legend.position = "none") to show the legend. Then try removing the geom_text() layer entirely — do you still know which bars are positive vs negative? This is the difference between encoding the sign (stem direction) and annotating the magnitude.
Complete Example: Polished Lollipop Chart
Common Mistakes and How to Fix Them
Mistake 1: Forgetting to set the baseline to 0 (or the right reference)
❌ Omitting y = 0 in geom_segment() makes stems appear as floating lines.
✅ Always specify both endpoints:
Mistake 2: Not ordering categories
❌ Alphabetical or arbitrary category order makes it impossible to see ranking.
✅ Reorder by value when categories have no natural order.
Mistake 3: Using lollipops for time series
Lollipops imply ranked comparison. For temporal data (months, years), a line chart communicates the trend better. Lollipops work for time when you want to emphasize individual period values rather than trend.
Mistake 4: Tiny dots on thick stems
When size (dot) is smaller than linewidth (stem), the dot disappears behind the stem. Keep the dot visually dominant.
Mistake 5: Overlapping x-axis labels
Long category labels on a vertical lollipop chart get clipped or overlap. Fix: use coord_flip() for horizontal layout, or rotate labels with theme(axis.text.x = element_text(angle = 45, hjust = 1)).
Practice Exercises
Exercise 1: Ranked lollipop
Create a horizontal lollipop chart using the mtcars dataset, showing average mpg by number of cylinders (cyl). Order by mpg descending (highest mpg on top).
Show solution
Exercise 2: Diverging lollipop for deviations
Using airquality, compute the average Temp per month, then calculate each month's deviation from the overall mean. Create a diverging lollipop chart (horizontal) with green for above-average months and orange for below-average months.
Show solution
Summary
| Task | Code |
|---|---|
| Basic lollipop | geom_segment(aes(y=0, yend=val)) + geom_point() |
| Order by value | reorder(category, value) |
| Horizontal layout | + coord_flip() |
| Color by condition | ifelse() variable + scale_color_manual() |
| Diverging lollipop | Baseline at 0, stems extend positive/negative |
| Value labels | geom_text(aes(label = val), hjust = -0.3) |
Lollipop vs bar chart:
- Use a lollipop when you have many categories (15+), when the visual clutter of filled bars is distracting, or when you want to emphasize the value position rather than the bar fill area.
- Use a bar chart when precise length comparison matters, when you need filled color for grouping, or when your audience is less familiar with lollipop charts.
FAQ
What is the difference between a lollipop chart and a Cleveland dot plot? A Cleveland dot plot shows dots only — no stems. It's used to compare multiple groups per category (two dots per row, one per group). A lollipop has a stem from zero and is used for single-variable ranked comparisons.
Can I add multiple lollipops per category (grouped lollipops)? Yes — use position_dodge() in both geom_segment() and geom_point(), similar to a grouped bar chart. But more than 2 groups gets cluttered; consider small multiples instead.
How do I add value labels that don't overlap the dots? Use hjust = -0.3 for vertical lollipops (labels to the right of dots) or hjust = 1.3 for labels to the left. For horizontal charts, use vjust = -0.5 (above) or vjust = 1.5 (below).
Is there a dedicated lollipop geom in ggplot2? No — you compose it from geom_segment() + geom_point(). The ggalt package has geom_lollipop() as a convenience wrapper, but the base ggplot2 approach gives more flexibility.
References
- Wickham H. (2016). ggplot2: Elegant Graphics for Data Analysis. Springer.
- R Graph Gallery — Lollipop chart: r-graph-gallery.com/lollipop-plot.html
- Wilke C. (2019). Fundamentals of Data Visualization — Chapter 6: Visualizing amounts
- data-to-viz.com — Lollipop chart
What's Next?
- ggplot2 Bar Charts — the classic alternative for comparing categorical magnitudes
- Error Bars in ggplot2 — add uncertainty intervals to your point estimates
- R Waffle Chart — display counts as a grid of unit squares for intuitive proportions