Schnelle Datenanalyse mit Gnuplot

Aus LinuxUser 09/2020

Schnelle Datenanalyse mit Gnuplot

© lightwise, 123RF

Malen statt Zahlen

Häufig vermitteln Diagramme einen Sachverhalt wesentlich anschaulicher als Zahlenkolonnen. Gnuplot leistet hier wertvolle Hilfe.

Eine Grafik sagt mehr als tausend Zahlen, weshalb auch viele Tabellenprogramme eigene Plot-Routinen mitbringen. Mit den Bibliotheken Matplotlib und Seaborn erzeugt Python exzellente Diagramme. In diesem Artikel geht es allerdings weniger um Schönheit als um Schnelligkeit.

Das freie Programm Gnuplot [1] gehört zur Standardausstattung jedes Linux-Rechners. Falls die Distribution es nicht ohnehin schon vorinstalliert, findet es sich zumindest in den Paketquellen.

Gnuplot dient als Allzweckwerkzeug zum grafischen Beurteilen von Daten. Es zeichnet nicht nur Wertepaare in Achsendiagramme: Mit wenigen Zeilen fittet es selbst definierte Funktionen an Messwerte, auf Wunsch mit Fehlerbalken und in dreidimensionaler Perspektive.

Datenanalyse

Dieser Artikel beschränkt sich auf Schnelligkeit – sowohl in der Handhabung als auch bei der Eleganz der Darstellung. Daten-Labels und Fehlerbalken bleiben außen vor, ebenso die perspektivische Darstellung von 3D-Datensätzen. Wer darüber hinaus Balkendiagramme, Mehrachsendiagramme, Farb- und Symboleffekte erkunden möchte, sei auf das 300 Seiten umfassende Handbuch auf der Gnuplot-Homepage verwiesen [2].

Als Beispiel für die Visualisierung dienen Daten zur Corona-Krise. Im Zusammenspiel mit der Programmiersprache Python erzeugt Gnuplot daraus Grafiken, die Zusammenhänge deutlicher erschließen als ein langer Blick auf die Zahlen. Die Datenanalyse erfolgt in mehreren Schritten: Laden der Daten, Aufbereitung und Beurteilung der Plausibilität, grafische Darstellung, erweiterte Aufbereitung, Datenfits und Interpretation der Daten.

Es gibt wenige Datenquellen zu den Fallzahlen von Covid-19, und noch weniger Informationen zur Beurteilung von deren Qualität. Das soll uns hier nicht weiter stören: Hier geht es lediglich um die Verfahren zur Darstellung, die Interpretation ist nicht Gegenstand dieses Artikels.

Methusalem Gnuplot

Das freie, quelloffene Plotprogramm Gnuplot erblickte vor über 30 Jahren das Licht der Welt. Manche seiner Aspekte wirken etwas aus der Zeit gefallen. Dazu gehören seine Programmierfähigkeiten, beispielsweise Schleifen, oder seine interaktive Benutzerschnittstelle.

Die Skriptsprache wirkt uneinheitlich. Für viele Kommandos kennt sie unterschiedliche Schreibweisen. Spalten adressiert sie etwa als Zahl der Spaltenposition. Um eine Rechenoperation einzuleiten, müssen Sie ein Dollarzeichen voranstellen und den Ausdruck klammern. Handelt es sich jedoch um eine Zeichenkette, dann erwartet Gnuplot statt des Dollarzeichens den geklammerten Ausdruck (stringcolumn(n)). Spalten lassen sich bei gesetztem Schlüssel autotitle columnhead über Spaltennamen ansprechen – aber nicht immer.

Die Grundfunktionen fallen erfrischend einfach aus. Sie laden dazu ein, schnell einen Überblick über Messwerte und Funktionsverläufe zu gewinnen. Anstatt sich in die Besonderheiten der Programmierung von Gnuplot einzuarbeiten, investieren Sie Ihre Mühen besser in die Python-Programmierung. Im Zusammenspiel beider Programme gelingen sogar animierte Plots.

