'ggplot2' Based Publication Ready Plots
Overview
The 'ggplot2' package is excellent and flexible 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. 'ggpubr' provides some easy-to-use functions for creating and customizing 'ggplot2'-based publication ready plots. This version includes modern R ecosystem compatibility updates and customizable p-value formatting presets (APA, AMA, NEJM, Lancet, GraphPad, and scientific notation) for publication workflows, plus robust sparse-subset handling in statistical annotation layers such as 'stat_compare_means()' and 'geom_pwc()', with informative per-group skip diagnostics for non-comparable subsets.
New features
New functions
- Added
format_p_value(),get_p_format_style(), andlist_p_format_styles(). - Added style presets (
default,apa,nejm,lancet,ama,graphpad,scientific).
New arguments, computed variables, and input support
ggmaplot()gains an optionalfacet.byargument to split the MA plot into multiple panels (e.g. one per contrast or species). Top genes are selected per panel, while point colors use the same significance thresholds in every panel. Default output (nofacet.by) is unchanged (#498).stat_cor()andstat_regline_equation()gain alabel.anchorargument. Withlabel.anchor = "panel"the label is placed at the true panel-relative position (npc), so labels stay aligned across panels or facets whose axis ranges differ - e.g.facet_wrap(scales = "free_y"),geom_smooth()extending each panel by a different amount, or separate plots combined withggarrange(). The defaultlabel.anchor = "data"keeps the previous data-range placement, so existing plots are unchanged (#248).
Show all changes in 1.0.0Hide the rest of the changes in 1.0.0
geom_bracket()gains anorientationargument.orientation = "vertical"draws a native vertical bracket (the bar spans the y axis, the tips point left and the label is rotated), specified withymin/ymax/x.position— useful to annotate plots where the comparison runs along the y axis, e.g. Kaplan-Meier curves (it is designed for a continuous x axis). The defaultorientation = "horizontal"is unchanged, and it cannot be combined withcoord.flip = TRUE(#456).ggpaired()gains an opt-injitterargument that spreads the paired points horizontally to reduce overlap. Each subject (id) gets a single offset, so its two points move together and the connecting line stays intact, and only the horizontal positions change (the values are never moved). Defaultjitter = 0leaves the plot unchanged (#407).ggboxplot(),ggviolin()andggstripchart()gain an opt-inshow.nargument. Whenshow.n = TRUE, the number of observations ("n = <count>") is displayed at the top of each group. When the groups are dodged (acolor/fillgrouping with a dodgingposition), one count is shown per group; otherwise a single count is shown per x-axis tick. Counts respectselect/removeand are computed per facet. Default isshow.n = FALSE, so existing plots are unchanged (#598, #627).ggarrange()gains aspacingargument to increase the gap between the arranged plots (a long-requested option). It adds a uniform margin, in text-line units, around each plot. Default is0(no extra space; each plot keeps its own margins, so existing arrangements are unchanged) (#151).compare_means()gains anidargument for paired comparisons. Without it, a paired test (paired = TRUE) pairs observations by row order, so the p-value is wrong when the data are not sorted so that the compared groups align by subject. Passingid(the name of a subject-identifier column) pairs the observations by id instead — row-order independent, using only the complete pairs (viarstatix, so the result matches a directrstatix::t_test()/wilcox_test()call). It works for a two-group, a pairwise (more than two groups) and aref.groupcomparison witht.test/wilcox.test. Default (id = NULL) is unchanged (#560).stat_compare_means()also gains theidargument, so a paired test can be aligned by subject id directly on a plot (e.g.ggpaired(...) + stat_compare_means(paired = TRUE, id = "subject")), displaying the correct p-value even when the data are not sorted by subject. Plots withoutidare unchanged (#560).geom_pwc()andstat_pwc()gain ap.adjust.nargument giving the number of comparisons to use for the p-value adjustment (passed asntostats::p.adjust()). Default isNULL(adjust by the number of computed p-values, unchanged behavior); set it when the displayed comparisons are a subset of a larger family so the adjustment reflects that larger size. It must be at least the number of p-values being adjusted, and applies to the panel-level adjustment (p.adjust.by = "panel") and the single-comparison case (#612).annotate_figure()gainscolumn.titlesandrow.titlesarguments to add a title above each column and to the left of each row of an arranged grid (useful for publication panels). Each accepts a character vector (one title per column / per row, rendered in bold) or a list of grobs (e.g. built withtext_grob()) for full styling control;row.titlesare rotated 90 degrees. The titles are placed in reserved space (they do not overlap the plots) and are evenly spaced, so they assume equal-sized columns / rows (theggarrange()default). Default isNULL(no titles; existing figures are unchanged) (#573).ggbarplot()gains anumeric.x.axisargument (already available inggline()andggerrorplot()). Withnumeric.x.axis = TRUEthe x variable is kept numeric instead of being coerced to a discrete factor, so bars are drawn at their numeric x positions (e.g. a time axis). Default isFALSE(unchanged behavior) (#463).geom_bracket()andstat_pvalue_manual()gain atip.length.refargument controlling whattip.lengthis a fraction of. With the defaulttip.length.ref = "data"the tips are a fraction of the data range (unchanged behavior). Withtip.length.ref = "axis"the tips are a fraction of the y-axis range (ylim/scale_y_*), which renders at the same physical fraction across plots and therefore gives visually constant tip lengths regardless of the data range - useful to keep tips consistent across facets or across separate plots with different scales (#362).stat_compare_means(comparisons =)draws its brackets viaggsignif::geom_signif()and does not supporttip.length.ref; passing it there now emits an informative message pointing tostat_pvalue_manual(..., tip.length.ref = "axis")instead of being silently ignored (#362).stat_cor()andstat_regline_equation()gain alabel.y.stepargument giving the vertical spacing (in text-line units) between the labels of successive groups. Default is1.4(unchanged behavior). Settinglabel.y.step = 0stops the per-group vertical shift so labels align across facet panels when a factor is mapped to an aesthetic that also defines the facets (the long-standing "labels climbing stairs across facets" behavior; #248). This control was inspired by thevstepargument in the ggpmisc package (by Pedro J. Aphalo), from which ggpubr's correlation/regression label-positioning logic was originally adapted; this is acknowledged via@seealsoin?stat_cor.ggviolin()gains adropargument (passed toggplot2::geom_violin()), defaultTRUE(unchanged behavior). Previously the argument was silently dropped, so grouped violins where a sub-sample has too few points to draw a density (whichgeom_violin()removes, including from the dodge position) could not be kept aligned with added boxplots / dot plots. Settingdrop = FALSEtogether withposition = position_dodge(0.8, preserve = "single")now reserves the empty dodge lane so all geoms stay aligned (#381).ggarrange()gained the ability to choose which plot(s) supply the shared legend:common.legendnow also accepts one or several plot indices.common.legend = 2uses the second plot's legend (handy when the first plot's legend is not representative, e.g. a group is missing in the first plot), andcommon.legend = c(1, 2)keeps and combines the legends of the listed plots into a single shared block (side by side forlegend = "top"/"bottom", stacked for"left"/"right") - useful when the arranged plots genuinely need different legends. The documentation ofcommon.legendwas also clarified:common.legend = TRUEkeeps the first plot's legend (it does not merge or validate legends), so plots should share a consistent scale for a single shared legend to be correct. LogicalTRUE/FALSEbehave exactly as before (#347).ggdonutchart()gained alabel.repelargument (defaultFALSE). WhenTRUE, slice labels are placed withggrepel::geom_text_repel()and connected to their slice with leader lines, so the labels of many small slices no longer overlap (or get dropped). The default output is unchanged (#655).ggboxplot()now accepts apositionargument (e.g.position = position_dodge(0.9)), likeggviolin()/ggdotplot()already do. Previously passingpositionerrored (formal argument "position" matched by multiple actual arguments) because the dodge was hardcoded; the default is unchanged (position_dodge(0.8)), so grouped box plots look the same unless you set it (#615).ggsummarytable()gains anangleargument to rotate the summary-table text; default (angle = 0) is unchanged (#595).ggmaplot()gains aline.colorargument to set the threshold line color; default ("black") is unchanged (#322).ggarrange()gains abyrowargument (defaultTRUE) to fill the plot grid by column (byrow = FALSE) instead of by row; forwarded tocowplot::plot_grid()(#225).stat_regline_equation()gainscoef.digitsandrr.digitsarguments to control the number of significant digits shown for the regression-equation coefficients and R2; defaults (2) reproduce the previous output (#312).- Added formatting parameters to statistical helpers:
p.format.stylep.digitsp.leading.zerop.min.thresholdp.decimal.mark- Added
p.format.signifsupport and related label handling paths. stat_pvalue_manual()gains ap.digitsargument (default3) that formats **numeric** p-value label columns for display (e.g.label = "p"orlabel = "p.adj"), using the sameformat_p_value()engine asstat_anova_test(). This restores clean labels (e.g.0.0156instead of0.015625) with rstatix>= 1.0.0, which now returns full-precision pairwise p-values. Character labels (significance symbols, pre-formatted strings, glue expressions) are unaffected. Setp.digits = NULLto print the raw value.stat_pvalue_manual()additionally gainsp.format.style,p.leading.zero,p.min.thresholdandp.decimal.mark, matching the p-value formatting arguments already available instat_compare_means(),geom_pwc()andstat_anova_test(). These are opt-in and default so that the rendered labels are unchanged; for examplep.min.threshold = 0.001displays very small p-values as< 0.001, andp.format.style = "nejm"applies a journal style.stat_cor()gains two label-formatting arguments:r.leading.zero— set toFALSEto drop the leading zero of the correlation coefficient (e.g..73instead of0.73), completing APA-style reporting together withp.leading.zero(#540). The dropped leading zero is preserved through plotmath rendering (the value is quoted so it is not silently re-normalized back to0.73in the defaultexpressionoutput).p.coef.name— symbol for the p-value label; use"P"for an uppercase p-value (#541).stat_cor()exposes two new computed variables,rmseandrmse.label, for the root mean square deviation (RMSE/RMSD) betweenxandy— useful for reporting agreement between paired measurements on the same scale (e.g. predicted vs. reference values). Display it withaes(label = after_stat(rmse.label)), or combine it with the correlation coefficient usingpaste(). The default label is unchanged (#458).stat_cor()can now display the confidence interval of the correlation coefficient, via the new computed variablesconf.int.low,conf.int.highandconf.int.label(e.g."95% CI [0.21, 0.75]") and aconf.levelargument (default0.95). Show it withaes(label = after_stat(conf.int.label)), or combine it with the coefficient usingpaste(). The confidence interval is available formethod = "pearson"only; it isNAfor Spearman/Kendall. The default label is unchanged (#418).
Main changes
Compatibility
- Added compatibility updates for modern
ggplot2,dplyr, andtidyr. - Updated legacy
sizeusage tolinewidthwhere required by recentggplot2. - Completed the
size->linewidthmigration for the remaining line layers that still passed the deprecatedsizeargument: the mean/median reference line added bygghistogram(add = ...)andggdensity(add = ...), and the connector segments ofggdotchart(add = "segments"). These no longer emit ggplot2's "sizeaesthetic for lines was deprecated" or "Ignoring empty aesthetic:size" warnings, and the requested line width is now applied vialinewidth. ggmaplot()no longer emits an "Ignoring empty aesthetic:size" warning on its default call; the point layer setssizeonly when the user supplies a value.- Replaced deprecated tidyverse APIs in affected helper functions.
- Added package startup lock-file checks and
clean_lock_files()helper. - Relaxed minimum
ggrepeldependency to>= 0.9.2to keep Ubuntu oldrel (R 4.4.x) CI dependency resolution working. geom_pwc()now detects an absentref.groupin a grouped subset via therstatix_missing_ref_groupcondition class raised by recentrstatix(walking the parent chain withrlang::cnd_inherits()), in addition to matching the error message. This keeps the "skip ref-less subsets" behaviour working afterrstatixmade that error message clearer (rstatix #153); olderrstatixversions are still handled via the message fallback.- The minimum required
rstatixversion is now>= 1.0.0(the version that introduced theidargument used bycompare_means(id = ), and the full-precision pairwise p-values relied on by the p-value formatting).
Validation and messaging
- Hardened sparse-group handling in
stat_compare_means()andgeom_pwc(). - Non-comparable grouped subsets (insufficient levels/observations) are now skipped while preserving valid inferential comparisons in the same layer.
- Added per-group skip diagnostics in
geom_pwc()so users can see which grouped subsets were skipped and why (e.g., missingref.groupor insufficient levels). - Added regression tests for mixed comparable/non-comparable subsets and fully sparse subsets.
- Passing
ggtheme = NULLto the plotting functions (e.g.ggscatter(),ggboxplot(),ggline(),gghistogram(), ...) now skips applying a ggpubr theme, so the plot keeps ggplot2's default theme or the theme set globally withtheme_set(). Previously an explicitggtheme = NULLwas treated like an unset argument andtheme_pubr()was still applied. Calls that omitggthemeor pass a specific theme are unchanged (#561). compare_means()now adjusts p-values **within** eachgroup.bylevel (and response) rather than pooling all groups together, so a grouped adjustment matches filtering to one group and adjusting there. This changesp.adjvalues for calls that usegroup.by; ungrouped calls are unchanged (#200).stat_compare_means(comparisons = )now emits a one-time message (and the docs note) clarifying that the displayed pairwise p-values are **not** adjusted for multiple comparisons, pointing togeom_pwc()/stat_pvalue_manual()+compare_means(p.adjust.method = )for corrected p-values (#293).
Documentation
- Documented a recipe for labelling groups with significance letters (compact letter display): compute the pairwise comparisons and derive the letters with
rstatix::add_cld(), then place them withgeom_text()(see the "Significance letters" section in?compare_means). Also covers the per-control letters case (a = differs from control 1, b = differs from control 2) (#464, #434). - Documented that a summarized
ggbarplot()(e.g.add = "mean_se") must be faceted with thefacet.by=argument, not by appending+ facet_wrap()/+ facet_grid(): the summaries are pre-computed overfacet.by, so a manually added facet pools the bars (and, for stacked bars, the error bars) across all panels (#739). - Clarified document
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
NOTERd contentsr-devel-linux-x86_64-debian-clang, r-devel-linux-x86_64-debian-gcc
Rd files without \usage: ‘ggpubr_args.Rd’ \arguments should not be documented without \usage.
- NOTE11 OK, 2 NOTENow
- NOTE12 OK, 1 NOTE2026-07-11
- OK13 OK2026-06-09
- ERROR12 OK, 1 ERROR2026-06-07
- OK13 OK2026-06-03
- ERROR11 OK, 2 ERROR2026-05-25
Show 1 earlier changeHide 1 earlier change
- OK14 OK2026-03-10
Documentation
- Help pages
- 93 (9 internal)includes a package overview page
- Examples that run
- 94%78 run in a plain check · 5 never run
- Exported functions with examples
- 96%
- Documented parameters
- 92%
- Return-value docs
- 27%
- Help pages citing references
- 1%
Downloads
Repository
- PRs merged (all time)
- 85%
- Median days to close, 50 most recently updated closed issues
- 1,724
- Median days to close, 50 most recently updated closed PRs
- 0
Stars over time
Forks over time
Releases over time
Issues over time
PRs over time
Commits over time
10 development-tooling and community-health practices detected across 7 families in the upstream repository
Checks run against github.com/kassambara/ggpubr on 2026-10-21.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
Show all practicesHide 3 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2025-12-21Named in .Rbuildignore: .claude, CLAUDE.local.md, CLAUDE.mdNamed in .gitignore: .claude, CLAUDE.mdCommits: at least 10 crediting it in the message
- Codexfirst seen 2026-07-04Named in .Rbuildignore: .codexNamed in .gitignore: .codex
- Claude Opus 4.56 commits · Dec 2025
- Claude Opus 4.62 commits · Feb 2026
Dependencies
Installing ggpubr adds 67 packages (33 with compiled code, 62 MB of source tarballs). Heaviest: stringi 11 MB, ggplot2 6.0 MB, doBy 4.3 MB.
Show the 48 most downloaded dependentsHide the 48 most downloaded dependents
and 181 more dependents and 89 packages that suggest it on CRAN ↗
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 1.0.0Latest2026-07-06 · current release · diff ↗ ·
added 5 exports
clean_lock_files, create_p_label, format_p_value, get_p_format_style, list_p_format_styles - RR 4.6.0 released · 2026-04-24
- 0.6.32026-02-24 · diff ↗
- 0.6.22025-10-17 · diff ↗
- 0.6.12025-06-27 · diff ↗
- RR 4.5.0 released · 2025-04-11
- RR 4.4.0 released · 2024-04-24
- RR 4.3.0 released · 2023-04-21
- 0.6.0
- 0.5.02022-11-16 · diff ↗ ·
added 12 exports
as_npc, as_npcx, as_npcy, geom_pwc, get_coord, ggpubr_options, npc_to_data_coord, stat_anova_test, stat_friedman_test, stat_kruskal_test, stat_pwc, stat_welch_anova_test - RR 4.2.0 released · 2022-04-22
- RR 4.1.0 released · 2021-05-18
- 0.4.02020-06-27 · diff ↗ ·
added 14 exports
create_aes, get_breaks, median_hilow_, median_q1q3, tab_add_border, tab_add_footnote, tab_add_hline, tab_add_title, tab_add_vline, tab_cell_crossout, tab_ncol, tab_nrow, tbody_add_border, thead_add_border - 0.3.02020-05-04 · diff ↗ ·
added 4 exports
clean_table_theme, get_summary_stats, ggsummarystats, ggsummarytable - RR 4.0.0 released · 2020-04-24
- 0.2.52020-02-13 · diff ↗
Show 19 earlier eventsHide 19 earlier events
- 0.2.42019-11-14 · diff ↗
- 0.2.3
- 0.2.2
- 0.2.1
- RR 3.6.0 released · 2019-04-26
- 0.22018-11-15 · diff ↗
- 0.1.92018-11-11 · diff ↗
- 0.1.82018-08-30 · diff ↗ ·
added 5 exports
%>%, group_by, mutate, stat_pvalue_manual, stat_regline_equation - 0.1.7
- RR 3.5.0 released · 2018-04-23
- 0.1.62017-11-14 · diff ↗ ·
added 5 exports
ggballoonplot, ggscatterhist, table_cell_bg, table_cell_font, theme_pubclean - 0.1.52017-08-22 · diff ↗
- 0.1.42017-06-28 · diff ↗ ·
added 17 exports
annotate_figure, as_ggplot, background_image, clean_theme, colnames_style, color_palette, fill_palette, get_legend, ggparagraph, ggtexttable, gradient_color, gradient_fill, rownames_style, tbody_style, text_grob, theme_transparent, ttheme - 0.1.32017-06-06 · diff ↗ ·
added 30 exports
add_summary, bgcolor, border, change_palette, compare_means, facet, font, ggadd, ggarrange, ggexport, ggpaired, grids, mean_ci, mean_range, mean_sd, mean_se_, median_iqr, median_mad, median_range, rotate, rotate_x_text, rotate_y_text, rremove, set_palette, stat_compare_means, stat_cor, stat_stars, theme_cleveland, xscale, yscale - RR 3.4.0 released · 2017-04-21
- 0.1.2
- 0.1.1
- 0.1.02016-07-20
- RR 3.3.0 released · 2016-05-03
Package metadata
- First published
- 2016-07-20
- Total releases
- 24 / 10 yrs
- License
- GPL (>= 2) OSI
- Minimum R
- ≥ 4.1.0
- Bundled data
- 779 KB / 3 files
- Download size
- 1.8 MB
Cite
Cite this package
Run in R for the authors' entry:
citation("ggpubr")The package provides a citation file, so citation() returns the authors' own entry.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-24, which the citation names so these numbers can be found later. More on citing and the projects behind them.