DistributionAdvanced

Half-Eye Plot

A one-sided density curve paired with a point-and-interval summary — the cleaner sibling of the raincloud plot. Shows distributional shape and key statistics without the noise of raw data points.

// 01The chart

What it looks like

Example — Posterior distributions, Bayesian model3 parameters
β₁β₂β₃-0.4-0.200.20.4

Half-eye plots showing Bayesian posterior distributions for three regression coefficients. The dot is the median; thick and thin lines show 66% and 95% credible intervals. The dashed vertical at zero highlights that all three intervals exclude the null effect.

// 02Definition

What is a half-eye plot?

A half-eye plot combines a one-sided density curve (the “half eye”) with a point-interval summary underneath. The density shows the full distributional shape, while the point-interval provides quick-read statistics — typically a median point with credible / confidence intervals as horizontal lines.

It’s essentially a raincloud plot without the raw data dots. This makes it cleaner and more compact, trading individual-level detail for a more polished, publication-ready appearance. It’s the chart of choice for Bayesian statisticians presenting posterior distributions, and it has become a standard in journals that publish Bayesian model summaries.

The name “half-eye” comes from the shape: a half-violin (density curve on one side only) with a point below it, which visually resembles a sideways eye. The asymmetric design is intentional — it avoids the visual redundancy of a full (mirrored) violin plot while reserving the empty side of the axis for explicit interval line widths that encode credibility levels.

Two interval levels are typically drawn: a thicker line (often 50% or 66%) for the central mass and a thinner line (often 95%) for the broader uncertainty band. This dual-width encoding makes credibility immediately legible without any axis ticks or numbers.

Origin: The half-eye plot was popularized through Matthew Kay’s R package ggdist (released 2019, building on earlier tidybayes work), which provides a systematic grammar for visualizing distributions with layered density + interval representations.

// 03When to use

When a half-eye plot is the right call

Reach for a half-eye plot whenever you need to communicate a full distribution along with explicit uncertainty intervals — most often for Bayesian posteriors, but increasingly for any summary of a sampling distribution.

✓Use a half-eye plot when…
  • Communicating Bayesian posterior distributions for coefficients or parameters
  • Comparing distributions across groups in a publication-ready way
  • You need shape + summary statistics in a single chart
  • Raincloud-style raw data dots would crowd the figure
  • Showing simulation outputs or bootstrap distributions
  • You need to emphasize credible / confidence intervals at multiple levels
  • Producing figures for an academic paper, slide, or technical report

// 04When not to use

When a half-eye plot is the wrong call

Half-eye plots assume a numerate audience and a continuous distribution to summarize. Outside that sweet spot, simpler charts almost always win.

×Avoid a half-eye plot when…
  • Your audience is unfamiliar with density curves and credible intervals
  • You only have a few data points per group — a strip plot or dot plot is more honest
  • You need to show individual observations — use a raincloud or strip plot instead
  • A simple bar chart with error bars would communicate the same conclusion
  • The distribution is binary or strongly bimodal in a way density estimation distorts
  • Space is too tight to draw two interval widths legibly
  • Your data is not really a distribution — just a set of point estimates

// 05Data requirements

What your data needs to look like

Before building the chart, your dataset needs to fit a specific shape. Use this checklist to confirm yours does.

Shape

Long format — one row per draw / observation, with a categorical group column and a numeric value column.

Minimum rows

A few hundred draws per group. Below ~100 draws, the density estimate becomes unreliable.

Maximum rows

No effective upper bound. ggdist and ArviZ both handle hundreds of thousands of draws routinely.

Required fields
grouprequired
string / categorical

A label for each row in the eventual half-eye axis. Typically a parameter name (β₁, β₂, …), a treatment arm, or a model term.

valuerequired
number (continuous)

The numeric draw being summarized. Each row is a single sample from the distribution — a posterior draw, a bootstrap replicate, or an experimental measurement.

draw
integer (optional)

Optional draw / iteration index. Useful when downstream code groups by chain or iteration to compute interval widths.

Example data
parameterdrawvalue
β₁10.31
β₁20.28
β₂1-0.18
β₂2-0.22
β₃10.21
β₃20.19

Tip: if your input is a tidy MCMC fit (PyMC, Stan, brms, NumPyro), you almost certainly already have it in long format. tidybayes::gather_draws() in R and ArviZ’s to_dataframe() in Python both produce the right shape directly.

// 06Anatomy

Parts of a half-eye plot

A half-eye is composed of four pieces: the one-sided density slab, the median point, the thicker inner interval, and the thinner outer interval. Knowing each piece by name makes it easy to talk about which to keep and which to drop.

