Lokale Datenanalyse mit Data Frames

Am Anfang war die Frage

Michael Vitz, Michael Krämer

Der Wunsch, aus Geschäftsdaten Erkenntnisse gewinnen zu wollen, ist in der Breite der Unternehmen angekommen. Häufig werden dabei technologische Aspekte wie Machine-Learning-Algorithmen und Cluster-Lösungen als Schlüssel betrachtet, um viele Fragen quasi automatisch zu beantworten. Doch muss es für einen Einstieg in dieses Thema immer direkt ein Big-Data-Framework oder ein Cluster für Machine Learning sein? Dieser Artikel zeigt Ihnen, wie Sie ganz einfach lokal auf Ihrem Rechner die ersten Schritte im Data-Science-Umfeld gehen können und dabei einen Überblick über die Vorgehensweise erhalten.

Betrachtet man die Geschichte der Informationstechnologie über die letzten 20 bis 30 Jahre, so kann man deutlich erkennen, dass eine Verschiebung der Schwerpunkte stattgefunden hat. Während früher performantere Hardware bedeutende Verbesserungen der Ergebnisse ermöglichte, rückte danach effiziente und wartbare Software in den Mittelpunkt. Inzwischen sind es jedoch die Daten selbst und das Know-how, diese auszuwerten, die den Unterschied ausmachen.

Doch obwohl die Technologien zur Verarbeitung dieser massiven Datenmengen, dank Open Source, allen frei zur Verfügung stehen, bleibt der Eindruck, dass nur wenige Firmen tatsächlich in der Lage sind, die ihnen verfügbaren Daten in Erkenntnisse und damit in echten Mehrwert zu verwandeln [1]. Der Unterschied scheint also weniger in der Technologie als mehr in der Methodik, mit der die Daten analysiert werden, zu liegen.

Data Science

Das oberste Ziel von Data Science besteht darin, aus Daten Erkenntnisse zu gewinnen, mit deren Hilfe sich Prognosen für weitere (meist zukünftige) Werte erstellen lassen. Damit dies optimal gelingt, bietet es sich an, einem Prozess [2] zu folgen.

In der ersten Phase geht es darum, den Gegenstand der Untersuchung festzulegen. Die wichtigsten Punkte sind hier die Fragen, die beantwortet werden sollen, und das Auswählen geeigneter Datenquellen hierzu.

Sind die Fragen formuliert und die Datenquellen ausgewählt, müssen die vorhandenen Daten importiert werden. Einen einzelnen Datensatz bezeichnet man dabei als Beobachtung (engl. “observation”). Diese wiederum besteht aus zusammengehörigen Attributen. Jedes Attribut hat dabei pro Beobachtung eine konkrete Ausprägung, die man Variable nennt.

Da die importierten Daten häufig keine zur Analyse passende Struktur besitzen, besteht der erste Schritt darin, diese Transformation durchzuführen. Typische Transformationen sind hierbei:

  • Aufsplitten von Daten einer Quelle in mehrere Beobachtungen,
  • Zusammenfügen mehrerer Quellen in eine einzelne Beobachtung,
  • Beseitigung von Fehlern einzelner Werte, wie Tippfehler,
  • Überführung von Werten in identische Skala, wie Umwandlung von Meter in Zentimeter.

Im Anschluss an den Import findet die explorative Analyse der Daten statt. Diese Phase hat nicht nur Einfluss auf die folgenden Phasen, sondern kann auch die Fragestellung oder Daten noch beeinflussen. Stellt man beispielsweise fest, dass bestimmte Attribute fehlen, so müssen Datenquellen für diese Attribute gefunden und anschließend hinzugefügt werden. Aber auch eine Anpassung der Fragestellung ist möglich, wenn man bereits hier feststellt, dass die zur Verfügung stehenden Daten diese nicht beantworten können. Aus diesem Grund bietet es sich an, in dieser Phase iterativ vorzugehen und möglichst interaktiv mit den Daten zu interagieren.

Die bisherigen Schritte überschneiden sich sowohl von den Tätigkeiten als auch in den Ergebnissen mit dem klassischen Verständnis von Business Intelligence (BI). Dort steht jedoch zumeist die Analyse der Vergangenheit im Fokus und mündet in aufbereiteten und zusammengefassten Kennzahlen der Daten oder entsprechender Diagramme als Endprodukt. Die oben angesprochene explorative Analyse kann ähnliche Ergebnisse erzielen, allerdings mit anderer Intention. Bei der explorativen Analyse geht es vor allem darum, Korrelationen zwischen Variablen zu identifizieren und ein Gefühl dafür zu bekommen, welche Informationen die Daten enthalten könnten und welche Teilmenge der Variablen eine gute Beschreibung dafür liefert.

