Skip to content

Implement geom_beeswarm using a quasi-random algorithm - #1068

Open
const-ae wants to merge 1 commit into
has2k1:mainfrom
const-ae:beeswarm
Open

Implement geom_beeswarm using a quasi-random algorithm#1068
const-ae wants to merge 1 commit into
has2k1:mainfrom
const-ae:beeswarm

Conversation

@const-ae

@const-ae const-ae commented Jun 7, 2026

Copy link
Copy Markdown

Fixes #318.

This PR implements a beeswarm algorithm that produces a (in my opinion) visually more appealing output than the random sampling implemented in geom_sina.

n = 200
df = pd.concat([
    pd.DataFrame({'y': np.random.RandomState(125).normal(0, 10, n), 'x': "beeswarm"}),
    pd.DataFrame({'y': np.random.RandomState(125).normal(0, 10, n), 'x': "sina"}),
])
(ggplot(df, aes(x = 'x', y = 'y')) +
    geom_beeswarm(data = lambda df: df[df['x'] == 'beeswarm'], maxwidth = 0.4) +
    geom_sina(data = lambda df: df[df['x'] == 'sina'], maxwidth = 0.4)
)
image

The code and unit testes are mostly copied from [geom|stat]_sina. I removed the random_state parameter as it isn't needed anymore (except when jitter is called). I wasn't sure what the best way to do here is, so any feedback would be appreciated.

I could also refactor the PR, so that stat_sina and stat_beeswarm use a common code base.

The method uses the van der Corput algorithm to produce a low discrepancy sequence between 0 and 1.
@const-ae

Copy link
Copy Markdown
Author

Just checking if this feature is of interest?

@has2k1

has2k1 commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Just checking if this feature is of interest?

Yes. I will get to it before the next minor release v0.16.0.

@has2k1 has2k1 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also refactor the PR, so that stat_sina and stat_beeswarm use a common code base.

They do have a lot of code in common and it would be make sense if the refactor can extract some meaningful structure beyond the stat methods. Otherwise we can leave it as it is.

all_integers = (y == np.floor(y)).all()
some_are_unique = len(np.unique(y)) > 1
if all_integers and some_are_unique:
# TODO: expose random_state as a stat parameter for reproducibility

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


n = 50
random_state = np.random.RandomState(123)
uni = random_state.chisquare(17, n)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce the degrees of freedom so that geometries in the test can cover more area.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

swarmplot

2 participants