ggplot2 extensions¶
ggplot2 has a large family of extension packages in R. ggrust builds many of the most popular ones straight into the library, with the R package's function and parameter names, so there is nothing extra to install. lets-plot has none of these, so this is where ggrust goes beyond a lets-plot replacement.
Each feature below credits the R package it comes from. ggrust reimplements the behavior in Rust; the R packages and their authors designed it.
Two more, geom_text_repel / geom_label_repel (ggrepel)
and geom_area_ridges (ggridges), come
with the lets-plot API and are covered in Layers. ggrust also adds core
ggplot2 features lets-plot lacks: geom_col, geom_rug, stat_ellipse, and secondary
axes with sec_axis / dup_axis.
Beeswarm points¶
geom_beeswarm places points side by side so that none overlap, which shows every
observation and the shape of the distribution at once. geom_quasirandom spreads them
by the local density instead. Both follow
ggbeeswarm by Erik Clarke, Scott
Sherrill-Mix and Charlotte Dawson.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
penguins = pl.read_csv(DATA + "penguins.csv").drop_nulls("body_mass_g")
plot = (
ggplot(penguins, aes("species", "body_mass_g", color="species"))
+ geom_beeswarm(size=2, cex=2.5, show_legend=False)
+ scale_color_npg()
+ labs(x="", y="body mass (g)")
+ theme_light()
+ ggsize(800, 500)
)
plot
Journal palettes¶
The scale_color_* / scale_fill_* palettes of
ggsci by Nan Xiao take their colors from
scientific journals and data tools: npg (Nature Publishing Group), aaas (Science),
nejm, lancet, jama, jco, d3 and igv. igv has 51 colors, enough for many
clusters. The examples on this page use several of them.
Significance tests¶
stat_compare_means runs the test (t.test, wilcox.test, anova, kruskal.test),
adjusts the p-values if asked (p_adjust_method) and draws the brackets and labels, as
in ggpubr by Alboukadel Kassambara. The
statistics are computed in Rust and match R's own tests. stat_cor labels a
correlation and stat_poly_eq a fitted polynomial, as in ggpubr and
ggpmisc by Pedro J. Aphalo.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
penguins = pl.read_csv(DATA + "penguins.csv").drop_nulls("flipper_length_mm")
plot = (
ggplot(penguins, aes("species", "flipper_length_mm"))
+ geom_boxplot(aes(fill="species"), alpha=0.6, outlier_alpha=0, show_legend=False)
+ geom_quasirandom(aes(color="species"), size=1.2, width=0.25, show_legend=False)
+ stat_compare_means(
comparisons=[("Adelie", "Chinstrap"), ("Chinstrap", "Gentoo"), ("Adelie", "Gentoo")],
label="p.signif",
)
+ scale_fill_jco()
+ scale_color_jco()
+ labs(x="", y="flipper length (mm)")
+ theme_classic()
+ ggsize(800, 550)
)
plot
Cluster marks¶
geom_mark_hull, geom_mark_ellipse and geom_mark_rect outline each group and can
label it outside the data, as in ggforce
by Thomas Lin Pedersen.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
penguins = pl.read_csv(DATA + "penguins.csv").drop_nulls("bill_length_mm")
plot = (
ggplot(penguins, aes("bill_length_mm", "bill_depth_mm"))
+ geom_mark_hull(
aes(fill="species", label="species"), concavity=8, alpha=0.15, show_legend=False
)
+ geom_point(aes(color="species"), size=2, show_legend=False)
+ scale_color_aaas()
+ scale_fill_aaas()
+ expand_limits(x=[28, 64], y=[12, 23])
+ labs(x="bill length (mm)", y="bill depth (mm)")
+ theme_minimal()
+ ggsize(800, 600)
)
plot
Highlighting¶
gghighlight keeps the groups that match a condition in color and draws the rest in
grey behind them, with optional direct labels. The condition is a Polars expression,
evaluated per group by default. It follows
gghighlight by Hiroaki Yutani.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
cities = pl.read_csv(DATA + "cities.csv")
plot = (
ggplot(cities, aes("month", "temperature", color="city"))
+ geom_line(size=1.5)
+ gghighlight(pl.col("temperature").min() < 0, label_key="city")
+ scale_x_continuous(breaks=list(range(1, 13)))
+ labs(y="mean temperature (°C)")
+ theme_minimal()
+ ggsize(800, 500)
)
plot
Point density¶
geom_pointdensity colors each point by the number of neighbours around it, so dense
regions stand out in a scatter plot that would otherwise overplot. It follows
ggpointdensity by Lukas P. M.
Kremer, and stays fast on millions of points.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
homes = pl.read_csv(DATA + "homes.csv")
plot = (
ggplot(homes, aes("area_m2", "price_k"))
+ geom_pointdensity(size=2, adjust=4)
+ scale_color_viridis()
+ labs(x="floor area (m²)", y="price (k)", color="neighbours")
+ theme_light()
+ ggsize(800, 550)
)
plot
Side panels¶
geom_xside* and geom_yside* layers draw into a panel beside the main one that
shares its x or y axis, after ggside by
Justin Landis. They stay aligned with the main panel, also when you zoom.
ggmarginal is lets-plot's shortcut for the same
idea.
Unlike ggside, an axis can have more than one band: side_pos="left" (or "right",
"top", "bottom") puts a layer's band on that side, side_band="name" gives it a band
of its own (bands on one side stack outward in plot order) and side_size=0.2 sets its
thickness. scale_ysidex_discrete(side_pos="left", ...) sets that band's own axis.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
penguins = pl.read_csv(DATA + "penguins.csv").drop_nulls("bill_length_mm")
plot = (
ggplot(penguins, aes("flipper_length_mm", "bill_length_mm", color="species"))
+ geom_point(size=1.8)
+ geom_xsidedensity(aes(fill="species"), alpha=0.4)
+ geom_ysideboxplot(aes(x="species", fill="species"), alpha=0.4, show_legend=False)
+ scale_color_nejm()
+ scale_fill_nejm()
+ labs(x="flipper length (mm)", y="bill length (mm)")
+ theme_light()
+ ggsize(800, 600)
)
plot
Clustered heatmaps¶
A clustered heatmap combines three extensions:
hclustclusters the rows (or columns) andscale_y_dendroorders the axis by the tree and draws it beside the labels, after legendry and ggh4x by Teun van den Brand.geom_dendrogramdraws a tree as a layer, as ggdendro by Andrie de Vries does. A tree computed elsewhere, for example by scipy or scanpy, can be passed in as its linkage matrix.new_scale_fillstarts a second fill scale, so each annotation track gets its own colors and legend, as in ggnewscale by Elio Campitelli.geom_ysidetiledraws the annotation track beside the heatmap (see Side panels).
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
fruit = pl.read_csv(DATA + "fruit.csv")
nutrients = ["calories", "sugar_g", "fibre_g", "vitamin_c_mg"]
scaled = fruit.with_columns((pl.col(n) - pl.col(n).mean()) / pl.col(n).std() for n in nutrients)
rows = hclust(scaled.select("fruit", *nutrients), labels="fruit", method="average")
long = scaled.unpivot(index="fruit", on=nutrients, variable_name="nutrient", value_name="z")
plot = (
ggplot(long, aes("nutrient", "fruit"))
+ geom_tile(aes(fill="z"), color="white")
+ scale_fill_gradient2(low="#2166ac", mid="white", high="#b2182b", name="z-score")
+ scale_y_dendro(rows)
+ new_scale_fill()
+ geom_ysidetile(
aes(x="calorie", fill="calories"), data=fruit.with_columns(calorie=pl.lit("kcal"))
)
+ scale_fill_viridis(option="cividis", name="kcal")
+ labs(x="", y="")
+ theme_minimal()
+ ggsize(800, 650)
)
plot
Raincloud plots¶
stat_halfeye draws a density slab with the median and quantile intervals, after
ggdist by Matthew Kay.
geom_half_violin, geom_half_boxplot and geom_half_point draw half of each shape,
after gghalves by Frederik Tiedemann.
Together they make raincloud plots; panel B of the next example is a stat_halfeye.
Figure layouts¶
gggrid accepts a design string that names a cell per letter, and tag_levels tags
the panels A, B, C (or a, 1, i). inset_element places a plot inside another. These
follow patchwork by Thomas Lin
Pedersen.
import polars as pl
from ggrust import *
DATA = "https://raw.githubusercontent.com/datavil/ggrust/main/docs/data/"
penguins = pl.read_csv(DATA + "penguins.csv").drop_nulls("bill_length_mm")
scatter = (
ggplot(penguins, aes("bill_length_mm", "bill_depth_mm", color="species"))
+ geom_point(show_legend=False)
+ scale_color_lancet()
+ labs(x="bill length (mm)", y="bill depth (mm)")
)
mass = (
ggplot(penguins, aes("species", "body_mass_g", fill="species"))
+ stat_halfeye(show_legend=False)
+ scale_fill_lancet()
+ labs(x="", y="body mass (g)")
)
islands = (
ggplot(penguins, aes("island", fill="species"))
+ geom_bar()
+ scale_fill_lancet()
+ labs(x="", y="penguins")
)
plot = gggrid(
[scatter, mass, islands],
design="AAB\nCCB",
tag_levels="A",
guides="collect",
) + ggsize(900, 600)
plot
Rasterized layers¶
rasterize(layer, dpi=300) draws one layer as an image inside SVG and PDF output while
axes and text stay vector, after ggrastr
by Viktor Petukhov, Teun van den Brand and Evan Biederstedt, and
scattermore by Tomas Sieger and
Mirek Kratochvil. A scatter plot of a million points then saves as a small PDF that
still has sharp, selectable text.
plot = ggplot(cells, aes("umap_1", "umap_2")) + rasterize(geom_point(size=0.5), dpi=300)
ggsave(plot, "umap.pdf")