Blog

R Tutorial: cowplot

Dieses Tutorial basiert auf Arbeiten von BMARSYS-Studierenden im begleitenden Seminar zum DS2-Modul und wurde redaktionell überarbeitet.

Das R-Paket cowplot (von Claus O. Wilke) ist ein Add-on zu ggplot2 und muss separat installiert werden — es ist nicht Teil des tidyverse. cowplot löst ein häufiges Problem in der wissenschaftlichen Arbeit: Wie kombiniert man mehrere ggplot2-Grafiken zu einer zusammengesetzten Abbildung mit Panel-Beschriftungen (A, B, C, …), wie sie für Publikationen üblich sind? Darüber hinaus bietet das Paket minimalistische Themes und Werkzeuge zum Annotieren von Grafiken.

Installation

# install.packages("cowplot")
library(ggplot2)
library(cowplot)

Plots anordnen mit plot_grid()

Die zentrale Funktion von cowplot ist plot_grid(). Sie nimmt beliebig viele ggplot-Objekte entgegen und ordnet sie in einem Raster an. Mit dem Argument labels lassen sich die einzelnen Panels beschriften — in wissenschaftlichen Abbildungen üblicherweise mit Großbuchstaben:

p1 <- ggplot(mtcars, aes(disp, mpg)) + geom_point()
p2 <- ggplot(mtcars, aes(qsec, mpg)) + geom_point()

plot_grid(p1, p2, labels = c("A", "B"))

Statt die Labels einzeln anzugeben, kann man mit labels = "AUTO" automatisch Großbuchstaben vergeben (oder labels = "auto" für Kleinbuchstaben). Die Schriftgröße der Labels lässt sich mit label_size anpassen.

Ausrichtung und Layout

Standardmäßig werden Plots nebeneinander angeordnet. Mit ncol und nrow lässt sich das Layout steuern. Das Argument align sorgt dafür, dass die Plotbereiche exakt ausgerichtet werden — ohne Ausrichtung können Plots unterschiedlich breite Achsenbeschriftungen haben und dadurch visuell nicht bündig erscheinen:

# Horizontale Ausrichtung der Achsen
plot_grid(p1, p2, labels = "AUTO", align = "h")

# Untereinander anordnen
plot_grid(p1, p2, labels = "AUTO", ncol = 1)

Bei der Kombination von facettierten und nicht-facettierten Plots reicht align allein nicht aus. Hier muss zusätzlich mit axis angegeben werden, an welchen Achsen ausgerichtet werden soll ("bt" = bottom und top, "lr" = left und right, "tblr" = alle):

library(dplyr)

p3 <- ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme_minimal_grid(14) +
  panel_border(color = "black")

p4 <- ggplot(mtcars, aes(factor(vs))) +
  geom_bar() +
  facet_wrap(~am) +
  theme_minimal_hgrid(12) +
  panel_border(color = "black")

# Ausrichtung an oberer und unterer Achse
plot_grid(p3, p4, align = "h", axis = "bt")

Relative Größen und gemeinsame Titel

Mit rel_widths und rel_heights lässt sich das Größenverhältnis der Panels steuern. Im folgenden Beispiel ist Panel B doppelt so breit wie Panel A. Zusätzlich wird mit ggdraw() und draw_label() ein gemeinsamer Titel über die gesamte Abbildung gesetzt:

plot_row <- plot_grid(p1, p2, labels = "AUTO", rel_widths = c(1, 2))

title <- ggdraw() +
  draw_label("Motor Trend Auto-Straßentests", fontface = "bold", x = 0, hjust = 0) +
  theme(plot.margin = margin(0, 0, 0, 32))

plot_grid(title, plot_row, ncol = 1, rel_heights = c(0.1, 1))

Verschachtelte Layouts

Für komplexere Anordnungen — z.B. ein großes Panel oben und zwei kleinere unten — kann ein plot_grid() in einen anderen verschachtelt werden. Dabei werden die Labels auf die jeweiligen Ebenen verteilt:

plot_bottom <- plot_grid(p1, p2, labels = c("B", "C"), label_size = 12)
plot_top <- ggplot(mtcars, aes(x = qsec, y = disp)) +
  geom_point() +
  facet_wrap(~gear)

plot_grid(plot_top, plot_bottom, labels = c("A", ""), label_size = 12, ncol = 1)

Plots modifizieren und annotieren

Wasserzeichen und Beschriftungen

Die Funktion ggdraw() wandelt einen Plot in eine Zeichenumgebung um. Auf dieser Umgebung können mit den draw_*()-Funktionen Texte, Linien oder Bilder platziert werden — nützlich z.B. für Wasserzeichen bei Entwürfen. Die Positionsangaben sind relative Koordinaten von 0 bis 1:

p5 <- ggplot(mpg, aes(displ, cty)) +
  geom_point() +
  theme_minimal_grid(12)

ggdraw(p5) +
  draw_label("Draft", color = "orange", size = 100, angle = 45)

Die Reihenfolge der Layer bestimmt, was vorne liegt. Um die Annotation hinter den Plot zu legen, wird zuerst ggdraw() ohne Argument aufgerufen, dann die Annotation, und zuletzt der Plot mit draw_plot():

ggdraw() +
  draw_label("Draft", color = "orange", size = 100, angle = 45) +
  draw_plot(p5)

Inset-Plots

Mit draw_plot() lassen sich auch kleinere Plots als Insets in größere Grafiken einfügen — z.B. eine Übersichtskarte in einer Detailkarte oder ein Balkendiagramm als Zusammenfassung. Die vier Zahlen nach dem Plot-Objekt geben Position (x, y) sowie Breite und Höhe an (jeweils als Anteil der Gesamtfläche):