Um Prognosen treffen zu können, nutzt man das Wissen der explorativen Analyse, um geeignete Parameter für einen Machine-Learning-Algorithmus abzuleiten und damit ein Vorhersage-Modell zu trainieren. Ein solches Modell liefert für eine Menge von Eingabevariablen einen Ausgabewert in ähnlicher Art, wie eine mathematische Funktion Parameter auf einen Funktionswert abbildet.

Es gibt eine Vielzahl von Algorithmen, Algorithmen-Typen und Implementierungen, auf die wir in diesem Rahmen nicht eingehen können. Wichtig ist jedoch zu wissen, dass die Algorithmen in einer Trainings-Phase versuchen, aus den vorhandenen Daten die systematischen Zusammenhänge der vorhandenen Variablen auf die Ausgangsvariable mit möglichst geringen Fehlern abzubilden. Im Optimalfall führt dies dazu, dass das erstellte Modell nicht nur die konkreten Werte der Beobachtungen aus den Daten abbildet, sondern auch für weitere Werte gute Vorhersagen liefert. Ein wirkliches „Verständnis” in dem Sinn, dass ein kausaler Zusammenhang entdeckt und benannt werden könnte, entsteht dabei im Modell nicht. Man überprüft die Qualität des Modells im Rahmen des Trainings immer wieder mit Teilen der Daten. Allerdings beurteilt dies immer nur die Qualität bezogen auf die Menge der bereits bekannten Daten, die Qualität der Prognosen kann nur statistisch bewertet werden, und auch dieses Teil-Gebiet der Modell-Validierung ist ein komplexes eigenes Feld.

Sofern man ein Modell zu exakt an die Trainingsdaten anpasst, spricht man von Overfitting. In diesen Fällen liefert das Modell für die bekannten Daten bei der Validierung sehr gute Vorhersagen, ist aber in der Realität deutlich weniger performant.

Der Einfluss auf das Endergebnis nimmt dabei mit jedem Schritt ab. Somit ist es am wichtigsten, am Anfang eine sinnvolle Frage zu stellen und diese auf saubere Daten anzuwenden. Vergleichen lässt sich dies mit der Herstellung von Espresso. Hier wird der Geschmack am meisten durch die Wahl der Bohnen bestimmt, allerdings lässt man sich häufig durch die teuren, großen und chromglänzenden Maschinen beeindrucken, die den Kaffee brühen.

Ähnlich scheint es uns auch beim Thema Data Science zu sein. Häufig entsteht der Eindruck, dass die Planung eines Hadoop-Clusters oder anderer Infrastruktur der erste Schritt in das Thema darstellt. Dabei wäre es viel sinnvoller, zuerst einmal mit den Daten, oder einem kleinen Ausschnitt davon, zu spielen. Genau dies wollen wir im Folgenden einmal exemplarisch zeigen.

Data Frames

Auf der Suche nach einer möglichst flexiblen Datenstruktur für tabellarische Daten wurde im Bereich der Statistik-Software das Konzept von Data Frames entwickelt. Diese In-Memory- Datenstrukturen weisen eine hohe Ähnlichkeit zu Tabellen in SQL-Datenbanken auf.

Ein Data Frame besteht aus Zeilen und Spalten. Eine Zeile entspricht dabei typischerweise einer Beobachtung, die Spalten den Variablen. Jede Spalte innerhalb einer Zeile besitzt einen definierten Datentyp.

Interessant werden Data Frames durch die umfangreichen Methoden, die man auf diese anwenden kann. Es lassen sich sowohl Zeilen (Filtering) als auch Spalten (Slicing) selektieren. Zudem können Zeilen sortiert (Sorting) oder zusammengefasst (Grouping) werden. Außerdem lassen sich mehrere Data Sets zu einem kombinieren (Joining). Hierzu können, je nach Implementierung, Indizes verwendet werden.

Neben der Data-Set-Implementierung werden in der Regel auch nützliche Funktionen zum Im- und Export der Daten angeboten. Gängige Datenformate, wie CSV, werden dabei von quasi allen unterstützt. Meistens finden sich auch Funktionen, um aus den Data Frames direkt Diagramme zu erstellen.

Für die bereits angesprochene Trainings-Phase beim Machine Learning (ML) ist es erforderlich, die Daten viele Male in zufälliger Reihenfolge durchzuarbeiten. Aus diesem Grund ist zum Training der Modelle fast immer eine In-Memory-Datenstruktur erforderlich. Auch dafür werden Data Frames als Datenformat sehr häufig eingesetzt.

Data Frames werden dadurch zu einem universellen Werkzeug für Transformation, explorative Analyse und als abstraktes Datenformat für Machine Learning, die in allen oben angesprochen Phasen eingesetzt werden können.

