Die Morpheus-Bibliothek soll die Entwicklung leistungsstarker Analysesoftware mit großen Datensätzen sowohl für die Offline- als auch für die Echtzeitanalyse auf der Java Virtual Machine (JVM) erleichtern. Die Bibliothek ist in Java 8 unter umfangreicher Verwendung von Lambdas geschrieben, ist aber für alle JVM-Sprachen zugänglich.
Eine ausführliche Dokumentation mit Beispielen finden Sie hier
Im Kern stellt Morpheus eine vielseitige zweidimensionale , speichereffiziente tabellarische Datenstruktur namens DataFrame
bereit, die der erstmals in R populären ähnelt. Dynamisch typisierte wissenschaftliche Computersprachen wie R, Python und Matlab eignen sich zwar hervorragend für die Forschung, sind aber nicht gut geeignet Sie eignen sich für groß angelegte Produktionssysteme, da ihre Wartung äußerst schwierig und die Umgestaltung gefährlich ist. Die Morpheus-Bibliothek versucht, die Leistungsfähigkeit und Vielseitigkeit des DataFrame
Konzepts beizubehalten und gleichzeitig einen viel typsichereren und selbstbeschreibenden Satz von Schnittstellen bereitzustellen, der die Entwicklung, Wartung und Skalierung der Codekomplexität erheblich erleichtern dürfte.
Ein weiterer Vorteil der Morpheus-Bibliothek besteht darin, dass sie sich aufgrund der leistungsstarken Threading-Funktionen der Java Virtual Machine hervorragend auf Multi-Core-Prozessorarchitekturen skalieren lässt. Viele Vorgänge auf einem Morpheus DataFrame
können nahtlos parallel ausgeführt werden, indem einfach parallel()
für die Entität aufgerufen wird, die Sie bearbeiten möchten, ähnlich wie bei Java 8 Streams. Intern basieren diese parallelen Implementierungen auf dem Fork & Join-Framework, und bei bestimmten Arten von Vorgängen werden nahezu lineare Leistungsverbesserungen beobachtet, wenn CPU-Kerne hinzugefügt werden.
Ein Morpheus DataFrame
ist eine Spaltenspeicherstruktur, bei der jede Spalte durch ein Morpheus- Array
dargestellt wird, für das es viele Implementierungen gibt, darunter dichte, spärliche und speicherzugeordnete Versionen. Morpheus-Arrays sind optimiert und werden, wo immer möglich, durch primitive native Java-Arrays unterstützt (sogar für Typen wie LocalDate
, LocalDateTime
usw.), da diese aus Sicht der Speicherung, des Zugriffs und der Speicherbereinigung weitaus effizienter sind. Speicherzugeordnete Morpheus Arrays
sind zwar noch experimentell, ermöglichen jedoch die Erstellung sehr großer DataFrames
mithilfe von Off-Heap-Speicher, die durch Dateien unterstützt werden.
Während sich der komplette Funktionsumfang des Morpheus DataFrame
noch in der Entwicklung befindet, gibt es bereits viele leistungsstarke APIs, mit denen sich komplexe Transformationen und Analysevorgänge problemlos durchführen lassen. Es gibt Standardfunktionen zum Berechnen zusammenfassender Statistiken, zum Durchführen verschiedener Arten linearer Regressionen und zum Anwenden der Hauptkomponentenanalyse (PCA), um nur einige zu nennen. Der DataFrame
ist sowohl in der Zeilen- als auch in der Spaltendimension indiziert, sodass Daten entlang beider Achsen effizient sortiert , segmentiert , gruppiert und aggregiert werden können.
Morpheus möchte außerdem einen Standardmechanismus zum Laden von Datensätzen verschiedener Datenanbieter bereitstellen. Die Hoffnung besteht darin, dass diese API von der Community angenommen wird, um den Katalog der unterstützten Datenquellen zu erweitern. Derzeit sind Anbieter implementiert, die das Laden von Daten von Quandl, der Federal Reserve, der Weltbank, Yahoo Finance und Google Finance ermöglichen.
Betrachten Sie einen Datensatz von Kraftfahrzeugeigenschaften, der hier zugänglich ist. Der folgende Code lädt diese CSV-Daten in einen Morpheus DataFrame
, filtert die Zeilen, um nur die Fahrzeuge einzuschließen, die ein Leistungsgewicht von > 0,1 haben (wobei das Gewicht in Kilogramm umgerechnet wird), und fügt dann eine Spalte hinzu, um die relative Effizienz zwischen Autobahn und Autobahn aufzuzeichnen Stadtmeilenzahl (MPG), sortiert die Zeilen nach dieser neu hinzugefügten Spalte in absteigender Reihenfolge und zeichnet dieses transformierte Ergebnis schließlich in einer CSV-Datei auf.
DataFrame . read (). csv ( options -> {
options . setResource ( "http://zavtech.com/data/samples/cars93.csv" );
options . setExcludeColumnIndexes ( 0 );
}). rows (). select ( row -> {
double weightKG = row . getDouble ( "Weight" ) * 0.453592d ;
double horsepower = row . getDouble ( "Horsepower" );
return horsepower / weightKG > 0.1d ;
}). cols (). add ( "MPG(Highway/City)" , Double . class , v -> {
double cityMpg = v . row (). getDouble ( "MPG.city" );
double highwayMpg = v . row (). getDouble ( "MPG.highway" );
return highwayMpg / cityMpg ;
}). rows (). sort ( false , "MPG(Highway/City)" ). write (). csv ( options -> {
options . setFile ( "/Users/witdxav/cars93m.csv" );
options . setTitle ( "DataFrame" );
});
Dieses Beispiel demonstriert die funktionale Natur der Morpheus-API, bei der viele Methodenrückgabetypen tatsächlich ein DataFrame
sind und daher diese Form der Methodenverkettung ermöglichen. In diesem Beispiel geben die Methoden csv()
, select()
, add()
und sort()
alle einen Frame zurück. In manchen Fällen derselbe Frame, den die Methode bearbeitet, oder in anderen Fällen ein Filter oder eine flache Kopie des Frames, der bearbeitet wird. Die ersten 10 Zeilen des transformierten Datensatzes sehen in diesem Beispiel wie folgt aus, wobei die neu hinzugefügte Spalte ganz rechts im Rahmen erscheint.
Index | Hersteller | Modell | Geben Sie | ein Mindestpreis | Preis | Max.Preis | MPG.city | MPG.highway | Airbags | Antriebsstrang | Zylinder | EngineSize | Pferdestärken | U/min | Umdrehungen pro Meile | Man.trans.avail | Kraftstofftankinhalt | Passagiere | Länge | Radstand | Breite | Wendekreis | Platz im Fond | Gepäckraum | Gewicht | Herkunft | Machen | MPG(Autobahn/Stadt) | -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- 9 | Cadillac | DeVille | Groß | 33.0000 | 34,7000 | 36,3000 | 16 | 25 | Nur Fahrer | Vorne | 8 | 4,9000 | 200 | 4100 | 1510 | Nein | 18.0000 | 6 | 206 | 114 | 73 | 43 | 35 | 18 | 3620 | USA | Cadillac DeVille | 1,5625 | 10 | Cadillac | Sevilla | Mittelklasse | 37,5000 | 40.1000 | 42,7000 | 16 | 25 | Fahrer & Beifahrer | Vorne | 8 | 4,6000 | 295 | 6000 | 1985 | Nein | 20.0000 | 5 | 204 | 111 | 74 | 44 | 31 | 14 | 3935 | USA | Cadillac Sevilla | 1,5625 | 70 | Oldsmobile | Achtundachtzig | Groß | 19,5000 | 20,7000 | 21.9000 | 19 | 28 | Nur Fahrer | Vorne | 6 | 3,8000 | 170 | 4800 | 1570 | Nein | 18.0000 | 6 | 201 | 111 | 74 | 42 | 31,5 | 17 | 3470 | USA | Oldsmobile Eighty-Eight | 1.47368421 | 74 | Pontiac | Feuervogel | Sportlich | 14.0000 | 17.7000 | 21.4000 | 19 | 28 | Fahrer & Beifahrer | Hinten | 6 | 3,4000 | 160 | 4600 | 1805 | Ja | 15,5000 | 4 | 196 | 101 | 75 | 43 | 25 | 13 | 3240 | USA | Pontiac Firebird | 1.47368421 | 6 | Buick | LeSabre | Groß | 19,9000 | 20,8000 | 21.7000 | 19 | 28 | Nur Fahrer | Vorne | 6 | 3,8000 | 170 | 4800 | 1570 | Nein | 18.0000 | 6 | 200 | 111 | 74 | 42 | 30,5 | 17 | 3470 | USA | Buick LeSabre | 1.47368421 | 13 | Chevrolet | Camaro | Sportlich | 13.4000 | 15.1000 | 16.8000 | 19 | 28 | Fahrer & Beifahrer | Hinten | 6 | 3,4000 | 160 | 4600 | 1805 | Ja | 15,5000 | 4 | 193 | 101 | 74 | 43 | 25 | 13 | 3240 | USA | Chevrolet Camaro | 1.47368421 | 76 | Pontiac | Bonneville | Groß | 19.4000 | 24.4000 | 29,4000 | 19 | 28 | Fahrer & Beifahrer | Vorne | 6 | 3,8000 | 170 | 4800 | 1565 | Nein | 18.0000 | 6 | 177 | 111 | 74 | 43 | 30,5 | 18 | 3495 | USA | Pontiac Bonneville | 1.47368421 | 56 | Mazda | RX-7 | Sportlich | 32,5000 | 32,5000 | 32,5000 | 17 | 25 | Nur Fahrer | Hinten | rotierend | 1,3000 | 255 | 6500 | 2325 | Ja | 20.0000 | 2 | 169 | 96 | 69 | 37 | NA | NA | 2895 | Nicht-USA | Mazda RX-7 | 1.47058824 | 18 | Chevrolet | Korvette | Sportlich | 34,6000 | 38.0000 | 41,5000 | 17 | 25 | Nur Fahrer | Hinten | 8 | 5,7000 | 300 | 5000 | 1450 | Ja | 20.0000 | 2 | 179 | 96 | 74 | 43 | NA | NA | 3380 | USA | Chevrolet Corvette | 1.47058824 | 51 | Lincoln | Town_Car | Groß | 34,4000 | 36.1000 | 37,8000 | 18 | 26 | Fahrer & Beifahrer | Hinten | 8 | 4,6000 | 210 | 4600 | 1840 | Nein | 20.0000 | 6 | 219 | 117 | 77 | 45 | 31,5 | 22 | 4055 | USA | Lincoln Town_Car | 1.44444444 |
Die Morpheus-API enthält eine Regressionsschnittstelle, um Daten mithilfe von OLS, WLS oder GLS an ein lineares Modell anzupassen. Der folgende Code verwendet denselben Fahrzeugdatensatz, der im vorherigen Beispiel eingeführt wurde, und regressiert Horsepower auf EngineSize . Das Codebeispiel druckt die Modellergebnisse im Standardout aus, wie unten gezeigt, und erstellt dann ein Streudiagramm mit deutlich angezeigter Regressionslinie.
//Load the data
DataFrame < Integer , String > data = DataFrame . read (). csv ( options -> {
options . setResource ( "http://zavtech.com/data/samples/cars93.csv" );
options . setExcludeColumnIndexes ( 0 );
});
//Run OLS regression and plot
String regressand = "Horsepower" ;
String regressor = "EngineSize" ;
data . regress (). ols ( regressand , regressor , true , model -> {
System . out . println ( model );
DataFrame < Integer , String > xy = data . cols (). select ( regressand , regressor );
Chart . create (). withScatterPlot ( xy , false , regressor , chart -> {
chart . title (). withText ( regressand + " regressed on " + regressor );
chart . subtitle (). withText ( "Single Variable Linear Regression" );
chart . plot (). style ( regressand ). withColor ( Color . RED ). withPointsVisible ( true );
chart . plot (). trend ( regressand ). withColor ( Color . BLACK );
chart . plot (). axes (). domain (). label (). withText ( regressor );
chart . plot (). axes (). domain (). format (). withPattern ( "0.00;-0.00" );
chart . plot (). axes (). range ( 0 ). label (). withText ( regressand );
chart . plot (). axes (). range ( 0 ). format (). withPattern ( "0;-0" );
chart . show ();
});
return Optional . empty ();
});
=============================================== ========================================== Ergebnisse der linearen Regression =============================================== ========================================== Modell: OLS R-Quadrat: 0,5360 Beobachtungen: 93 R-Quadrat (angepasst): 0,5309 DF-Modell: 1 F-Statistik: 105,1204 DF-Residuen: 91 F-Statistik (Prob): 1,11E-16 Standardfehler: 35,8717 Laufzeit (Millis) 52 Durbin-Watson: 1,9591 =============================================== ========================================== Index | PARAMETER | STD_ERROR | T_STAT | P_VALUE | CI_LOWER | CI_UPPER | -------------------------------------------------- --------------------------------------------- Abfangen | 45.2195 | 10.3119 | 4.3852 | 3.107E-5 | 24.736 | 65.7029 | EngineSize | 36,9633 | 3.6052 | 10.2528 | 7.573E-17 | 29.802 | 44.1245 | =============================================== ==========================================
Über die UK Government Open Data-Initiative ist es möglich, auf alle britischen Wohnimmobilientransaktionsaufzeichnungen von 1995 bis heute zuzugreifen. Die Daten werden im CSV-Format dargestellt und enthalten zahlreiche Spalten, darunter Informationen wie das Transaktionsdatum, den gezahlten Preis, die vollständig qualifizierte Adresse (einschließlich Postleitzahl), die Art der Immobilie, die Art des Mietvertrags usw.
Beginnen wir mit dem Schreiben einer Funktion zum Laden dieser CSV-Dateien aus Amazon S3-Buckets. Da sie eine Datei pro Jahr speichern, stellen wir eine entsprechend parametrisierte Funktion bereit. Angesichts der Anforderungen unserer Analyse ist es nicht erforderlich, alle Spalten in der Datei zu laden. Daher wählen wir im Folgenden nur das Lesen der Spalten an den Indexnummern 1, 2, 4 und 11 aus. Darüber hinaus enthalten die Dateien keinen Header , benennen wir die Spalten in einen aussagekräftigeren Namen um, um den späteren Zugriff etwas klarer zu machen.
/**
* Loads UK house price from the Land Registry stored in an Amazon S3 bucket
* Note the data does not have a header, so columns will be named Column-0, Column-1 etc...
* @param year the year for which to load prices
* @return the resulting DataFrame, with some columns renamed
*/
private DataFrame < Integer , String > loadHousePrices ( Year year ) {
String resource = "http://prod.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-%s.csv" ;
return DataFrame . read (). csv ( options -> {
options . setResource ( String . format ( resource , year . getValue ()));
options . setHeader ( false );
options . setCharset ( StandardCharsets . UTF_8 );
options . setIncludeColumnIndexes ( 1 , 2 , 4 , 11 );
options . getFormats (). setParser ( "TransactDate" , Parser . ofLocalDate ( "yyyy-MM-dd HH:mm" ));
options . setColumnNameMapping (( colName , colOrdinal ) -> {
switch ( colOrdinal ) {
case 0 : return "PricePaid" ;
case 1 : return "TransactDate" ;
case 2 : return "PropertyType" ;
case 3 : return "City" ;
default : return colName ;
}
});
});
}
Im Folgenden verwenden wir diese Daten, um den mittleren Nominalpreis (nicht inflationsbereinigt) einer Wohnung für jedes Jahr zwischen 1995 und 2014 für eine Teilmenge der größten Städte im Vereinigten Königreich zu berechnen. Der ungefilterte Datensatz enthält zwischen 1993 und 2014 etwa 20 Millionen Datensätze, und während das Laden und Parsen ziemlich lange dauert (ungefähr 3,5 GB Daten), führt Morpheus den analytischen Teil des Codes in etwa 5 Sekunden aus (ohne). Ladezeit) auf einem Standard-MacBook Pro von Apple, das Ende 2013 gekauft wurde. Beachten Sie, wie wir die Parallelverarbeitung verwenden, um die Daten durch Aufrufen zu laden und zu verarbeiten results.rows().keys().parallel()
.
//Create a data frame to capture the median prices of Apartments in the UK'a largest cities
DataFrame < Year , String > results = DataFrame . ofDoubles (
Range . of ( 1995 , 2015 ). map ( Year :: of ),
Array . of ( "LONDON" , "BIRMINGHAM" , "SHEFFIELD" , "LEEDS" , "LIVERPOOL" , "MANCHESTER" )
);
//Process yearly data in parallel to leverage all CPU cores
results . rows (). keys (). parallel (). forEach ( year -> {
System . out . printf ( "Loading UK house prices for %s... n " , year );
DataFrame < Integer , String > prices = loadHousePrices ( year );
prices . rows (). select ( row -> {
//Filter rows to include only apartments in the relevant cities
final String propType = row . getValue ( "PropertyType" );
final String city = row . getValue ( "City" );
final String cityUpperCase = city != null ? city . toUpperCase () : null ;
return propType != null && propType . equals ( "F" ) && results . cols (). contains ( cityUpperCase );
}). rows (). groupBy ( "City" ). forEach ( 0 , ( groupKey , group ) -> {
//Group row filtered frame so we can compute median prices in selected cities
final String city = groupKey . item ( 0 );
final double priceStat = group . colAt ( "PricePaid" ). stats (). median ();
results . data (). setDouble ( year , city , priceStat );
});
});
//Map row keys to LocalDates, and map values to be percentage changes from start date
final DataFrame < LocalDate , String > plotFrame = results . mapToDoubles ( v -> {
final double firstValue = v . col (). getDouble ( 0 );
final double currentValue = v . getDouble ();
return ( currentValue / firstValue - 1d ) * 100d ;
}). rows (). mapKeys ( row -> {
final Year year = row . key ();
return LocalDate . of ( year . getValue (), 12 , 31 );
});
//Create a plot, and display it
Chart . create (). withLinePlot ( plotFrame , chart -> {
chart . title (). withText ( "Median Nominal House Price Changes" );
chart . title (). withFont ( new Font ( "Arial" , Font . BOLD , 14 ));
chart . subtitle (). withText ( "Date Range: 1995 - 2014" );
chart . plot (). axes (). domain (). label (). withText ( "Year" );
chart . plot (). axes (). range ( 0 ). label (). withText ( "Percent Change from 1995" );
chart . plot (). axes (). range ( 0 ). format (). withPattern ( "0.##'%';-0.##'%'" );
chart . plot (). style ( "LONDON" ). withColor ( Color . BLACK );
chart . legend (). on (). bottom ();
chart . show ();
});
Die prozentuale Veränderung der nominalen Durchschnittspreise für Wohnungen in der Teilmenge der ausgewählten Städte ist in der folgenden Grafik dargestellt. Es zeigt, dass London infolge der globalen Finanzkrise (GFC) keinen nominalen Rückgang der Immobilienpreise erlitten hat, allerdings erwiesen sich nicht alle Städte im Vereinigten Königreich als widerstandsfähig. Etwas überraschend ist, dass einige der weniger wohlhabenden Städte im Norden im Zeitraum 2003 bis 2006 im Vergleich zu London eine höhere Aufwertungsrate verzeichneten. Zu beachten ist, dass es in London zwar keinen nominellen Preisrückgang gab, es aber sicherlich zu einer ziemlich starken Korrektur in Bezug auf EUR und USD kam, da das Pfund Sterling während der globalen Finanzkrise gegenüber diesen Währungen stark an Wert verloren hat.
Die Visualisierung von Daten in Morpheus DataFrames
wird durch eine einfache Diagrammabstraktions-API mit Adaptern erleichtert, die sowohl JFreeChart als auch Google Charts unterstützen (weitere folgen aufgrund der großen Nachfrage). Dieses Design ermöglicht die Generierung interaktiver Java-Swing-Diagramme sowie HTML5-browserbasierter Diagramme über dieselbe Programmschnittstelle. Weitere Informationen zur Verwendung dieser API finden Sie im Abschnitt zur Visualisierung hier und im Code hier.
Morpheus wird in Maven Central veröffentlicht, sodass es problemlos als Abhängigkeit zum Build-Tool Ihrer Wahl hinzugefügt werden kann. Die Codebasis ist derzeit in 5 Repositories unterteilt, um die unabhängige Weiterentwicklung jedes Moduls zu ermöglichen. Das Kernmodul, das treffend Morpheus-Core genannt wird, ist die grundlegende Bibliothek, von der alle anderen Module abhängen. Die verschiedenen Maven-Artefakte sind wie folgt:
Morpheus-Kern
Die grundlegende Bibliothek, die Morpheus-Arrays, DataFrames und andere wichtige Schnittstellen und Implementierungen enthält.
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-core</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Morpheus-Visualisierung
Die Visualisierungskomponenten zur Anzeige DataFrames
in Diagrammen und Tabellen.
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-viz</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Morpheus Quandl
Der Adapter zum Laden von Daten aus Quandl
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-quandl</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Morpheus Google
Der Adapter zum Laden von Daten aus Google Finance
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-google</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Morpheus Yahoo
Der Adapter zum Laden von Daten von Yahoo Finance
< dependency >
< groupId >com.zavtech</ groupId >
< artifactId >morpheus-yahoo</ artifactId >
< version >${VERSION}</ version >
</ dependency >
Mithilfe von Google Groups wurde ein Forum für Fragen und Antworten eingerichtet, auf das Sie hier zugreifen können
Morpheus-Javadocs können hier online abgerufen werden.
Hier kann auf einen Continuous-Integration-Build-Server zugegriffen werden, der nach jeder Zusammenführung Code erstellt.
Morpheus wird unter der Apache Software Foundation-Lizenz Version 2 veröffentlicht.