ABCDE
A — Density slab: The one-sided density curve showing the full distributional shape
B — Median point: The central tendency — typically the median, sometimes the mean or mode
C — Thick interval (66%): The inner interval covering the central mass of the distribution
D — Thin interval (95%): The outer interval covering broader uncertainty
E — Reference line (0): Optional dashed line marking a meaningful null value

// 07Step-by-step

Step-by-step: how to build a good half-eye plot

An eight-step recipe that works regardless of tool. The bandwidth and interval-level choices are the only steps that need taste; the rest is mechanical once the data is in long format.

  1. 1

    Decide what the distribution represents

    Half-eye plots are most often used for Bayesian posteriors, but they also work for bootstrap distributions, simulation outputs, or repeated measurements. Be explicit in the title — “posterior”, “bootstrap”, or “observed” — so the reader knows what to interpret.
  2. 2

    Reshape the data into long format

    One row per draw, two columns: the group label and the numeric value. ggdist, ArviZ, and Observable Plot all expect this shape; wide formats need a pivot first.
  3. 3

    Pick the interval widths

    The convention is to draw two intervals: a thicker one at 50–66% and a thinner one at 95%. Some teams use 89% in place of 95% to break the false-alarm pattern of ‘0.95’ being read as a p-value cutoff.
  4. 4

    Choose a sensible bandwidth

    Densities are estimated, not measured. The default kernel-density bandwidth usually works, but you should sanity-check by overlaying the empirical histogram and confirming the curve isn’t dramatically over- or under-smoothed.
  5. 5

    Sort the categorical axis meaningfully

    Order groups by posterior median or by an experimental design (treatment arms, model terms in canonical order). Alphabetical order is almost never the right choice.
  6. 6

    Add a reference line at zero or null effect

    When the posterior is for a coefficient, draw a faint vertical line at zero so readers can visually check whether the credible interval excludes the null.
  7. 7

    Title the takeaway

    Replace “Posterior distributions” with “All three coefficients exclude zero with 95% credibility — β₁ has the strongest effect.” The chart should announce the conclusion.
  8. 8

    Check that intervals stay legible at thumbnail size

    Half-eye intervals are thin lines; if your panel will be embedded in a paper margin or dashboard tile, verify the thicker / thinner contrast is still readable.

// 08Real-world examples

Where you’ll see half-eye plots used

Half-eye plots have become the default chart for Bayesian model summaries in academic publications and well-documented industry analyses.

01

Bayesian regression: posterior coefficients

Almost every published Bayesian regression now ships posterior summaries as half-eye plots, one row per coefficient, with the canonical 66/95 interval pair and a reference line at zero.

Statistics
02

A/B testing: lift per variant

Modern experimentation platforms summarize each variant’s lift as a half-eye over the posterior of the treatment effect. Decision-makers read the chart by checking which intervals exclude zero.

Experimentation
03

Epidemiology: parameter estimates from disease models

Compartmental epidemic models (SEIR, age-structured) report key parameters — R₀, generation interval, IFR — as half-eye summaries that capture both central estimate and posterior spread.

Epidemiology
04

Sports analytics: player skill estimates

Hierarchical models of player skill produce posteriors with very different widths between veterans and rookies. Half-eye plots make the uncertainty visible alongside the point estimate.

Sports analytics

// 09Variations

Variants of the half-eye plot

ggdist exposes a family of related geoms; each replaces the density slab or the interval encoding with something different to answer a different question.

Eye plot (full violin + interval)

Mirrors the density on both sides. Use when distributional shape is the headline and the asymmetry of half-eye feels too sparse.

Gradient interval

Replaces discrete interval lines with a smooth color gradient encoding interval width. More compact but harder to read precise levels.

Dotsinterval

Replaces the density slab with a quantile-dotplot — a small constant number of dots arranged to mimic the density. Better at communicating frequency framing.

CCDF interval

Plots the complementary cumulative distribution function with intervals overlaid. Used when readers care about the probability of exceeding a threshold.

// 10Comparisons

How it compares

Half-eye sits between violin (more shape) and raincloud (more raw data) in the family of distribution charts. Box plots are the older, lossier alternative.

Half-eye vs violin plot

Both show distributional shape. A violin mirrors the density on both sides; a half-eye uses only one side and adds an explicit point-interval summary, which makes the central tendency and credibility easier to read at a glance.

Half-eye plot

One-sided density paired with a point-and-interval summary. Designed for posteriors and other distributional summaries with explicit uncertainty.

  • Asymmetric — no mirrored ink
  • Point + intervals are explicit
  • Best for posteriors and credible-interval style summaries