Die hier behandelten Aufrufe entsprechen der Form aus Listing 1: Die erste Zeile startet das Programm und übergibt ihm eine Skriptdatei mit den Gnuplot-Anweisungen. Die zweite Zeile liefert ein Beispiel dafür, wie das Tool Kommandos und Variablen an die Skriptdatei weiterreicht. Fehlt der Parameter -persistent (-p), schließt Gnuplot das Grafikfenster unmittelbar nach dem Plot.

Listing 1

$ gnuplot -persistent plot.plt
$ gnuplot -persistent -e "data='demo5_de.csv'" plot2.plt

Datenvorbereitung

Die meiste Zeit verbringt ein Datenanalytiker mit dem Bereinigen und Aufbereiten der Daten. Die Beispieldaten zu Covid-19 liegen in gutem Zustand vor. Da aber die Zusammenstellung nicht passt, fallen hier vorab einige Handgriffe an.

Die erste Zeile aus Listing 2 lädt die Demodaten [3] von der Webseite in das aktuelle Verzeichnis (Abbildung 1). Der dritte Befehl in Listing 2 extrahiert die Zahlen für Deutschland und nummeriert die Zeilen. Damit die Spaltennamen nicht verloren gehen, stellt der Echo-Befehl aus Zeile 2 den Daten eine Zeile mit den entsprechenden Bezeichnern voran.

Listing 2

$ wget https://raw.githubusercontent.com/datasets/covid-19/master/data/countries-aggregated.csv
$ echo Id,Date,Country,Confirmed,Recovered,Deaths > demo5_de.csv
$ grep Germany countries-aggregated.csv | nl -v 0 -s , >> demo5_de.csv
$ awk 'BEGIN {FS = ","}; NR>1 {print $1, $4}' demo5_de.csv > demo2_de.csv

Abbildung 1: Die Corona-Fallzahlen aus der Web-Datenquelle: Datum, Ländername und Anzahl der Covid-Infizierten, Genesenen und Gestorbenen.

Abbildung 1: Die Corona-Fallzahlen aus der Web-Datenquelle: Datum, Ländername und Anzahl der Covid-Infizierten, Genesenen und Gestorbenen.

Der vierte Befehl schränkt die Daten noch weiter auf die zwei Spalten Index und Infektionen für Deutschland ein; er löscht auch die erste Textzeile. Die Demodateien demo2_de.csv (zwei Spalten), demo5_de.csv (fünf Spalten, Listing 3) und countries-aggregated.csv (ebenfalls fünf Spalten, aber vermischte Zeilen vieler Staaten) bilden die Basis für die folgenden Beispiele.

Listing 3

Id,Date,Country,Confirmed,Recovered,Deaths
0,2020-01-22,Germany,0,0,0
1,2020-01-23,Germany,0,0,0
2,2020-01-24,Germany,0,0,0
...

Erste Plots

Die Datendatei demo2_de.csv besteht aus zwei Zahlenspalten, wobei Leerzeichen die Werte in einer Zeile voneinander trennen. Das Diagramm aus Abbildung 2 erscheint nach dem Aufruf der ersten Zeile aus Listing 1. Die Skriptdatei plot.plt enthält dabei lediglich folgende Zeile:

plot 'demo2_de.csv'
Abbildung 2: Der erste Datenplot enth&auml;lt die Anstiegskurve der Infiziertenzahlen in Deutschland. Als Grundlage verwendet die Grafik die Datei <code>demo2_de.csv</code>.

Abbildung 2: Der erste Datenplot enthält die Anstiegskurve der Infiziertenzahlen in Deutschland. Als Grundlage verwendet die Grafik die Datei demo2_de.csv.

Alternative Schreibweisen für den Aufruf finden Sie in Listing 4. Dort kommt die letzte Zeile ohne Skriptdatei aus; über den Schalter -e leitet sie das nachfolgende Kommando unmittelbar an Gnuplot weiter.

Listing 4

$ gnuplot -persist plot.plt
$ gnuplot -p plot.plt
$ gnuplot -p -e "plot 'demo2_de.csv'"

Fehlen weitere Angaben, arbeitet Gnuplot mit plausiblen Annahmen. Es passt automatisch die Achsen an, verwendet als Bezeichner den Dateinamen der Daten und wählt passende Marker, um Datenreihen voneinander zu unterscheiden.

