Skip to content

Bug report: varwidth = TRUE has no effect in geom_quasirandom (since v0.7.1) #104

Description

@kotliary

===Report generated with Claude assistance===

Summary

The varwidth argument in geom_quasirandom() no longer works as of v0.7.1. All groups are rendered at the same width regardless of their relative sizes, making varwidth = TRUE effectively a no-op. This was working correctly in v0.6.0.

Reproducible Example

library(ggplot2)
library(ggbeeswarm)
library(patchwork)

N1 <- 10
N2 <- 100
set.seed(123)
distro <- data.frame(
  variable = c(rep("runif", N1), rep("rnorm", N2)),
  value    = c(runif(N1, min = -3, max = 3), rnorm(N2))
)

pT <- ggplot(distro, aes(variable, value)) +
  geom_quasirandom(varwidth = TRUE) +
  ggtitle("varwidth = TRUE")

pF <- ggplot(distro, aes(variable, value)) +
  geom_quasirandom(varwidth = FALSE) +
  ggtitle("varwidth = FALSE")

pT + pF

Expected Behavior

The runif group (N = 10) should be rendered with a significantly narrower spread than the rnorm group (N = 100), scaled proportionally to sqrt(10 / 100) = 0.316. This is the behavior documented in the vignette and was working correctly in v0.6.0.

Actual Behavior

Both panels (varwidth = TRUE and varwidth = FALSE) look identical. The width of each group is not scaled by relative group size.

Root Cause Analysis

The regression was introduced in the v0.7.1 refactor of the position functions.

In v0.6.0, PositionQuasirandom used compute_panel, which receives all groups' data simultaneously. This allowed the varwidth scaling to compute max_n — the size of the largest group — across all groups, and then scale each group relative to it:

# v0.6.0 - compute_panel sees all groups at once
max_n <- max(table(data$x))
width_scale <- sqrt(nrow(group_data) / max_n)  # correct relative scaling

In v0.7.x, the refactor switched to compute_group, which only receives one group's data at a time. As a result, max_n is now implicitly the current group's own size, so the scaling factor is always sqrt(n / n) = 1.0 for every group:

# v0.7.x - compute_group sees only the current group
max_n <- nrow(data)  # always equals the current group size
width_scale <- sqrt(nrow(data) / max_n)  # always 1.0 -> no effect

Suggested Fix

The fix requires access to all groups' sizes when computing the width scale for any individual group. Two approaches:

Option A — Switch varwidth logic back to compute_panel:

compute_panel = function(self, data, params, scales) {
  if (self$varwidth) {
    max_n <- max(table(data$x))
    # pass max_n into each per-group call
  }
  # then call compute_group per group with max_n available
}

Option B — Pre-compute max_n across all groups and store it as a position parameter before compute_group is called, then use it inside compute_group.

Workaround

Until this is fixed, varwidth can be approximated manually:

n_per_group <- table(distro$variable)
max_n <- max(n_per_group)
distro$vw <- sqrt(n_per_group[distro$variable] / max_n)

ggplot(distro, aes(variable, value)) +
  geom_quasirandom(aes(width = vw))

Version Information

packageVersion("ggbeeswarm")  # 0.7.3 (affected)
packageVersion("ggplot2")
R.version$version.string
  • Affected versions: v0.7.1, v0.7.2, v0.7.3
  • Last tested working version: v0.6.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions