Blog

Python für R-Programmierer

Python hat in den letzten Jahren, insbesondere durch die intensive Nutzung in den Bereichen Data Science, Machine Learning und Deep Learning, einen massiven Aufschwung erfahren. Auch wenn R in ähnlicher Form am Wachsen ist, kann es für professionelle AnwenderInnen immer von Vorteil sein, über mehrere Sprachen Bescheid zu wissen. Im Folgenden soll für R-ProgrammiererInnen ein Einstieg in konzeptionelle Gemeinsamkeiten und Unterschiede der beiden Sprachen gegeben werden. Diese sind umgekehrt natürlich auch für Python-Erfahrene interessant, die sich mit R beschäftigen wollen.

Gemeinsamkeiten

Python und R sind interpretierte Sprachen

Python ist eine recht klassische Programmiersprache, aber ursprünglich u.a. als Unix-Hacking-Sprache entstanden. Sie wird interpretiert. Man schreibt also keinen Code, der auskompiliert wird (wie etwa bei C, Java, Scala), sondern ein Skript, welches Punkt für Punkt abgearbeitet wird, wie in MatLab, oder eben R. Funktionen und Klassen können aus anderen Skripten oder Packages geladen werden, die sich entweder in einem spezifizierten Ordner befinden oder über das Paketsystem installiert werden. Des weiteren ist es bei einer interpretierten Sprache nicht nötig, statische Typisierung zu verwenden, also den Typ eines Objekts bei der Initialisierung explizit anzugeben (Integer, Double, String, Array usw). In diesem Punkt unterscheidet sich Python also nicht von R. Dies ist auch ein wesentlicher Punkt, warum Python überhaupt für Datenanalyse und statistische Modelle verwendet werden kann: Man schreibt eine Zeile Code, schickt diese an die Python-Konsole, wie man das in R mit der R-Konsole tut. Dadurch wird es möglich, interaktiv mit der Sprache zu arbeiten. Man kann zwischen jedem Schritt Daten, Grafiken und Ergebnisse anschauen, genau wie in R. Das ermöglicht einfache explorative Datenanalyse genauso wie die bequeme Modellentwicklung. In kompilierten Sprachen ist das nicht ohne weiteres möglich! Hier muss man Code schreiben, auskompilieren, und erst danach kann man sich Ergebnisse anschauen. Der Zwischenschritt des fehlerfreien Kompilierens verkompliziert Datenanalyse ungemein, jedenfalls wenn die Kompilierung nicht zur Laufzeit geschieht.

Python und R verfügen über ein umfangreiches Universum an Open Source-Paketen und eine aktive Community

Python und R verfügen über Systeme zur einfachen Erstellung, Weitergabe und zum Management von Paketen. Das und der generelle Erfolg beider Sprachen führen zu umfangreichen Paketrepositories (CRAN bzw. PyPI), welche ständig wachsen und erweitert werden. Beide Sprachen werden intensiv genutzt und befinden sich im Wachstum, was einerseits zu eben diesem großen Umfang an Paketen, aber auch zu exzellentem Community-Support führt. Python ist bei StackOverflow bezüglich der Anzahl an Fragen/Antworten mittlerweile sogar führend.

Python und R haben ein paar Jahre auf dem Buckel

Die Entwicklung von Python wurde 1989 begonnen (Fun Fact: als Hobbyprojekt über die Weihnachtsfeiertage), die von R 1992. Das erklärt, warum die beiden Sprachen bereits über einen so großen Blumenstrauß an Paketen und eine so große Community verfügen. Und das erklärt auch, warum sie beide ein paar Konzepte mitbringen, die aus den Anfangszeiten stammen und heute vielleicht etwas unverständlich erscheinen. Beide Sprachen werden jedoch auch aktiv weiterentwickelt.

Python und R sind langsam

Python und R sind interpretiert, und sie sind nicht ganz neu. Diese Kombination führt zu massiven Geschwindigkeitsnachteilen zum Beispiel im Vergleich zu einer kompilierten Sprache wie C oder einer modernen, zur Laufzeit kompilierten Sprache ohne statische Typisierung wie Julia, welche die Vorteile von interpretierten und kompilierten Sprachen geschickt vereinigt. Mittels Bibliotheken wie Cython oder Rcpp lässt sich dieser Nachteil bei beiden Sprachen bis zu einem gewissen Grad ausgleichen, indem Teile des Codes in kompilierte Sprachen ausgelagert werden.

Unterschiede

R ist eine Sprache zur Datenanalyse, mit der man programmieren kann - Python ist eine Programmiersprache, mit der man Datenanalyse machen kann