Violin plot

Symmetric density on both sides of the categorical axis. Better when shape is the headline and explicit intervals aren’t needed.

  • Mirrored density emphasizes shape
  • No built-in interval summary
  • Better for raw empirical distributions

Half-eye vs raincloud plot

A raincloud is a half-eye with raw data dots glued underneath. The raincloud is more honest about per-observation noise; the half-eye is cleaner and more publication-friendly.

Half-eye plot

Density + point-interval summary, no raw data. Compact and presentation-ready.

  • Lower ink-to-data ratio
  • Cleaner at small sizes
  • Hides individual observations

Raincloud plot

Density + interval + a strip of raw data dots. Better when readers need to see individual observations or sample size.

  • Shows per-observation detail
  • Higher ink-to-data ratio
  • Best with small / moderate n

Half-eye vs box plot

Box plots compress the distribution into five numbers; half-eye plots show the full distributional shape plus an interval summary. Half-eye reveals multimodality and skew that box plots silently hide.

Half-eye plot

Continuous density curve with explicit credible / confidence intervals. Reveals modes, skew, and tails.

  • Shows distributional shape
  • Explicit interval levels
  • Reveals multimodality

Box plot

Five-number summary (min, Q1, median, Q3, max) drawn as a box with whiskers. Compact but lossy.

  • Five-number summary, no shape
  • Hides multimodality
  • Familiar to all audiences

// 11Common mistakes

Common half-eye plot mistakes

The chart is forgiving of bandwidth choice but unforgiving of unlabeled intervals. Almost every bad half-eye in the wild fails on the same handful of issues.

Failing to label the interval levels

Without an explicit caption, no one can tell whether you drew 50/89, 66/95, or 50/95 intervals. State it.

Truncating the density at the interval bounds

Cutting the density off where the 95% interval ends hides skew and tails. Let the curve extend over the actual support.

Mirroring the density to both sides

Mirroring is exactly what a violin does; a half-eye should be one-sided to leave room for the interval visual.

Picking decorative colors instead of meaningful ones

Color should encode group identity. Random palettes from the default colormap dilute the signal.

Plotting on an axis without a reference line

For coefficients, omit the dashed line at zero and you make readers do mental arithmetic. Always include it.

Comparing groups without a consistent axis order

Sort the categorical axis by median or by experimental design. Alphabetical order is almost always wrong.

// 12Accessibility

Accessibility checklist

Run through this list before publishing. The chart should still communicate its message to readers using assistive technology, color-blind users, keyboard navigation, and reduced-motion settings.

  • ✓

    Color contrast for density fills and interval lines

    WCAG 1.4.3
    Density fills should reach at least 3:1 against the chart background; interval lines and the median dot should reach 4.5:1 against any region they overlay so they remain visible at thumbnail size.
  • ✓

    Don’t rely on color to distinguish groups

    WCAG 1.4.1
    When multiple groups share a panel, encode each group’s identity with both color and direct labels (or marker shape). Color-vision-deficient readers should still be able to map each density to its group.
  • ✓

    Provide a text alternative for the chart

    WCAG 1.1.1
    An accessible name should describe the takeaway: “Posteriors for three regression coefficients; all three 95% credible intervals exclude zero with β₁ having the largest median effect.”
  • ✓

    Expose the underlying summary table

    WCAG 1.3.1
    Place a small table next to the chart with each group’s median and interval bounds. Screen readers and analysts both rely on it. The chart’s shape information can’t be experienced without sight.
  • ✓

    Label the interval level explicitly

    WCAG 3.3.2
    Captions or legend entries must say what each line represents (“thick = 66% CI, thin = 95% CI”). Without the label, the visual asymmetry is meaningless.
  • ✓

    Respect prefers-reduced-motion

    WCAG 2.3.3
    If your interactive version animates the density on hover, gate the animation behind prefers-reduced-motion: no-preference. Static rendering is fine for everyone.
  • ✓

    Keyboard-accessible interactivity

    WCAG 2.1.1
    If the plot is interactive (tooltips on hover, focus-driven highlighting), every density / interval pair must be reachable with the Tab key with a visible focus ring.

// 13Best practices

Design and craft tips

A short list of dos and don’ts that separate the publication-quality half-eye plots from the demo-grade ones.

Do

Always draw two interval levels

A thick + thin pair (66% and 95% by convention) communicates uncertainty far better than a single bracket and matches the ggdist default for a reason.
×Don’t

Mirror the density to make a violin

