Skip to contents

Group comparisons are the bread and butter of statistical graphics: bars, boxes, violins, and cumulative curves all place summaries or distributions side by side.

Bars

Counts by class

Count bars tally the rows per category — map only x and mark_bar() knows to count. Here it shows how many cars of each class are in the ggplot2::mpg data.

ggplot2::mpg |>
  plotit(encode(x = class)) |>
  mark_bar()

Value bars

Map y as well and the bars read their heights directly from a data column. Inline data frames are a convenient source for small, hand-built figures.

dfv <- data.frame(cat = c("A", "B", "C", "D"), val = c(12, 7, 19, 5))
dfv |>
  plotit(encode(x = cat, y = val)) |>
  mark_bar()

Stacked bars

Mapping a second categorical variable to fill and stacking shows the composition of every group at a glance.

ggplot2::mpg |>
  plotit(encode(x = class, fill = drv)) |>
  mark_bar(position = "stack")

Filled bars

With position = "fill" every stack is scaled to a constant height, turning the comparison into group shares that sum to one.

ggplot2::mpg |>
  plotit(encode(x = class, fill = drv)) |>
  mark_bar(position = "fill")

Flipped bars

Long category labels read better along the horizontal axis — flip the coordinates with project_cartesian(flip = TRUE).

ggplot2::mpg |>
  plotit(encode(x = class)) |>
  mark_bar() |>
  project_cartesian(flip = TRUE)

Grouped bars

Colour by a second group and dodge the bars side by side. Value bars need pre-aggregated data, so summarise with aggregate() first.

mpg_grp <- aggregate(hwy ~ class + drv, data = ggplot2::mpg, FUN = mean)
mpg_grp |>
  plotit(encode(x = class, y = hwy, fill = drv)) |>
  mark_bar(position = "dodge")

Lollipop and Dumbbell

Lollipop chart

mark_lollipop() anchors each point with a stem at zero, giving bar-like rankings without the visual weight of a filled rectangle.

dfl <- data.frame(cat = LETTERS[1:6], val = c(3, 7, 2, 9, 5, 6))
dfl |>
  plotit(encode(x = cat, y = val)) |>
  mark_lollipop()

Dumbbell chart

mark_dumbbell() connects paired before/after values with a line. Encode the start in y and the end in yend; each row is one comparison.

dfd <- data.frame(
  item = c("A", "B", "C", "D", "E"),
  before = c(3, 5, 2, 8, 4),
  after = c(7, 6, 5, 10, 6)
)
dfd |>
  plotit(encode(x = item, y = before, yend = after)) |>
  mark_dumbbell()

Boxplots

Boxplot by group

mark_boxplot() summarises a distribution with quartiles and outliers. Fill by the grouping variable so each box is visually distinct.

iris |>
  plotit(encode(x = Species, y = Sepal.Length, fill = Species)) |>
  mark_boxplot()

Grouped boxplots

Two grouping variables on the same axes make pairwise comparisons easy — engine cyl on x, transmission am mapped to fill.

mtcars |>
  plotit(encode(x = factor(cyl), y = mpg, fill = factor(am))) |>
  mark_boxplot()

Violins and Strips

Violin plot

A violin shows the full density shape instead of just the quartiles. draw_quantiles = 0.5 marks the median with a line across each body.

iris |>
  plotit(encode(x = Species, y = Sepal.Length, fill = Species)) |>
  mark_violin(draw_quantiles = 0.5)

Beeswarm

mark_beeswarm() packs every point without overlap, preserving each observation while showing where the data are densest.

iris |>
  plotit(encode(x = Species, y = Sepal.Length, colour = Species)) |>
  mark_beeswarm()

Strip plot

A strip plot jitters points horizontally along each group. set.seed() keeps the jitter reproducible across renders.

set.seed(42)
iris |>
  plotit(encode(x = Species, y = Sepal.Length, colour = Species)) |>
  mark_point(position = "jitter", alpha = 0.5, size = 1.5)

Boxplot with jitter overlay

Layer a boxplot and jittered points together for the classic raw-data-plus- summary view. Suppress the boxplot’s own outliers so points are not doubled.

set.seed(42)
iris |>
  plotit(encode(x = Species, y = Sepal.Length, fill = Species)) |>
  mark_boxplot(outlier.shape = NA) |>
  mark_point(mapping = encode(colour = Species), position = "jitter", alpha = 0.4, size = 1)

Cumulative Distributions

Single ECDF

mark_ecdf() draws the empirical cumulative distribution as a step — every observation is represented exactly, with no binning parameter to choose.

faithful |>
  plotit(encode(x = eruptions)) |>
  mark_ecdf()

ECDF by group

Colouring the ECDF by a group compares entire distributions at once: shifts, spreads, and tail behaviour are all visible.

ec <- data.frame(
  value = c(iris$Sepal.Length, iris$Petal.Length),
  part = rep(c("Sepal", "Petal"), each = 150)
)
ec |>
  plotit(encode(x = value, colour = part)) |>
  mark_ecdf()

Distributions Across Groups

Side-by-side histograms

Dodging histograms by a fill group aligns bins between categories, making shape comparisons direct.

iris |>
  plotit(encode(x = Sepal.Length, fill = Species)) |>
  mark_histogram(position = "dodge", bins = 20)

Density by group

Overlapping density curves are the smoothest way to compare several groups on one panel. Mapping both fill and colour gives a translucent curve outline.

iris |>
  plotit(encode(x = Sepal.Length, fill = Species, colour = Species)) |>
  mark_density(alpha = 0.4)