An der von Listing 2 erzeugten Datendatei demo5_de.csv scheitern die Standardeinstellungen von Gnuplot allerdings aus drei Gründen: Es erwartet ein Whitespace (Leerzeichen oder Tabulator) als Datentrenner. Oft fällt es nicht auf, wenn in einer kommaseparierten Datei (CSV) dem Komma ein Leerzeichen folgt.

Des Weiteren bezeichnet die erste Zeile die Spalten, Gnuplot findet dort keine Zahlen. Und schließlich interpretiert die Software ohne weitere Angaben die ersten beiden Datenspalten als x- und y-Wert. Das Datum in der zweiten Spalte kann Gnuplot nicht ohne Weiteres interpretieren.

Listing 5 berücksichtigt diese Anforderungen und hübscht die Grafik weiter auf (Abbildung 3). Der Titel erscheint über der Grafik. Der Befehl set key autotitle columnhead weist Gnuplot an, die Spaltenbeschriftungen aus der ersten Zeile der Datendatei zu übernehmen. Die Zuordnung der Zahlenreihen schreibt das Tool in die obere linke Ecke (set key top left). Kommas separieren die Daten innerhalb einer Zeile (set datafile separator ','). xlabel und ylabel beschriften die Achsen, xrange schränkt den Plotbereich der x-Werte ein.

Listing 5

set title 'Corona Demodata'
set key autotitle columnhead
set key top left
set datafile separator ','
set xlabel 'Days'
set ylabel 'People'
set xrange[40:100]
plot data using 1:4 pointsize 2,\
  '' using "Id":"Recovered" pointtype 7,\
  '' using 1:6  linewidth 4 with lines,\
  '' using (column('Id')):(column('Confirmed')-\
       column('Recovered')-column('Deaths'))\
       linewidth 2 smooth acsplines title 'Infected'
set terminal png size 640,384
set output 'temp.png'
replot

Abbildung 3: Die aus <a href="#artRef-l5">Listing&nbsp;5</a> generierte Grafik b&uuml;gelt einige Unzul&auml;nglichkeiten von Gnuplot aus und bereitet die Daten informativer auf.

Abbildung 3: Die aus Listing 5 generierte Grafik bügelt einige Unzulänglichkeiten von Gnuplot aus und bereitet die Daten informativer auf.

Die Bildschirmausgabe lässt sich in die Zwischenablage kopieren. Das Speichern über das Disketten-Icon (Abbildung 3) führt wegen eines Modulfehlers ins Nirvana. Deshalb legen die letzten drei Zeilen die Grafik in der Datei temp.png ab.

Wie alle anderen Befehle steht auch die Plotanweisung in einer Zeile. Ein Backslash als Zeilentrenner ist erlaubt, sofern es sich um das letzte Zeichen in der Zeile handelt.

In Abbildung 3 zeichnet Gnuplot vier Kurven. Alle Anweisungen dafür stehen hinter dem Befehl plot, zur besseren Lesbarkeit auf mehrere Zeilen aufgeteilt. Weil es sich um eine Variable handelt, steht die Datendatei nicht in Hochkommas wie in Listing 4.

Der Gnuplot-Aufruf nach dem Schema der zweiten Zeile in Listing 1 ordnet der Variablen data den Wert der Datendatei zu, hier demo5_de.csv mit der in Listing 3 gezeigten Struktur.

Die Schreibweise von plot erklärt sich selbst. Die erste Kurve plottet die erste Spalte als x-Wert gegen die vierte Spalte (using 1:4) mit der Markergröße von 2.

Die zweite Kurve stützt sich auf dieselben Daten, deshalb der leere String. Dieses Mal werden die Spalten nicht über ihre Position angesprochen, sondern über den Spaltennamen. Als Markertyp mit der Nummer 7 dient ein kleiner Kreis.