Die bekanntesten Implementierungen von Data Frames finden sich unter anderen in der Programmiersprache R, der Python-Bibliothek Pandas und in Apache Spark. Auf der JVM kann man zudem das Java/R Interface oder Joinery verwenden. Zum einfachen Einstieg nutzen wir im Folgenden Joinery, um Ihnen anhand eines kleinen Beispiels die Verwendung von Data Frames näher zu bringen.

Beispiel: Bahnsteiglänge

Natürlich muss man sich zuerst für ein Data Set entscheiden. Mittlerweile steht dazu eine große Anzahl an frei verfügbaren Daten (siehe Kasten „Öffentliche Daten“) zur Verfügung. Für diesen Artikel haben wir uns entschieden, Daten der Deutschen Bahn zu verwenden. Diese hat bereits 2015 damit begonnen, ein eigenes Datenportal aufzubauen. Aus diesem Fundus nutzen wir die Liste von Bahnhöfen und den dort vorhandenen Bahnsteigen.

Öffentliche Daten

Spätestens, wenn man am Bahnsteig steht und die Durchsage „heute in umgekehrter Wagenreihung“ durchgegeben wird, stellt man fest, dass Bahnsteige unter Umständen ziemlich lang sein können. Doch wie lang ist so ein Bahnsteig in Deutschland eigentlich? Und sind alle Bahnsteige gleich lang? Diese beiden Fragen sollen nun mit Hilfe von Joinery geklärt werden.

Zuerst lesen wir dazu die CSV-Datei mit den Bahnsteigdaten mit Hilfe einer zur Verfügung gestellten Methode ein und erhalten somit einen Data Frame, auf dem wir arbeiten können (s. Listing 1).

String url = "http://.../DBSuS-Bahnsteigdaten-Stand2016-03.csv";
DataFrame<Object> platforms = DataFrame.readCsv(url, ";");
Listing 1: Einlesen der Daten

Anschließend schneiden wir lediglich die Spalte für die Länge eines Bahnsteigs aus und sortieren die Zeilen danach (s. Listing 2).

platforms = platforms
        .retain(4)  // keep only length of platform
        .sortBy(0); // sort by length of platform
Listing 2: Selektieren der Daten

Um auf die so gewonnenen Daten und deren Verteilung einen ersten Blick werfen zu können, bietet es sich an, ein Histogramm (wie häufig kommen welche Bahnsteiglängen vor) auszugeben. Hierzu müssen wir allerdings die Daten noch nach den Längen gruppieren (s. Listing 3).

DataFrame<Object> data = platforms
        .groupBy(row -> {
            double length = (double) row.get(0);
            return bucketFor(length);
        })
        .count();
data.rename("Nettobaulänge (m)", "Anzahl");
System.out.println("data: \n" + data);
data.plot(DataFrame.PlotType.BAR);
Listing 3: Gruppieren der Daten

Der so auf die Konsole ausgegebene Data Frame ist bereits gut lesbar (s. Listing 4) und das geplottete Diagramm (s. Abb. 1) gibt einen ersten Einblick in die Daten bezogen auf unsere Frage.

Data:
     Anzahl
 15    21
 47   379
 79  1110
111  2450
143  2469
175  1227
207  1944
239   509
271   481

... 10 rows skipped ...

655     1
Listing 4: Textausgabe der Data Frames
Abb. 1: Säulendiagramm der gruppierten Bahnsteiglängen

Wir können bereits jetzt sehen, dass es Bahnsteige mit nur ein paar Metern Länge gibt. Es können aber im Extremfall auch einmal über 600 Meter sein. Der Großteil der Bahnsteige scheint jedoch zwischen 100 und 250 Metern lang zu sein.

Neben dieser, zugegeben sehr simplen, Auswertung enthält der unter https://github.com/mkraemerx/joinery-example erhältliche Code zu diesem Artikel noch ein zweites Beispiel, welches das Zusammenfügen zweier Data Sets demonstriert. Hierzu nutzen wir Daten über die Bahnhöfe, um zu zeigen, welche Bahnhöfe die fünf längsten Bahnsteige besitzen.

Die Nutzung von Data Frames und deren Methoden ähnelt dabei häufig vom Aufbau her Datenbankabfragen in relationalen Datenbanken. Durch die Java-Struktur lassen sich aber zum Beispiel bei Gruppierungen beliebige Funktionen nutzen. Auch die einfache Möglichkeit, sich eine Visualisierung der Daten anzuzeigen, stellt einen Mehrwert von Data Frames gegenüber SQL dar.

