R-Programmierung: Was ist %>% ? dplyr vs. Base R

Was bedeutet die sonderbar anmutende Zeichenkombination %>% , die man seit ein paar Jahren häufig in R-Skripten findet? Woher kommt sie und wie können wir sie nutzen, um eleganteren und besser lesbaren R-Code zu schreiben?

R und moderne Kunst: René Magritte

R inspiriert uns mit %>% , wenigstens einen kurzen Abstecher in die moderne Kunst zu unternehmen. Eines der bekanntesten Bilder des belgischen Surrealisten René Magritte (1898 – 1967) heißt „Der Verrat der Bilder“:

René Magritte: La Trahison des images
René Magritte: Der Verrat der Bilder (1929)

Das Bild eines Gegenstandes ist nicht mit dem Gegenstand selbst zu verwechseln; einen gemalten Apfel kann ich nicht essen, und diese Pfeife („Das ist keine Pfeife“) nicht rauchen.

magrittr und %>%

Stefan Milton Bache und Hadley Wickham führten mit dem magrittr-Paket den „Pipe Operator“ %>% in die Programmiersprache R ein. Paket-Name und Logo verweisen deutlich auf den Künstler:

magrittr wird in dplyr eingebunden, sodass man in der täglichen Praxis in der Regel nicht direkt mit magrittr zu tun hat, sondern dplyr oder gleich das Metapaket tidyverse lädt mit library(dplyr) bzw. library(tidyverse) und dann den Pipe Operator nutzen kann.

Kleine Modifikation in der Bildunterschrift: Beim Paket heißt es „un“, also mit männlichem Artikel. Eine passendere deutsche Übersetzung in diesem Zusammenhang dürfte „Rohr“ sein (engl. pipe); die Idee erinnert an die alte Rohrpost:

  • Nimm einen Datensatz (oder ein Daten-Objekt)
  • Gib ihn mittels Rohrpost %>% an die nächste Zeile weiter
  • Verkette so viele Funktionen, wie Du möchtest

Vorteil: Besser lesbarer Code; konkret: Man kann die für Base R manchmal typischen geschachtelten Klammerausdrücke auflösen, und man muss Zwischenergebnisse nicht in Objekten speichern.

Mit %>% geschachtelte Klammerausdrücke umgehen

Hier ein typischer Base-R-Code:

Typisch für Base R-Code: Geschachtelte Klammerausdrücke

Der Datensatz albums (Datenquelle: tsort.info) enthält Daten über die Top 3000 Musikalben (ermittelt nach einem Punktesystem auf Basis von Chartplatzierungen). Wende ich die summary-Funktion auf die character-Variable albums$name (Albumtitel) an, erhalte ich eine recht wenig aussagekräftige Auswertung. Wandle ich die Variable hingegen in einen factor um, erhalte ich eine Häufigkeitsauszählung. Der Übersichtlichkeit halber zeige ich mit head(n=3) nur die ersten drei Ergebnisse. Vier Alben im Datensatz heißen „Unplugged“, ebenfalls vier „Up“, drei „Believe“.

Ungünstig: Der geschachtelte Klammerausdruck muss von rechts nach links gelesen werden; zudem sind die Funktion „head“ und der Parameter „n=3“ weit auseinander gerissen.

Mit dplyr’s Piping erreiche ich das gleiche Ergebnis – allerdings auf deutlich eleganterem Weg:

dplyr’s Piping %>% kann geschachtelte Klammerausdrücke ersetzen

Hier wird intuitiv von oben nach unten gelesen. In die leeren Klammern wird jeweils das Objekt aus der vorigen Zeile eingesetzt. Ich gehe von der Variable albums$name aus, wandle sie in einen Faktor um, wende darauf die summary-Funktion an und lasse R per head(n = 3) die ersten drei Ergebnisse ausgeben.

Mit %>% Zwischenergebnisse nicht mehr in Objekten speichern

Im zweiten Beispiel zunächst wieder ein typischer Base R-Code:

Base R_Intermediate-Objects
Typischer Base R-Code: Zwischenergebnisse werden in Objekten gespeichert

Hier möchte ich Beatles-Alben heraussuchen, deren Titel möglichst weit hinten im Alphabet stehen. Zunächst wähle ich die ersten drei Variablen des Datensatz aus, dann filtere ich nach den Beatles, dann sortiere ich absteigend nach Albumtiteln und lasse die ersten sechs Ergebnisse in der Konsole ausgeben. Insgesamt neun Mal tippe ich dazu den Objektnamen test, den ich nicht weiter benötige und am Ende lösche. (Es ginge kürzer, z. B. kann man die ersten beiden Zeilen zusammenfassen und gleichzeitig Zeilen und Spalten auswählen. Ich wollte jeden Schritt einzeln demonstrieren.)

Die dplyr-Alternative mit dem Pipe Operator %>% sieht so aus:

Dank dplyr’s Piping %>% muss man Zwischenergebnisse nicht in Objekten speichern

Hier komme ich ganz ohne temporäre Objekte aus. Ausgehend vom Datensatz albums, der nicht verändert wird (es gibt keine Zuweisung, ich will lediglich eine Ausgabe in der Konsole erreichen), werden mit select() Variablen ausgewählt, mit filter() die Beatles-Alben herausgesucht, mit arrange(desc()) absteigend sortiert, und mit head() die ersten sechs Zeilen ausgegeben. Gleiches Ergebnis mit kompakterem Code, ohne die Arbeitsumgebung (Environment) mit zusätzlichen Objekten zu füllen.

Zudem erspart mir dplyr das Hantieren mit den eckigen Klammern für die Indizierung und das Nachdenken über die richtige Kommasetzung [Zeile, Spalte].

tidyverse vs. Base R

Der Pipe Operator %>% und generell die Pakete des tidyverse (zu denen dplyr zählt, daneben auch ggplot2, forcats, readr, stringr und andere) haben sich weitgehend in der R-Gemeinschaft durchgesetzt und erleichtern das Arbeiten mit R enorm. Es gibt allerdings auch Kritiker: Zum Beispiel …

Viel Freude mit elegantem R-Code – happy Piping %>% !

Freue mich über Kommentare!