Die dritte Kurve stellt eine Linie dar (with lines). Die vierte berechnet die Zahl der aktuell Infizierten als Differenz aller Infizierter abzüglich der Genesenen und der Verstorbenen. Die interne Funktion column() übersetzt den Spaltennamen in die Spaltennummer. Während die vorigen Kurven ihre Bezeichnung aus den Spaltenüberschriften beziehen, vergibt title hier einen eigenen Namen.

Gnuplot kennt mehrere Glättungsfunktionen, beispielsweise Spines (smooth acsplines) oder Bezier-Kurven (smooth bezier).

Der Austausch des Filterworts Germany bei Durchstöbern der heruntergeladenen Fallzahlendatei genügt, um Grafiken für beliebige andere Länder zu zeichnen. Die Ausgangstabelle bezeichnet die Spalten mit englischen Ländernamen. Listing 6 wählt Schweden (engl. Sweden) aus (Abbildung 4).

Listing 6

$ echo Id,Date,Country,Confirmed,Recovered,Deaths > demo5_se.csv
$ grep Sweden countries-aggregated.csv | nl -v 0 -s , >> demo5_se.csv
$ gnuplot -persistent -e "data='demo5_se.csv'" plot2.plt

Abbildung 4: Bereits der Austausch eines Filterworts gen&uuml;gt, um aus der Quelldatei die Statistik eines anderen Lands zu generieren (im Beispiel Schweden).

Abbildung 4: Bereits der Austausch eines Filterworts genügt, um aus der Quelldatei die Statistik eines anderen Lands zu generieren (im Beispiel Schweden).

Zusätzlich erhält auch die Zieldatei den neuen Namen demo5_se.csv, um jene für Deutschland nicht zu überschreiben.

Datenfit

Gnuplot zeichnet auch Funktionen. In der Anfangsphase der Corona-Pandemie nahmen die Fallzahlen exponentiell zu. Eine Skriptdatei mit den Zeilen aus Listing 7 genügt, um die Exponentialfunktion mit ihren beiden Parametern L und k auszugeben.

Listing 7

f(x)= L * exp(k*x)
L=0.1
k=0.2
plot f(x)

Gnuplot passt auch Funktionen an Messwerte an. Es freut den Praktiker, wie einfach sich ein Fit anstoßen lässt. Diejenigen, die sich mit Problemen nichtlinearer Fits auskennen, verfolgen die Konvergenz des Levenberg-Marquardt-Algorithmus über die Protokolldatei fit.log.

Listing 8 erzeugt zwei Funktionen: Die Funktionsparameter L0 und k0 der Funktion f0(x) bleiben unverändert. Die Zeile mit fit optimiert die Parameter L und k der Funktion f(x). In Abbildung 5 folgt die blaue Linie den Punkten mit dem kleinsten quadratischen Fehler.

Listing 8

set title 'Corona Demodata'
set key autotitle columnhead
set key top left
set datafile separator ','
set xlabel 'Days'
set ylabel 'People'
data = 'demo5_de.csv'
# Reference function, no fit
f0(x)= L0 * exp(k0 * x)
L0=0.1
k0=0.2
# fit function and its parameters
f(x)= L * exp(k * x)
L=0.1
k=0.2
fit [30:60] f(x) data using "Id":"Confirmed" via L,k
T2 = log(2)/k
set xrange [30:60]
set yrange [0:16000]
plot data using "Id":"Confirmed" ps 1 pt 7, \
  f0(x) title 'f0(x)',\
  f(x) lw 2 title \
  sprintf('exp-fit: L=%.3f; T2=%.3f', L, T2)

Abbildung 5: In der aus <a href="#artRef-l8">Listing&nbsp;8</a> generierten Grafik kommt das Datenfitting zum Einsatz. Die blaue Linie folgt den Punkten mit dem kleinsten quadratischen Fehler.

Abbildung 5: In der aus Listing 8 generierten Grafik kommt das Datenfitting zum Einsatz. Die blaue Linie folgt den Punkten mit dem kleinsten quadratischen Fehler.

Der Schreibbefehl sprintf() stammt als Anleihe aus der Programmiersprache C. Der erste Parameter definiert den auszugebenden Text; bei den folgenden handelt es sich um die Variablen, die im Text das Prozentzeichen ersetzen.