Python ist Anfang der Neunziger Jahre als neue Skriptsprache entstanden, ohne Datenanalyse im Hinterkopf zu haben. Zusätzliche Funktionalitäten, die Python zu der Data Science-Sprache machen, die sie heute ist, wurden erst sehr viel später über Pakete hinzugefügt. Erst Mitte der Neunziger Jahre wurden erstmals Pakete mit einem Fokus auf numerische Programmierung angeboten. Das heute so gängige NumPy ist in der ersten Version 2006 veröffentlicht worden, pandas 2008, scikit-learn in der ersten Version erst 2010. Für R-User führt das zu der ungewohnten Situation, dass man in Python selbst für einfache Matrix-Berechnungen oder lineare Modelle einiges an Paketen laden muss, und dass es für solche Berechnungen keine Operatoren gibt. Für eine einfache Matrixmultiplikation der Matrizen A und B schreibt man klassischerweise etwa

import numpy as np
result = np.dot(A, B)

statt "einfach" (wie in R)

result <- A %*% B

Gerade am Anfang kann die ständige Verwendung etwa des Kürzels "np" für numpy etwas anstrengend sein. Für sämtliche Funktionen zur Datenanalyse muss man diese Umwege über Pakete gehen, welche bei R oft schon in Base-R vorhanden sind. Umgekehrt gibt es viele Umständlichkeiten und Ungewöhnlichkeiten, die in R bei klassischen Programmieraufgaben auftreten, aber in Python nicht. Zudem hat die Tatsache, dass Python eine "general purpose"-Sprache ist, viele Vorteile, weil sie noch leichter in andere Programmteile integrierbar ist und viele klassische Programmierkonzepte in Python grundsätzlicher implementiert sind als in R. Ein Beispiel hierfür ist OOP:

R ist (u.a.) funktional, und ein wenig objektorientiert - Python ist (u.a.) objektorientiert, und ein wenig funktional

R ist funktional. Das merkt man als Anwender etwa an der viel höheren Geschwindigkeit aller Arten von apply() im Vergleich zu einfachen For-Schleifen. Entwickler, welche sich weigern Funktionen zu verwenden, dürften relativ bald Probleme bekommen, effizienten Code zu schreiben. In Python wurden einige Konzepte aus der funktionalen Programmierung schon relativ früh implementiert. So gibt es etwa die klassischen funktionalen Programmiertools lambda, map, filter, reduce, wie sie auch in R existieren. Zudem ist Python berühmt für seine List- und Dict-Comprehensions. Diese ähneln in ihrer Funktionsweise einem apply auf einem R-Vector.

persons = ['Mira', 'Marina', 'Matthaeus', 'Marcus', 'Carl']
print("Wer arbeitet bei INWT?")
print([l + ' arbeitet bei INWT' for l in persons if l[0] == "M"])

Das ist im Wesentlichen eine syntaktische, sehr elegante Vereinfachung von map und filter. In R würde man hier mit Vektorisierung und Subsetting arbeiten:

persons <- c('Mira', 'Marina', 'Matthaeus', 'Marcus', 'Carl')
cat("Wer arbeitet bei INWT?\n")
for (p in persons[grepl('^M', persons)]) cat(p, 'arbeiten bei INWT\n')

Mit Comprehensions lassen sich viele Dinge stark vereinfachen und verkürzen, sie gelten als "pythonischer" Stil, sind aber nicht wesentlich schneller als eine klassische For-Schleife. Auch beim Arbeiten mit Data.Frames aus der pandas-Welt lässt sich nicht im Allgemeinen sagen, dass apply schneller ist als die Iteration über die Achsen! Das ist ein großer Unterschied zu R, wo die Iteration über die Achsen "verboten" ist. Auch andere Konzepte aus der funktionalen Programmierung, wie es sie in R gibt, sind in Python verfügbar, etwa Closures. Python ist jedoch nicht vollständig funktional. Jede Menge an Tools für Funktionen, wie es sie in rein funktionalen Sprachen, etwa Haskell gibt, fehlen. Zum Beispiel gibt es kein klassisches Pattern-Matching (in R aber auch nicht), zudem keine große Zahl an Tools für Funktionen. Währenddessen ist R im Kern funktional, was man an Konstruktionen wie

`+`(1, 2)

merkt, eine Schreibweise, wie sie in Python unmöglich wäre. Python hat hier einen stärkeren Fokus auf klassische objektorientierte Paradigmen, die in R eher ungewöhnlich nachgebildet werden. Hierbei hat R nach aktuellem Stand vier Konzepte: S3, S4, Reference Classes und R6. Die beiden ersten implementieren ein gänzlich anderes Konzept von Klassen, die beiden letzteren sind deutlich näher an den in Python (und anderen Sprachen) gängigen Konzepten.

Hier eine kleine Klasse und ihre Anwendung:

class INWT():
    def __init__(self):
        self.some_persons = ['Mira', 'Marina', 'Matthaeus', 'Marcus']
        self.__boss = ['Amit']

    def print_all(self):
        print(self.some_persons + self.__chef)

inwt = INWT()
inwt.some_persons.append('Julia')
inwt.print_all()
print(inwt)