Mirroring the density to both sides is exactly what a violin plot does. Pick one geom or the other; doing both wastes ink without adding information.
Do

Anchor the categorical axis

Sort by posterior median or by experimental design order. Add a reference line at zero or the null value so readers can visually check exclusion.
×Don’t

Truncate the density at the interval bounds

The density should extend to the actual support of the distribution — truncating it at the 95% boundary hides skew and tails.
Do

Label the interval levels in the caption

Without explicit captions, readers can’t know whether you drew 50/89, 66/95, or 50/95 intervals. Always state it.
×Don’t

Use a default colormap with five colors

Half-eye plots rarely need more than 1–3 fill colors per panel. Reserve color for grouping; let the geometry carry the information.
Do

Add a faint reference line at zero

For coefficients, a dashed vertical at zero turns visual interval-checking into a one-glance task.
×Don’t

Overlay raincloud dots when n is huge

If you’re tempted to add raw observation dots and your sample is large, you’ve drifted from a half-eye toward a raincloud. Pick the right chart instead of stacking encodings.

// 15Tool instructions

How to build it in your tool of choice

Recipes for the libraries that have a real half-eye implementation, plus pragmatic workarounds for tools that don’t.

R (ggplot2 + ggdist)

Code — ~5 min
  1. 01Install ggplot2 and ggdist with install.packages(c('ggplot2', 'ggdist')).
  2. 02Reshape your samples into a long-format tibble: one row per draw, columns for group label and numeric value.
  3. 03Pipe into ggplot(df, aes(x = value, y = group)).
  4. 04Add stat_halfeye(.width = c(0.66, 0.95)) for the canonical thick + thin interval pair.
  5. 05Layer geom_vline(xintercept = 0, linetype = 'dashed', alpha = 0.4) when comparing to a null effect.
  6. 06Polish with labs(), theme_minimal(), and a takeaway-style title.

ggdist is the reference implementation — every other tool’s half-eye is essentially a port of Matthew Kay’s grammar.

Python (ArviZ)

Code — ~5 min
  1. 01Install ArviZ and matplotlib with pip install arviz matplotlib.
  2. 02Load your MCMC samples into an ArviZ InferenceData (most PPLs export this directly).
  3. 03Call az.plot_forest(idata, kind='ridgeplot', combined=True) for the closest built-in equivalent.
  4. 04Or use az.plot_density() for true half-density curves and overlay az.plot_hdi() for the intervals.
  5. 05Set figsize tightly so the asymmetric layout doesn’t leave a lot of empty space.
  6. 06Add a title naming the model and the credible interval level.

For more flexible half-eye-style plots in Python, the seaborn.violinplot(split=True, inner='quart') trick gets you most of the way without ArviZ.

JavaScript (Observable Plot)

Code — ~10 min
  1. 01Install Observable Plot with npm i @observablehq/plot or include via CDN.
  2. 02Compute the density per group with a kernel-density helper (Plot.binY or a custom kde).
  3. 03Render the density as Plot.areaX(... , { fillOpacity: 0.3, curve: 'basis' }) restricted to one side of the categorical axis.
  4. 04Compute median and interval bounds with d3.quantile.
  5. 05Overlay Plot.ruleY for the thin (95%) interval and Plot.ruleY({ strokeWidth: 4 }) for the thick (66%) interval.
  6. 06Add a Plot.dot for the median point and a Plot.ruleX at zero for the reference line.

There’s no first-party half-eye mark in Observable Plot today; the recipe above composes density + ruleY + dot to imitate the ggdist output.

Tableau

BI — ~7 min
  1. 01Tableau doesn’t ship a half-eye mark. The two routes are: (1) calculate density bins in a calculated field and draw them as filled-area marks, or (2) embed an R or Python script.
  2. 02For the density route, bin your value column into ~50 buckets and aggregate count per bucket per group.
  3. 03Use a polygon mark to draw the half-density on one side of the categorical axis.
  4. 04Compute median, 66%, and 95% percentiles in three calculated fields.
  5. 05Layer a reference line per group for each percentile, with line thickness encoding the interval level.
  6. 06Combine via dual axis with the polygon density on the secondary axis.

If you have R/Python integration set up, calling ggdist via TabPy / Rserve is dramatically easier than reproducing the geom by hand.

Power BI

BI — ~7 min
  1. 01Power BI has no native half-eye visual. The pragmatic option is the R script visual.
  2. 02Drag your group and value fields into the Values well of an R script visual.
  3. 03Paste a ggplot2 + ggdist script that uses stat_halfeye() on the supplied dataset.
  4. 04Set ggsave dimensions in the script so the visual exports at the report’s panel size.
  5. 05Add a title via the Power BI title bar that names the model and interval levels.
  6. 06Cache the visual’s underlying dataset so the R script doesn’t re-run on every interaction.