Das Beispiel zeigt nicht unmittelbar den Wert von k an, sondern dessen Kehrwert, multipliziert mit 0,69, dem natürlichen Logarithmus von 2. Dieser Wert entspricht der Zeit in Tagen, nach der sich der Funktionswert verdoppelt – hier sind es knapp 3,3 Tage.

Mehrfachplot

Wenige kennen die Fähigkeit von Gnuplot, Datenblöcke nacheinander in dieselbe Grafik zu zeichnen. Zur Demonstration vergleichen wir die Corona-Fallzahlen bezogen auf 100 000 Einwohner für Deutschland, Schweden und Island.

Listing 9 bereitet die Daten vor. Zunächst erfasst es die Daten für Deutschland. Zwei Echo-Befehle schreiben zwei Leerzeilen in die Datei, die die Blöcke voneinander abgrenzen. Es folgen die Daten für Schweden und Island, jeweils mit eigener Spaltenüberschrift und ebenfalls getrennt durch zwei Leerzeilen.

Listing 9

$ echo Id,Date,Country,Confirmed DE,Recovered,Deaths > demo5_deseis.csv
$ grep Germany countries-aggregated.csv | nl -v 0 -s , >> demo5_deseis.csv
$ echo >> demo5_deseis.csv
$ echo >> demo5_deseis.csv
$ echo Id,Date,Country,Confirmed SE,Recovered,Deaths >> demo5_deseis.csv
$ grep Sweden countries-aggregated.csv | nl -v 0 -s , >> demo5_deseis.csv
$ echo >> demo5_deseis.csv
$ echo >> demo5_deseis.csv
$ echo Id,Date,Country,Confirmed IS,Recovered,Deaths >> demo5_deseis.csv
$ grep Iceland countries-aggregated.csv | nl -v 0 -s , >> demo5_deseis.csv

Listing 10 dient als Steuerdatei für Gnuplot. Das Schlüsselwort, um auf die Datenblöcke zuzugreifen, lautet index, gefolgt von der Blocknummer. Deutschland hat rund 83 Millionen Einwohner, Schweden etwa 10 Millionen, Island nur knapp 360 000. Die Korrekturzahlen, bezogen auf 100 000 Einwohner, stehen in Listing 10 als Variablen (pde=831, pse=103, pis=3.57). Wie erläutert erfordert die Division die eigenartige Syntax, der Spaltennummer ein Dollarzeichen voranzustellen und den Ausdruck zu klammern.

Listing 10

set title 'Corona Demodata'
set key autotitle columnhead
set key top left
set datafile separator ','
set xlabel 'Days'
set ylabel 'Confirmed/100000 inhabitants'
set xrange[40:100]
data = 'demo5_deseis.csv'
pde=831
pse=103
pis=3.57
plot data index 0 using 1:($4)/pde,\
  '' index 1 using 1:($4)/pse,\
  '' index 2 using 1:($4)/pis

Sie speichern die Plotdatei aus Listing 10 unter dem Namen plot3.plt und stoßen dann mit gnuplot -persistent plot3.plt die Grafikausgabe an. Es öffnet sich das Fenster aus Abbildung 6. Als Bezeichner der Kurven wählt Gnuplot jeweils die Spaltenbezeichner der betreffenden Datenblöcke.

Abbildung 6: Ergebnis des Plots aus <a href="#artRef-l10">Listing&nbsp;10</a>. Was viele nicht wissen: Gnuplot zeichnet auch Datenbl&ouml;cke nacheinander in dieselbe Grafik.

Abbildung 6: Ergebnis des Plots aus Listing 10. Was viele nicht wissen: Gnuplot zeichnet auch Datenblöcke nacheinander in dieselbe Grafik.

Gnuplot kennt auch Schleifen. Listing 11 zeigt eine entsprechende Anwendung für Listing 10. Array-Variablen vereinfachen die Auswahl der richtigen Beschriftung. Auch hier offenbart Gnuplot sein Alter und die Unausgeglichenheit der Entwicklungen: Die Zählung der Blöcke beginnt bei 0, die der Arrays bei 1.

Listing 11