Hier wird eine Klasse INWT definiert, als Objekt Listen mit einigen Personen hinzugefügt. Außerdem gibt es eine Print-Methode, welche alle Personen ausgibt. Sämtliche Objekte sind von außen über einen Punkt als Trenner erreichbar und änderbar ('Julia' wird ohne weiteres hinzugefügt), als Konvention ist nur inwt.__boss privat (wird durch den Doppelstrich gekennzeichnet). Setter und Getter braucht es nicht (anders als z.B in Java). Alles, was zur Klasse gehört, wird innerhalb der Klassendefinition niedergeschrieben. Print gibt hingegen nur eine Information über das Objekt und seinen Pointer wieder.

Das ganze in R als S3-Klasse:

INWT <- function(employees = c('Mira', 'Marina', 'Matthaeus', 'Marcus'),
                 boss = 'Amit') {
  res <- list(employees = employees, boss = boss)
  class(res) <- 'INWT'
  res
}

append <- function(inwt, with) {
  INWT(c(inwt$employees, with), inwt$boss)
}

print.INWT <- function(x, ...) cat(x$employees, x$boss, "\n")

inwt <- INWT()
append(inwt, 'Julia')

Hier wird bei der Definition und Initialisierung der Klasse nicht alles mitgegeben. Die "Klasse" kann nachträglich hinzugefügt werden, zudem können generische Methoden nachträglich angefügt werden. Ebenfalls können Objekte verändert werden. Dieses (und das S4-) System ist fundamental anders als in Python, und stammt ebenfalls eher aus der funktionalen Sichtweise. Gängige Methoden funktionieren für jede Klasse anders und können individuell definiert werden. Das ist bei Python nicht der Fall, wo jede Methode nur innerhalb einer initialisierten Klasse erreichbar ist. Diese Unterschiede sind bei Reference Classes nicht so stark, auch dort werden alle Teile innerhalb der Klasse definiert, jedoch auch typisiert. Insgesamt lässt sich sagen, dass Objektorientiertheit viel tiefer in die Python-Philosophie eingebunden ist und man als Entwickler nicht um diese Konzepte herumkommt. Doch auch die S3- und S4-Systeme sind in R sehr beliebt und werden aktiv propagiert.

Data Science ist in Python stärker konsolidiert und in R flexibler

R bietet für fast jede Aufgabe mehrere Pakete mit unterschiedlichen Philosophien, etwa für Daten-Handling base, dplyr, data.table etc. Gleichzeitig gibt es eine enorme Anzahl an Modellen, welche in R-Paketen verfügbar sind, die jedoch eine durchaus unterschiedliche Syntax aufweisen können. Das ist in Python nicht so stark der Fall. Für Data-Handling wird de facto nur pandas verwendet, für Modellierung hat sich in sehr starkem Ausmaß scikit-learn und seine Syntax etabliert. Neue Modelle bieten hierfür sehr oft einen Wrapper an. Für alle Modelle innerhalb von scikit-learn ist die Syntax für Definition, Modellierung, Prediction gleich!

Die Syntax für Definition, Fitting und Prediction eines "Modell"s mit Daten "X" und "y" (als numpy-Arrays) und neuen Daten "X_hat" sieht folgendermaßen aus:

modell = Modell(Hyperparameter)
modell.fit(X, y)
y_hat = modell.predict(X_hat)

Das ist für ALLE angebotenen Modelle gleich! Gleichzeitig ist innerhalb von scikit-learn ein großes Angebot an zusätzlichen Methoden zum Data-Handling (z.B. Missing Values, Skalierung), für das Modell-Fitting (z.B. Kreuzvalidierungsmethoden, Grid Search) sowie für Prediction und Deployment (z.B. Pipelines) vorhanden. Das ist alles ein Paket und beinhaltet sehr viele Modelle. Dies ist in vergleichbarer Form nicht in R vorhanden bzw. höchstens in Paketen wie caret, welche für das Deployment aber einen Overhead darstellen. Dafür kann man in R aus einer größeren Zahl an unterschiedlichen Herangehensweisen wählen. Dennoch ist die starke Konsolidierung in Python recht bequem, es konnte sich innerhalb recht kurzer Zeit eine Syntax durchsetzen. Das ist allerdings bspw. im Deep Learning-Bereich (noch) nicht in dem Ausmaß der Fall, wo unterschiedliche Ansätze und Frameworks (keras, tensorflow, PyTorch, etc) miteinander konkurrieren.

Fazit

Python und R sind als Data-Science-Sprachen mittlerweile Standard. Beiden ist gemeinsam, dass sie interpretierte Open-Source-Sprachen mit starker, aktiver Community sind, ein florierendes Package-System inklusive performanter Data-Handling und Modellierungs-Pakete aufweisen können und aktiv weiterentwickelt werden. Die Unterschiede zwischen den beiden Sprachen liegen in ihrer unterschiedlichen Geschichte begründet, stellen für Data Scientists, die über den programmiererischen Tellerrand blicken möchten, aber keine große Hürde dar.