The R script visual requires R to be installed locally and configured under File → Options → R scripting in Power BI Desktop.

// 16Code examples

Working code in the most common stacks

Three runnable snippets that produce equivalent half-eye plots in R (the canonical ggdist version), Python (ArviZ), and JavaScript (Observable Plot composition). Each uses the same simulated three-coefficient posterior.

half_eye.R
library(ggplot2)
library(ggdist)
library(dplyr)

set.seed(42)

# Simulated posterior samples for three regression coefficients.
posterior <- bind_rows(
  tibble(parameter = "β₁", draw = 1:4000, value = rnorm(4000,  0.32, 0.12)),
  tibble(parameter = "β₂", draw = 1:4000, value = rnorm(4000, -0.18, 0.16)),
  tibble(parameter = "β₃", draw = 1:4000, value = rnorm(4000,  0.21, 0.10))
)

ggplot(posterior, aes(x = value, y = parameter, fill = parameter)) +
  stat_halfeye(
    .width = c(0.66, 0.95),
    point_interval = "median_qi",
    slab_alpha = 0.5,
    slab_color = "#1a1a18",
    slab_size = 0.4
  ) +
  geom_vline(xintercept = 0, linetype = "dashed", alpha = 0.4) +
  scale_fill_manual(values = c("#c94a2e", "#e8c4b8", "#c94a2e"), guide = "none") +
  labs(
    title = "All three coefficients exclude zero — β₁ has the largest effect",
    subtitle = "Posterior medians with 66% (thick) and 95% (thin) credible intervals",
    x = "Coefficient value",
    y = NULL
  ) +
  theme_minimal(base_size = 12) +
  theme(panel.grid.major.y = element_blank(),
        plot.title.position = "plot")
$ Rscript half_eye.R

// 17 — FAQs

Frequently asked questions

What is a half-eye plot?+

A half-eye plot combines a one-sided density curve (the ‘half eye’) with a point-and-interval summary underneath. The density shows the full distributional shape, while the point-interval provides quick-read statistics — typically a median point with credible or confidence intervals as horizontal lines.

When should you use a half-eye plot?+

Use a half-eye plot when communicating Bayesian posteriors, comparing distributions across groups in a publication-ready way, or when you need to show both shape and key statistics in a single chart. It is the preferred chart when raincloud-style raw data dots would crowd the figure.

When should you avoid a half-eye plot?+

Avoid a half-eye plot when your audience needs to see individual observations — use a raincloud or strip plot instead. Skip it when you have only a handful of data points, when categorical bar charts would communicate the same conclusion, or when readers are not familiar with density curves and credible intervals.

What is the difference between a half-eye plot and a violin plot?+

A violin plot mirrors the density on both sides; a half-eye plot draws it on one side only and pairs it with an explicit point-interval summary below. The asymmetric design avoids the visual redundancy of a full violin while making the median and intervals easier to compare across groups.

What’s the difference between a half-eye plot and a raincloud plot?+

A raincloud plot adds a strip of raw data dots beneath the density and interval. A half-eye plot omits the dots, trading per-observation detail for a cleaner, more compact, publication-ready figure.

Is a half-eye plot suitable for dashboards?+

Yes — half-eye plots work well in dashboards as long as the panel is large enough to read the interval line widths, the title makes clear which interval level is shown, and the comparison axis runs along a consistent direction across panels.

What category of chart is a half-eye plot?+

Half-Eye Plot belongs to the Distribution family of charts. Charts in that family are designed to answer the same kind of question — what is the shape and spread of a continuous variable — so they often work as alternatives when one doesn’t quite fit your data.

How do you read a half-eye plot?+

Start with the density curve to get the overall shape and any skew. Then look at the dot below it for the central tendency, the thicker line for the tighter interval (typically 50% or 66%), and the thinner line for the wider interval (typically 95%). Compare these summaries across groups along the categorical axis.

What’s the best library for building half-eye plots?+

For R, Matthew Kay’s ggdist package (paired with ggplot2) is the canonical choice and the source of the modern half-eye grammar. For Python, ArviZ provides built-in half-eye-style posterior plots, and seaborn or matplotlib can approximate the chart with violinplot half-options. For the web, Observable Plot or D3 with a kernel-density helper produce custom versions.

// 18References

References and further reading

Primary sources, official library documentation, and the foundational papers on Bayesian visualization cited throughout this guide.