array popl[3] = [831, 103, 3.57]
array coutr[3] = ['Germany', 'Sweden', 'Iceland']
plot for [i=0:2] data index i using 1:($4)/popl[i+1] title coutr[i+1]

Daten aufbereiten

Puristen scheuen sich, ähnliche Befehle mehrfach zu schreiben. Stattdessen programmieren sie eine Programmierschleife. Wie in Gnuplot kommt auch in der Linux-Shell nicht die rechte Freude auf, wenn erweiterte Anforderungen an die Daten gestellt werden.

Im Folgenden geben wir einen kurzen Ausblick, wie Python die Plotdaten zusammenträgt. Auf das Bereinigen schlecht organisierter Daten, wie falscher Index, fehlende oder unplausible Daten, gehen wir nicht ein.

Das Python-Skript aus Listing 12 erledigt alles, was auch die oben gezeigten Kommandozeilenbefehle erledigt haben. Da die Python-Bibliothek die Hauptarbeit übernimmt, fällt es vergleichsweise kurz aus.

Listing 12

import pandas as pd
import subprocess
# Get and save data
url = 'https://raw.githubusercontent.com/datasets/covid-19/master/data/countries-aggregated.csv'
df=pd.read_csv(url, header=0)
# collect data from Iceland
mycountry = 'Iceland'
dg = df[df.Country == mycountry].reset_index()
dg.index.name = 'Id'
dg.drop('index', axis=1, inplace=True)
dg.to_csv('is_country.csv', index=True)
# plot Iceland data
p = subprocess.Popen(['gnuplot', '-persistent', '-e',  "data='is_country.csv'", 'plot2.plt'])
# collect 'Confirmed' from all countries
select = 'Confirmed'
dh = df.pivot(index='Date', columns='Country', values=['Confirmed', 'Recovered', 'Deaths'])
dk= dh[select].reset_index()
dk.index.name = 'Id'
dk.to_csv('countries_confirmed.csv', index=True)

Die ersten Zeilen nach den Import-Befehlen speichern die Rohdaten aus dem Internet in einem Pandas-Dataframe-Objekt (Zeile 5 und 6). Die nächsten Zeilen wählen die Daten eines Lands aus, im Beispiel Island, und speichern sie als CSV-Datei unter dem Namen is_country.csv (Zeile 9 bis 13). Mit subprocess.Popen ruft Python die Kommandozeile auf, wobei es die Parameter als Elemente einer Liste übergibt (Zeile 16).

Die Grafik in Abbildung 7 zeigt die Werte für Island – dort ist die Corona-Krise praktisch ausgestanden. Der zweite Teil des Python-Skripts erzeugt eine Liste aller erfassten Staaten (Zeile 19 bis 22). Sie beschränkt sich auf das Attribut select, hier die Zahl der bestätigten Infektionen (Confirmed).

Abbildung 7: Das Ergebnis des Skripts aus <a href="#artRef-l12">Listing&nbsp;12</a>. Das Resultat unterscheidet sich nur marginal von <a href="#artRef-f3">Abbildung&nbsp;3</a>, wurde aber komplett von einem Python-Skript generiert.

Abbildung 7: Das Ergebnis des Skripts aus Listing 12. Das Resultat unterscheidet sich nur marginal von Abbildung 3, wurde aber komplett von einem Python-Skript generiert.

Fazit

Eine Datenanalyse besteht aus dem Sammeln von Daten, deren Aufbereitung, dem Sichten und Auswerten. Das Programm Gnuplot unterstützt die Beurteilung der Daten durch eine schnelle grafische Darstellung. Wenige Skriptzeilen genügen, um Grafiken unterschiedlicher Datensätze vergleichbar gegenüberzustellen.

Gnuplot stellt selbstdefinierte Funktionen dar, die es auf Wunsch an Messwerte angleicht. Einfache Werkzeuge für die Linux-Kommandozeile bereiten die Daten für die Plots vor. Noch einfacher klappt das Ganze mit Python: Mit wenigen Befehlszeilen stellt es die Zahlen in jeder gewünschten Form zusammen. (tle/jlu)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 09/2020 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben