Open Source im professionellen Einsatz
Linux-Magazin 03/2015
1598

R-Schnittstelle

Die Beispielanwendung nutzt das Python-Paket Rpy2 [6] als Schnittstelle zu R. Rpy2 übergibt Daten und Funktionsaufrufe über Rs C-Schnittstelle an einen eingebetteten R-Prozess und nimmt die Ergebnisse wieder entgegen. Mit dem Modul »robjects« stellt Rpy2 darüber hinaus eine objektbasierte Schnittstelle zu R zur Verfügung, die für jeden Objekttyp aus R eine eigene Python-Klasse anbietet.

Die Funktionen aus R arbeiten auf den Objekten der Python-Klassen wie mit nativen R-Objekten: Sie überführen zum Beispiel den Python-Ausdruck »robjects.r. length(robjects.FloatVector([1.0, 2.0])« für den eingebetteten R-Prozess in den Ausdruck »length(c(1.0, 2.0))« und werten ihn dort aus. Rpy2 steht unter der GPLv2 und liegt derzeit in Version 2.5.4 vor. Listing 5 installiert die aktuelle Version von Rpy2 unter Ubuntu 12.04.

Listing 5

Installation von Rpy2

01 sudo apt-get install python-dev
02 sudo easy_install rpy2

Zum Report

Die Python-Reports der Beispielanwendung nutzen allesamt die Klasse »reporter« aus Listing 6 (Zeilen 5 bis 21), um Daten aus der Mongo-DB-Datenbank zu lesen und sie mit Hilfe von R in ein Python-Dataframe-Objekt zu überführen. Ihre Konstruktor-Funktion übernimmt in ihrer Parameterliste (Zeile 6) den Namen einer Mongo DB und einer Datensammlung, eine Liste mit Attributen sowie einen optionalen Selektor.

Listing 6

www/rreporter.py

01 from pymongo import MongoClient
02 import rpy2.robjects as ro
03 import json
04
05 class reporter:
06   def __init__(self, name, collec, keys, fexpr=None):
07     count = 0
08     doc = None
09     for doc in MongoClient()[name][collec].find(fexpr):
10       try:
11         if count == 0:
12           ro.r('df = %s' %ro.Dataframe({key: float(doc[key]) for key in keys}).r_repr())
13           count += 1
14         else:
15           ro.r('df[nrow(df)+1,] = %s' %ro.FloatVector([float(doc[key]) for key in keys]).r_repr())
16       except:
17         pass
18     self.df = ro.r('df')
19
20   def respond(self, obj):
21     print "Content-Type: application/json;charset=utf-8\n\r\n\r%s" %json.dumps(obj)

Die Zeilen 9 bis 18 iterieren über alle Einträge eines Datenbank-Cursors, den der Aufruf der Methode »find()« in Zeile 9 aus der abgefragten Datensammlung generiert. Beim ersten Schleifendurchlauf erzeugt der Konstruktor »Dataframe« (Zeile 12) aus dem übergebenen Dictionary ein Objekt. Der Generatorausdruck erzeugt das Dictionary in den geschweiften Klammern aus der Liste der übergebenen Attribute, dem Aufrufparameter »keys« und den gleichnamigen Attributwerten des aktuellen Cursoreintrags aus der Variablen »doc« . Nach der Instanzierung wandelt die Methode »r_repr()« das Objekt in eine Zeichenkette um und setzt es für »%s« in »df=%s« ein.

Der eingebettete R-Prozess wertet das Objekt mit Hilfe der Methode »r()« als Dataframe-Objekt aus. Zuletzt speichert er es in der Variablen »df« . Auf ähnliche Weise fügt der zweite R-seitige Schleifendurchlauf in Zeile 15 »df« die ausgelesenen Cursoreinträge hinzu. Diesmal verpackt die Methode »r()« die Werte in ein Objekt vom Typ »FloatVector« und fügt sie in den R-Ausdruck »df[nrow(df)+1,] = %s« ein.

Zeile 18 speichert den so erzeugten Dataframe wieder als Python-Objekt in der Komponente »self.df« . Die Python-Skripte nutzen »respond()« ab Zeile 20, um die Ergebnis-URL im Stil einer CGI-Antwort an den Webserver zu übertragen. Zeile 21 hängt das übergebene Python-Objekt dazu im Json-Format an den Header.

Listing 7 erzeugt den ersten Report. Dieser stellt die relative Häufigkeit der Bahntypen Ellipse, Parabel und Hyperbel als Tortendiagramm dar (Abbildung 1). Per Shebang »#!« wählt das Skript zuerst Python als ausführende Instanz. Über die Methode »importr()« holt es R-Module nach, dann importiert es die Klasse »reporter« aus Listing 6 sowie das Modul »robjects« .

Listing 7

www/bahntypen.py

01 #!/usr/bin/env python
02 from rpy2.robjects.packages import importr
03 from rreport import reporter
04 import rpy2.robjects as ro
05
06 devs = importr('grDevices')
07
08 main = "Verteilung der Bahntypen"
09 path = "tmp/bahntypen.png"
10
11 rep = reporter("galaxy", "comets", ["ecc"])
12 num = ro.r.nrow(rep.df)[0]
13 vals = ro.r.table(ro.r.cut(rep.df.rx2(1), [0, 0.9999, 1.0001, 2]))
14
15 label = lambda label, i: "%s %s %%" %(label, round((vals[i]*100)/num))
16 labels = [label("Ellipse", 0), label("Parabel", 1), label("Hyperbel", 2)]
17 col = ro.r.rainbow(len(labels))
18
19 devs.png(file=path, width=512, height=512)
20 ro.r.pie(vals, labels=labels, col=col, main=main)
21 devs.dev_off()
22
23 rep.respond({"fileref":path})

Zeile 6 nutzt »importr()« , um den Exportmechanismus von Grafiken in den laufenden R-Prozess einzubinden. Die Variable »main« speichert in Zeile 8 den Titel des Reports, in »path« (Zeile 9) legt das Python-Skript den Pfad der zu erstellenden Grafik relativ zum eigenen Speicherort ab. Zeile 11 erzeugt als Instanz ein Objekt der Klasse »reporter« und speichert es in der Variablen »rep« . Der Konstruktor-Aufruf übernimmt den Namen der Mongo DB (»galaxy« ) sowie der Datensammlung (»comets« ). Die Liste im dritten Aufrufparameter fordert die Werte der Exzentrizitäten an.

Zeile 12 zählt mittels »nrow« die Datensätze im Dataframe-Objekt »rep.df« und speichert das Ergebnis in der Variablen »num« . Die Methode »rx2()« kopiert zunächst die Werte aus der ersten Spalte von »rep.df« in ein Objekt vom Typ »FloatVector« . Dann bildet »cut()« die Komponenten des Vektors auf einen der drei R-Faktoren »[0-0.9999)« , »[0.999-1.0001)« oder »[1.0001-2)« ab (es sind halboffene Intervalle) und erstellt mit »table()« aus dem Ergebnisvektor eine Kontingenztafel. Die Variable »vals« speichert Letztere ab.

Die Lambda-Funktion aus Zeile 15 formatiert die Beschriftung der Tortenstücke und übernimmt dafür eine Bezeichnung in der Variablen »label« und einen Index in »i« . Aus »i« , »vals« und »num« berechnet die Funktion die relative Häufigkeit des Bahntyps in Prozent.

Indem sie die Lambda-Funktion wiederholt aufruft, erzeugt und speichert die Zeile 16 Beschriftungen im Feld »labels« , Zeile 17 erzeugt einen Farbindex für die Tortenstücke aus drei Farbstufen. Zeile 19 öffnet mittels »png()« die Datei aus der Variablen »path« , um darin die Abbildungen zu speichern. Zeile 20 erstellt mit »pie()« das Tortendiagramm. Die Funktion übernimmt die Kontingenztafel »vals« mit den Grafikoptionen der Zeilen 16, 17 und 8. Zeile 21 beendet die Ausgabe in die Datei, bevor »rep.respond()« die relative URL der Grafik als HTTP-Antwort an den Browser schickt.

Der Report »keppler3.py« aus Listing 8 verifiziert hingegen die Gültigkeit des Dritten Keplerschen Gesetzes [7]. Demnach ist das Quadrat der Umlaufzeit T eines Himmelskörpers, der sich auf einer Ellipsenbahn um die Sonne bewegt, proportional zum Kubik seiner großen Halbachse: T2~a3. Wird also a3 gegen T2 aufgetragen, so sollte sich nach Kepler eine Gerade einstellen.

Listing 8

www/keppler3.py

01 #!/usr/bin/env python
02 from rpy2.robjects.packages import importr
03 from rreport import reporter
04 import rpy2.robjects as ro
05
06 devs = importr('grDevices')
07
08 main = "3. Keplersches Gesetz"
09 path = "tmp/keppler3.png"
10
11 rep = reporter("galaxy", "comets", ["semaj_axs", "period"], {"semaj_axs":{"$lt":100}})
12 x_lab = "Grosse Halbachse / [AE]"
13 x_vals = ro.FloatVector([x**3 for x in rep.df.rx2(1)])
14
15 y_lab = "Umlaufdauer / [Jahre]"
16 y_vals = ro.FloatVector([x**2 for x in rep.df.rx2(2)])
17
18 devs.png(file=path, width=512, height=512)
19 ro.r.plot(x_vals, y_vals, xlab=x_lab, ylab=y_lab, main=main)
20 devs.dev_off()
21
22 rep.respond({"fileref":path})

Listing 8 unterscheidet sich erst ab Zeile 11 von Listing 7, indem es die Werte der großen Halbachse und der Umlaufdauer in das Dataframe-Objekt lädt. Der letzte Parameter im Konstruktor-Aufruf von Zeile 11 enthält den Mongo-DB-Selektor »{"semaj_axs":{"$lt":100}}« , den die aus Listing 6 übernommene Klasse »reporter« an die Methode »find()« übergibt. Der Selektor wählt nur die Daten von Kometen mit einer großen Halbachse kleiner als 100 AE aus.

In Zeile 12 speichert die Variable »x_lab« die Beschriftung der x-Achse. Zeile 13 nimmt mit dem Generatorausdruck die Werte der großen Halbachse ins Kubik und übergibt das Ergebnis an den Konstruktor der Klasse »FloatVector« , der sie in der Variablen »x_vals« ablegt.

Die Zeilen 15 und 16 wiederholen das Prozedere für die Umlaufdauer und die y-Achse, erstere quadriert das Skript jedoch nur. Die Funktion »plot()« erstellt ein Punktdiagramm, das »x_vals« gegen »y_vals« aufträgt (Abbildung 5). Die Punkte fallen wie erwartet auf eine Gerade.

Abbildung 5: Die Kometen bestätigen das Dritte Keplersche Gesetz.

Listing 9 vergleicht die Verteilung der Exzentrizitäten für reguläre und wiederkehrende Kometen (»{"type":"RP}« ) mit der Normalverteilung mit Hilfe des Quantil-Quantil-Plots »qqnorm()« (Zeile 14). Der Aufruf von »qqline()« zeichnet nachträglich eine Modellgerade in die Grafik. Je besser die Quantile der gemessenen Verteilung mit jener der Normalverteilung übereinstimmt, desto näher liegen die Punkte auf der Winkel-halbierenden Modellgeraden (Abbildung 6). Die Verteilung der Exzentrizitäten ähnelt der Normalverteilung weitestgehend, weicht jedoch für größere Werte von &0x03B5; zu stark davon ab. Womöglich unterliegen diese Kometen stärkeren Streuprozessen.

Listing 9

www/eccdistr.py

01 #!/usr/bin/env python
02
03 from rreport import reporter
04 from rpy2.robjects.packages import importr
05 import rpy2.robjects as ro
06
07 devs = importr('grDevices')
08
09 path = "tmp/qqnorm.png"
10
11 rep = reporter("galaxy", "comets", ["ecc"], {"type":"RP"})
12
13 devs.png(file=path, width=512, height=512)
14 ro.r.qqnorm(rep.df.rx2(1))
15 ro.r.qqline(rep.df.rx2(1))
16 devs.dev_off()
17
18 rep.respond({"fileref":path})
Abbildung 6: Keine normalverteilte Exzentrizität.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 7 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Web-Beschleunigung

    Nach der Ajax-Revolution im Browser steht eine Modernisierung der Server-Seite an. Auch dort sollen asynchrone Requests zu schnelleren und besser skalierenden Webanwendungen führen. Der Jetty-Server hat dazu Continuations implementiert, Cometd bietet einen Standard.

  • Python

    Einsteigerfreundlich, schnell zu lernen und flexibel: Die Skriptsprache Python genießt trotz des vermeintlich gefährlichen Namens einen guten Ruf. Dank zahlreicher Module und einfacher Syntax reichen für Datei-Operationen, Ein- und Ausgaben und selbst fürs Monitoring eines Servers wenige Zeilen Code.

  • Online-Seminare zu MongoDB kostenlos

    Die Firma 10gen bietet drei kostenlose Online-Videokurse für die NoSQL-Datenbank MongoDB an, von denen der erste am Montag startet.

  • React

    Vom Unternehmen Facebook kommt das quelloffene Javascript-Framework React, das Weboberflächen geschickt mit Datenschätzen verknüpft. Insbesondere die Renderfunktion macht sich dabei nützlich.

  • Mongo DB

    Mongo DB ist ein Musterexemplar unter den NoSQL-Datenbanken: Die Open-Source-Software hat zeitgemäße Features wie Replikation, Failover und Sharding bereits eingebaut. Der Autor setzt die Datenbank schon seit 2009 produktiv ein und breitet seinen Erfahrungsschatz aus.

comments powered by Disqus

Ausgabe 08/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.