ggpubr is ggplot2 with the publication formatting already done
'ggplot2' Based Publication Ready Plots
At a glance
- What is it?
- ggpubr is an R package that wraps ggplot2 with short functions for the plots a researcher actually has to submit, box plots with significance brackets, density plots, ROC curves, forest plots and raincloud plots, and it warns on screen when a comparison is unadjusted. It is on CRAN, and the 1.x line adds statistical helpers and p-value formatting controls.
- Who is it for?
- ggpubr fits a researcher or a student who has to produce journal figures and wants the formatting done without learning ggplot2's layer grammar, since a box plot with a p-value bracket is one call instead of a page of stat and annotate code. It does not fit a team that wants full control over the plot specification, since the wrapper hides the layers it does not expose, and it does not fit a project that has already standardised on a house style built directly on ggplot2.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 84 days ago.
- What is it written in?
- Mainly R, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The stated problem is that ggplot2's syntax is opaque
The README explains the motivation in a way that is unusual for a wrapper package, because it names the cost rather than just the benefit.
ggplot2, by Hadley Wickham, is an excellent and flexible package for elegant data visualization in R. However the default generated plots require some formatting before we can send them for publication. Furthermore, to customize a ggplot, the syntax is opaque and this raises the level of difficulty for researchers with no advanced R programming skills.
So there are two complaints, and they are separate. The first is that the output needs work before publication, which is a matter of defaults. The second is that the syntax for customising is opaque, which is a matter of the layer grammar, and the layer grammar is exactly the thing that makes ggplot2 excellent and flexible.
ggpubr is positioned against the second complaint primarily. It provides some easy-to-use functions for creating and customizing ggplot2-based publication ready plots.
The two together are the trade. You get the output that a journal will accept and the vocabulary a non-programmer can type, and you give up the direct control over layers, themes and scales that an experienced ggplot2 user relies on. A package that promised both would be impossible, since the opacity of the grammar is what makes the flexibility possible.
That also explains the audience. The primary users are researchers producing figures for papers, and the ones who feel the friction most are the ones with limited R programming experience, which is most of them. The documentation site at rpkgs.datanovia.com is the package's main public surface, with the site built by pkgdown and the content in vignettes/ and man/ in the repository.
One call for a box plot with significance brackets
The distribution section shows the pattern in a way that makes the value obvious, because the output is a complete figure rather than a fragment.
library(ggpubr)
data("ToothGrowth")
df <- ToothGrowth
p <- ggboxplot(df, x = "dose", y = "len",
color = "dose", palette =c("#00AFBB", "#E7B800", "#FC4E07"),
add = "jitter", shape = "dose")
pThat is a box plot, coloured by group, with a custom palette, with jittered points added and the point shape varying by group. The equivalent in raw ggplot2 is a boxplot call, a position_jitter call, a scale_colour_manual call and a scale_shape_manual call, assembled in layers.
The part that matters more for a publication is the annotation, and it is a separate call that takes a list of comparisons.
my_comparisons <- list( c("0.5", "1"), c("1", "2"), c("0.5", "2") )
p + stat_compare_means(comparisons = my_comparisons)+ # Add pairwise comparisons p-value
stat_compare_means(label.y = 50) # Add global p-valueSo the pairwise brackets and the overall test label are two additions to an existing plot object, and the comparison list is plain R data, a list of character pairs. The alternative in raw ggplot2 is a data frame of comparisons, a stat layer with a bracket geom, a separate layer for the text, and the positioning arithmetic to stop the brackets overlapping.
The violin version shows the same idea with an add argument that nests a boxplot inside the violin, with the inner boxplot styled separately through add.params.
ggviolin(df, x = "dose", y = "len", fill = "dose",
palette = c("#00AFBB", "#E7B800", "#FC4E07"),
add = "boxplot", add.params = list(fill = "white"))+
stat_compare_means(comparisons = my_comparisons, label = "p.signif")+
stat_compare_means(label.y = 50)The label argument is the other half of the convenience, since p.signif turns the p-value into the compact stars that journals expect, and the same call with the default label gives the numeric value.
The package warns you when your p-values are not adjusted
There is a detail in the README's example output that deserves more attention than it usually gets, and it is the most valuable thing in the package.
#> `stat_compare_means()` with `comparisons` displays *unadjusted* p-values (no correction for multiple comparisons).
#> ℹ For p-values adjusted for multiple comparisons, use `geom_pwc()`, or `stat_pvalue_manual()` together with `compare_means(..., p.adjust.method = )`.
#> This message is displayed once per session.So the function that draws the significance brackets is telling you, out loud, that the numbers on the brackets have not been corrected for multiple comparisons. And it tells you the alternatives, geom_pwc() for adjusted p-values, or stat_pvalue_manual() combined with compare_means() with a p.adjust.method argument.
This matters because unadjusted pairwise p-values on a plot are one of the most common statistical errors in published papers, and they are almost always introduced by convenience. A researcher runs three comparisons, gets three p-values, draws them with the obvious helper, and ships the figure. Nobody reviewed the statistics, only the picture.
The message is printed once per session, so it does not spam the console, and it appears as a condition rather than a warning that would break a script, which is the right level of friction for something that is a documentation problem rather than a crash.
The 1.x line makes the same point structurally. The new helpers include stat_cld() for a compact letter display, which is the standard way of presenting many groups without pairwise brackets at all, add_test_label() for an omnibus test as a subtitle, and automatic bracket packing with geom_pwc(pack = "auto"), which is what stops a dozen comparisons from overlapping into an unreadable mess. The README also notes that sparse grouped subsets are handled robustly in stat_compare_means() and geom_pwc(), with non-comparable subsets skipped while valid comparisons are kept.
The 1.x line adds the plot types a biologist actually needs
The highlights section for version 1.1.0 lists new plot types, and they are the plots that publication workflows tend to demand.
ggcompare() is a one-call group comparison figure, box, violin or strip plus points, means, adjusted p brackets and an omnibus test label, and it includes two-way designs with simple main effects. That is the function that replaces the three-call pattern in the earlier examples, so the newer release has absorbed the pattern rather than only adding to it.
ggrocplot() draws ROC curves with the AUC and its confidence interval, an optional optimal cut-point marker, and multi-marker overlays. The AUC with a confidence interval is the part that a journal reviewer will ask for, and it is not a default in base R plotting.
ggestimates() produces forest and estimation plots, covering odds ratios, hazard ratios and risk ratios, regression coefficients and meta-analysis. ggraincloud() does raincloud plots, a half violin plus a box plus jittered points, which is a distribution display that has become common in the last few years. ggvolcano() does volcano plots for differential expression results, which is the standard figure for transcriptomics and genomics results.
The p-value formatting side has grown too. There are new helpers format_p_value(), get_p_format_style() and list_p_format_styles(), and extended p-value formatting parameters across the statistical layers: p.format.style, p.digits, p.leading.zero, p.min.threshold and p.decimal.mark.
The p.decimal.mark parameter is the one that shows how seriously this is treated, because the decimal mark is not always a point. A European journal or a thesis in a locale that uses a comma wants 0,05 rather than 0.05, and a figure with a mixed convention is a figure a reviewer will query. The p.min.threshold parameter is the corresponding answer for very small values, whether they are shown as p < 0.001 or as a number with a chosen number of digits.
There are also modern compatibility updates for the current ggplot2, dplyr and tidyr, and the vignettes hold worked examples, browsable with browseVignettes("ggpubr").
A CRAN package with a guarded documentation build
The repository layout is a standard R package, and the parts worth mentioning are the ones that are unusual.
The DESCRIPTION file, the NAMESPACE file, the R/ directory with the code, man/ for the manual pages, tests/, vignettes/ for the long form documentation, data/ and data-raw/ for the bundled datasets, inst/ for extra files, and _pkgdown.yml for the site configuration.
The README is generated. The first line of the README file says README.md is generated from README.Rmd, please edit that file, and the R Markdown file is at the root. That is the normal pattern for an R package, where the README is knitted from the code so the examples are executed and the plots in the README are the actual output. The images/ directory holds the figure files, and the plots in the README are written into the man/figures directory.
The check-as-cran.sh script and the cran-comments.md file are the CRAN submission machinery, and the R-CMD-check workflow badge in the README header is the automated version of the same thing. The Makefile wraps the devtools commands.
test:
Rscript --vanilla -e 'devtools::test()'
check:
Rscript --vanilla -e 'devtools::check()'The --vanilla flag is the right habit, since it makes R ignore the user's startup files so the check runs in a clean environment.
The build_site target is the interesting one, and its comment explains a specific hazard. The pkgdown site is built through a guard, which refuses to build while a root CLAUDE.md exists and fails closed if any private artifact survives into docs/. The comment then says never to call pkgdown::build_site() directly, because it renders every root *.md into a public page and bakes the full text into search.json. There is a scrub_site target for verifying an existing docs tree without rebuilding.
That is a guard against publishing private material, and the reason is a specific failure mode of pkgdown, that it picks up every markdown file in the project root. A root CLAUDE.md, the kind of file that holds instructions for an AI assistant working in the repository, would end up as a public documentation page and in the site search index. The guard is more careful than most package maintainers need to be, and it is the sort of thing that tells you the maintainer has had a scare.
Where ggpubr is the wrong choice
The first wrong case is a team with its own house style. A group that has already built a set of internal ggplot2 theme and helper functions, with a fixed palette, a fixed font and a house way of drawing significance, does not need a wrapper that makes its own aesthetic decisions. Using ggpubr there means fighting the wrapper, or maintaining a fork, and the wrapper's value, less typing, is worth less than the consistency it costs.
The second wrong case is a plot the wrapper does not cover. The convenience of ggpubr is that it exposes the common cases. Anything outside them, a custom annotation, an unusual stat, a bespoke theme, a layer the wrapper does not compose, means going back to raw ggplot2, and at that point you are running two styles in one project.
The third wrong case is expecting the version numbering to be stable. The README highlights version 1.1.0 while the newest tagged release is v1.0.0, so the documented features and the published release are not the same thing. A team that pins a version should check which of those highlighted functions exist in the version it pins.
How it differs from writing the ggplot2 layers yourself
The comparison worth making is with the ggplot2 code that the wrapper is standing in for, and the difference in approach is where the decisions live.
Raw ggplot2 is a grammar. You build a plot by adding layers, each layer a statistical transformation plus a set of mappings, and the plot object is the result of that composition. The power comes from being able to add any layer in any combination, and the cost is that a common plot is a common arrangement of layers that you assemble each time.
ggpubr encodes a set of those arrangements. The function knows that a publication box plot wants a boxplot, optionally jitter points, optionally a mean line, optionally a marginal rug, and it applies the group colour and shape mappings, the palette, and the theme in one call. The decisions that a researcher would otherwise make at every plot are made once, by the package author, and they are consistent because they are the same every time.
The second difference is the surface for statistics. In raw ggplot2, adding a significance bracket to a plot is a data frame of comparisons, a stat layer, a geom, a text position calculation, and enough positioning arithmetic to avoid overlap. In ggpubr it is a list of pairs and a function call, and the statistical machinery, the test, the adjustment method and the label formatting, is handled by the same layer that draws the bracket, so it cannot get out of step with the drawing.
The third difference is what you inherit. A wrapper that draws the plot well also decides the theme, the legend position, the label sizes and the default statistics, and those decisions are made for a general audience rather than for your journal. A team that has published before and knows what its target journal wants will find some of them wrong, and then the question becomes whether to override each one or to write the layer directly, and at that point the wrapper's advantage has gone.
Editorial conclusion
ggpubr fits a researcher or a student who has to produce journal figures and wants the formatting done without learning ggplot2's layer grammar, since a box plot with a p-value bracket is one call instead of a page of stat and annotate code. It does not fit a team that wants full control over the plot specification, since the wrapper hides the layers it does not expose, and it does not fit a project that has already standardised on a house style built directly on ggplot2. Before adopting it, read the on screen warning that stat_compare_means() with comparisons displays unadjusted p-values, and decide whether you need geom_pwc() instead, and note that the newest tagged release is v1.0.0 from 2026-07-06 with the last push on 2026-07-31.
Frequently asked questions
How do I install ggpubr?
From CRAN with install.packages("ggpubr"), or the latest version from GitHub with devtools::install_github("kassambara/ggpubr"), installing devtools first if it is not present. The package is published on CRAN with a downloads badge and an R-CMD-check workflow badge in the repository.
Does ggpubr adjust p-values for multiple comparisons?
Not by default, and it tells you so. The README shows the message that stat_compare_means() with comparisons displays unadjusted p-values, and points to geom_pwc(), or stat_pvalue_manual() with compare_means(..., p.adjust.method = ), for adjusted values. The message is displayed once per session.
What new plot types does the ggpubr 1.x line add?
ggcompare() for one-call group comparison figures, ggrocplot() for ROC curves with AUC and confidence interval, ggestimates() for forest and estimation plots, ggraincloud() for raincloud plots, and ggvolcano() for differential expression results, along with stat_cld() for compact letter display and add_test_label() for an omnibus test subtitle.
How do I control how p-values are formatted in ggpubr?
There are the helpers format_p_value(), get_p_format_style() and list_p_format_styles(), plus per-layer parameters p.format.style, p.digits, p.leading.zero, p.min.threshold and p.decimal.mark. The decimal mark parameter matters for locales where the separator is a comma, and p.min.threshold controls how very small values are shown.
How is the ggpubr documentation site built?
The Makefile builds the pkgdown site through a guard script, .github/scripts/build-site.sh, which refuses to build while a root CLAUDE.md exists and fails closed if any private artifact survives into docs/. The comment warns never to call pkgdown::build_site() directly, because it renders every root markdown file into a public page and bakes the text into the site search index.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kassambara-ggpubr)Was this useful?