inset <- ggplot(mpg, aes(drv)) +
  geom_bar(fill = "skyblue2", alpha = 0.7) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
  theme_minimal_hgrid(11)

ggdraw(p5 + theme_half_open(12)) +
  draw_plot(inset, 0.45, 0.45, 0.5, 0.5) +
  draw_plot_label(c("A", "B"), c(0, 0.45), c(1, 0.95), size = 12)

Bilder einfügen

Mit draw_image() können auch externe Bilder (PNG, JPEG) in eine Grafik eingefügt werden. Dies erfordert das magick-Paket. Typische Anwendung: Logos oder Fotos in Abbildungen:

# Erfordert das magick-Paket
logo_file <- system.file("extdata", "logo.png", package = "cowplot")

ggdraw(p5) +
  draw_label("Draft", color = "orange", size = 100, angle = 45) +
  draw_image(logo_file, x = 0.95, y = 0.95, hjust = 1, vjust = 1, width = 0.15)

Themes

cowplot bietet mehrere minimalistische Themes, die auf wissenschaftliche Abbildungen zugeschnitten sind. Sie unterscheiden sich hauptsächlich darin, welche Achsenlinien und Gitterlinien angezeigt werden. Die Zahl (z.B. 12) gibt die Basis-Schriftgröße in Punkten an.

Halboffener Rahmen (Standard)

Das Standard-Theme theme_cowplot() (identisch mit theme_half_open()) zeigt nur die linke und untere Achse — ein klassisches Design für Streudiagramme und Liniendiagramme:

ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) +
  geom_point() +
  theme_cowplot(12) # identisch zu theme_half_open()

Minimalistische Themes mit Gitterlinien

Je nach Diagrammtyp sind unterschiedliche Gitterlinien sinnvoll. theme_minimal_grid() zeigt ein vollständiges Gitter, theme_minimal_hgrid() nur horizontale und theme_minimal_vgrid() nur vertikale Linien:

# Vollständiges Gitter
ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) +
  geom_point() +
  theme_minimal_grid(12)

# Nur horizontales Gitter (gut für Balken-/Dichtediagramme)
ggplot(iris, aes(Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.5) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
  theme_minimal_hgrid(12)

# Nur vertikales Gitter
ggplot(iris, aes(Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.5) +
  theme_minimal_vgrid(12)

Tipp

Faustregel für Gitterlinien: Horizontales Gitter (_hgrid) eignet sich für Diagramme, bei denen man y-Werte abliest (Balken-, Dichtediagramme). Vertikales Gitter (_vgrid) ist selten nötig, kann aber bei horizontalen Balkendiagrammen helfen.

Themes für Karten und leere Plots

  • theme_map() — entfernt Achsen, behält Titel und Legende (ideal für Karten)
  • theme_nothing() — entfernt alles außer dem Plotbereich (nützlich für Hintergrundbilder)

Panel-Rahmen für Facetten

Bei facettierten Plots (facet_wrap() / facet_grid()) sind die einzelnen Panels oft schwer voneinander zu unterscheiden. panel_border() fügt einen sichtbaren Rahmen um jedes Panel hinzu. Wichtig: panel_border() muss immer nach dem Theme aufgerufen werden, da es sonst überschrieben wird:

ggplot(mtcars, aes(disp, mpg)) +
  facet_wrap(~factor(cyl)) +
  geom_point() +
  theme_half_open(12) +
  panel_border() # immer nach dem Theme aufrufen!

Gemeinsame Legenden

Wenn mehrere Plots die gleiche Farbkodierung verwenden (z.B. verschiedene Ansichten desselben Datensatzes), ist eine gemeinsame Legende übersichtlicher als separate Legenden pro Plot. Der Workflow: Legende aus einem Plot extrahieren mit get_legend(), die Plots ohne Legende anordnen, und die extrahierte Legende separat platzieren.

library(forcats)

dsamp <- diamonds[sample(nrow(diamonds), 1000), ]

plot_diamonds <- function(xaes) {
  xaes <- enquo(xaes)
  ggplot(dsamp, aes(!!xaes, price, color = clarity)) +
    geom_point() +
    theme_half_open(12) +
    theme(plot.margin = margin(6, 0, 6, 0))
}

p1 <- plot_diamonds(carat)
p2 <- plot_diamonds(depth) + ylab(NULL)
p3 <- plot_diamonds(color) + ylab(NULL)

# Plots ohne Legende anordnen
prow <- plot_grid(
  p1 + theme(legend.position = "none"),
  p2 + theme(legend.position = "none"),
  p3 + theme(legend.position = "none"),
  align = "vh", labels = c("A", "B", "C"), nrow = 1
)

Legende rechts platzieren

Die extrahierte Legende wird als eigenes Element neben die Plotreihe gesetzt. Mit rel_widths wird sichergestellt, dass die Legende nicht zu viel Platz einnimmt:

legend <- get_legend(
  p1 + theme(legend.box.margin = margin(0, 0, 0, 12))
)
plot_grid(prow, legend, rel_widths = c(3, 0.4))

Legende unten platzieren

Alternativ kann die Legende unter den Plots platziert werden. Dafür wird die Legende als horizontale Reihe formatiert (nrow = 1 in guide_legend()) und mit rel_heights die Höhe angepasst:

legend_b <- get_legend(
  p1 +
    guides(color = guide_legend(nrow = 1)) +
    theme(legend.position = "bottom")
)
plot_grid(prow, legend_b, ncol = 1, rel_heights = c(1, 0.1))

Weiterführende Ressourcen