Bereits an diesem Beispiel ist ersichtlich, dass man für einen Einstieg in Data Science keinen großen Cluster oder riesige Frameworks benötigt. Mit Joinery ist es auch lokal möglich, einen ersten Blick in vorhandene Daten zu werfen. Wir wollen jedoch auch die bereits hier sichtbaren Schwachstellen nicht verschweigen.

Für das Zusammenführen zweier Data Sets müssen sowohl der Fremd- als auch der Primärschlüssel innerhalb des Data Sets eindeutig sein. Zudem lassen sich nur indizierte Spalten als Schlüssel nutzen. Bereits simple Verknüpfungen wie eine 1:n-Beziehung lassen sich hiermit nur über den Umweg der Gruppierung erreichen.

Zudem fällt schnell auf, dass der Umgang mit teil-strukturierten Daten in einer statisch typisierten Sprache wie Java mühsam sein kann. Joinery hat sich hierbei entschieden, Generics lediglich auf der obersten Ebene zu unterstützen. Somit muss der Entwickler immer wieder die Datentypen per Cast festlegen. Zwar enthält jede Spalte einen Typen, die Zugriffsfunktionen, wie get(), geben allerdings das generische Object zurück.

Die dritte Schwachstelle von Joinery zeigt sich im Speicherverbrauch. Um beispielsweise eine 537 MB große CSV-Datei mit Daten zu Wetterphänomenen aus den USA [3] einzulesen, verbrauchte Joinery in unserem Test fast 3 GB Heap. Dasselbe kann in R mit einem deutlich kleineren Footprint von ca. 150 MB erledigt werden. Da wir dies jedoch weder genauer untersucht noch durch weitere Versuche validiert haben, ist dies natürlich nur ein erster Hinweis.

Neben dem hier vorgestellten Ansatz sei noch auf den Shell-Modus von Joinery verwiesen. Diesen kann man gut dazu nutzen, interaktiv mit vorhandenen Daten zu experimentieren, ohne direkt die IDE zu starten.

Fazit

Es braucht nicht immer ein komplexes Setup für den Einstieg in Data Science. Bereits mit einfachen Werkzeugen wie Joinery lassen sich erste Erfolge erreichen.

Sollten Sie sich jetzt mehr für die Thematik interessieren und tiefer einsteigen wollen, empfiehlt es sich jedoch, sich R oder Pandas anzuschauen. R erscheint dem aus der Java-Welt kommenden Entwickler auf den ersten Blick zwar sehr seltsam, ist jedoch die Referenz in der Data-Science-Welt. Wer sich einmal mit der scheinbar unstrukturierten Syntax und den heterogenen Funktions- und Paketnamen angefreundet hat, erhält einen sehr performanten Kern, eingebaute Unterstützung für Listen und deren Verarbeitung sowie ein umfangreiches Paket von Statistikfunktionen. Ebenso gibt es sehr gute Bibliotheken für Machine Learning und Plotting.

Wem die in diesem Artikel genutzten Beispiele zu trivial erscheinen, dem seien die Arbeiten von Todd W. Schneider ans Herz gelegt. Seine Analysen zu der US-amerikanischen Zeichentrickserie „Die Simpsons“ und Taxifahrten in New York zeigen, dass auch größere Analysen ohne Cluster möglich sind. Wer das ganze lieber per Audio konsumieren möchte, sollte sich die Software Engineering Daily Episode vom 18.10.2016 anhören.

Als letztes sei noch darauf hingewiesen, dass uns als Entwickler, die mit solchen Daten arbeiten, eine große Verantwortung zukommt. Den Umgang damit müssen wir nun lernen und bei jedem weiteren Schritt zwischen dem Schutz der Privatsphäre und dem Nutzen der Daten abwägen.

Links

Thumb michael vitz innoq

Michael Vitz ist Senior Consultant bei innoQ und verfügt über mehrjährige Erfahrung in der Entwicklung und im Betrieb von JVM-basierten Systemen.

Zur Zeit beschäftigt er sich vor allem mit den Themen Microservices, Cloud-Architekturen, DevOps, Spring-Framework sowie Clojure.

Weitere Inhalte

Thumb michel face sq800 1171

Michael Krämer entwickelt seit über 15 Jahren Software und arbeitet als Softwarearchitekt bei innoQ. Er beschäftigt sich in letzter Zeit vor allem mit Metriken in verteilten Systemen, funktionaler Programmierung in Scala, Continuous Deployment und Data Science.

Weitere Inhalte

Java spektrum
Dieser Artikel ist ursprünglich in Ausgabe 01/2017 der Zeitschrift JavaSPEKTRUM erschienen. Die Veröffentlichung auf innoq.com erfolgt mit freundlicher Genehmigung des SIGS-Datacom-Verlags.

Kommentare

Um die Kommentare zu sehen, bitte unserer Cookie Vereinbarung zustimmen. Mehr lesen