No description has been provided for this image

Machine
Learning

Joern Ploennigs

Datenvorverarbeitung

Dieser Abschnitt erklärt und demonstriert bestimmte Datenbereinigungs- und Vorbereitungsaufgaben unter Verwendung von Pandas. Die Aufgabe hier besteht hauptsächlich darin, Sie mit verschiedenen nützlichen Funktionen vertraut zu machen und zu zeigen, wie häufige Aufgaben gelöst werden. Wir sprechen nicht viel über grundlegende Datenverarbeitungsprobleme.

Fehlende Beobachtungen¶

In vielen Datensätzen aus der Praxis stoßen wir auf fehlende Werte. Sie enstehen weil (i) Werte nicht regelmäßig erfasst wurden, (ii) Werte historisch nicht gemessen wurden, (iii) Messgeräte ausfallen oder (iv) Werte entfernt worden sind, weil man dachte, dass sie nicht benötigt werden.

Solche fehlenden Werte, können sehr schnell Berechnungen verfälschen oder unmöglich machen. Deshalb ist es wichtig, fehlende Werte zu identifizieren und diese richtig zu behandeln.

Wie werden fehlende Daten codiert?¶

Grundsätzlich ist es wichtig das man fehlende Werte in den Daten codiert und abspeichert, denn nur so kann man unterscheiden, ob sie bewust oder zufällig fehlen.

  • Numpy np.nan - steht eigentlich für "Not a Number"
  • Pandas pd.NA - steht für "Not Available" und ist eindeutiger als np.nan

Umgang mit fehlenden Werten¶

Um den Umgang mit fehlenden Werten zu lernen, schauen wir uns den Wetter Datensatz des DWD für die Wetterstation in Warnemünde vor 1960 an.

In [5]:
import numpy as np # Import von NumPy
import pandas as pd # Import von Pandas
np.random.seed(3) # Fixierung des Zufallszahlgenerators zur beseren Erklärung

wetter = pd.read_csv("../data/Wetter/warnemuende_1960.csv", sep=';')
wetter.shape
Out[5]:
(7209, 19)
In [6]:
from myst_nb import glue
glue("wetter_size1", wetter.shape[0])
glue("wetter_size2", wetter.shape[1])
7209
19

Die Originaldaten enthalten {glue}:wetter_size1 Zeilen und {glue}:wetter_size2 Spalten.

Als erstes schauen wir uns die Daten mal an. Hierfür bieten sich die *.head() und *.tail() Funktionen, da sie uns den Anfang und das Ende des Datensatzes zeigen.

In [5]:
wetter.head()
Out[5]:
STATIONS_ID MESS_DATUM QN_3 FX FM QN_4 RSK RSKF SDK SHK_TAG NM VPM PM TMK UPM TXK TNK TGK eor
0 4271 19470101 -999 -999 -999.0 5 -999.0 -999 -999.0 -999 -999.0 4.1 1019.3 -2.1 78.0 -1.5 -3.3 -5.8 eor
1 4271 19470103 -999 -999 -999.0 3 -999.0 -999 -999.0 -999 -999.0 3.6 1032.4 -4.8 80.0 -2.4 -6.2 -4.5 eor
2 4271 19470105 -999 -999 -999.0 5 -999.0 -999 -999.0 -999 -999.0 1.9 1031.9 -12.9 79.0 -9.4 -14.0 -16.0 eor
3 4271 19470108 -999 -999 -999.0 5 -999.0 -999 -999.0 -999 -999.0 1.9 1023.1 -12.9 82.0 -9.8 -15.3 -19.0 eor
4 4271 19470109 -999 -999 -999.0 5 -999.0 -999 -999.0 -999 -999.0 2.4 1017.5 -9.9 84.0 -8.7 -13.7 -19.0 eor

Als erstes schauen wir uns die Daten mal an. Hierfür bieteten sich die head und tail Funktionen, da sie uns den Anfang und das Ende des Datensatzes zeigen.

In [6]:
wetter.tail()
Out[6]:
STATIONS_ID MESS_DATUM QN_3 FX FM QN_4 RSK RSKF SDK SHK_TAG NM VPM PM TMK UPM TXK TNK TGK eor
7204 4271 19660926 5 -999 6.6 5 0.3 1 2.2 0 6.6 12.8 1014.0 13.1 86.0 15.1 9.5 7.9 eor
7205 4271 19660927 5 -999 10.0 5 0.0 1 6.1 0 5.8 10.1 1014.3 12.0 72.0 13.3 10.9 10.4 eor
7206 4271 19660928 5 -999 4.5 5 0.0 0 9.9 0 2.6 9.1 1017.5 9.7 74.0 12.5 7.7 8.7 eor
7207 4271 19660929 5 -999 3.3 5 0.0 0 7.8 0 4.8 9.2 1015.8 9.7 78.0 14.9 5.1 3.1 eor
7208 4271 19660930 5 -999 3.5 5 0.0 0 7.8 0 2.2 10.9 1007.9 11.1 83.0 16.1 5.8 4.8 eor

Es gibt also am Anfang des Datensatzes sehr viele Werte die -999 oder -999.0 sind. Am Ende allerdings nur noch in der Spalte FX. Wir können also davon ausgehen, dass in dem Datensatz fehlende Werte mit -999 codiert worden sind. Die fehlenden Werte sind auch erklärbar, da 1947 Messungen noch händisch durchgeführt wurden und nur einfache Größen wie Temperatur erfasst wurden.

Diese fehlenden Werte müssen wir mit np.nan oder pd.NA ersetzen. Hierfür können wir die Dataframe-Methode replace an.

In [7]:
wetter = wetter.replace(-999, pd.NA)
wetter.head()
Out[7]:
STATIONS_ID MESS_DATUM QN_3 FX FM QN_4 RSK RSKF SDK SHK_TAG NM VPM PM TMK UPM TXK TNK TGK eor
0 4271 19470101 <NA> <NA> <NA> 5 <NA> <NA> <NA> <NA> <NA> 4.1 1019.3 -2.1 78.0 -1.5 -3.3 -5.8 eor
1 4271 19470103 <NA> <NA> <NA> 3 <NA> <NA> <NA> <NA> <NA> 3.6 1032.4 -4.8 80.0 -2.4 -6.2 -4.5 eor
2 4271 19470105 <NA> <NA> <NA> 5 <NA> <NA> <NA> <NA> <NA> 1.9 1031.9 -12.9 79.0 -9.4 -14.0 -16.0 eor
3 4271 19470108 <NA> <NA> <NA> 5 <NA> <NA> <NA> <NA> <NA> 1.9 1023.1 -12.9 82.0 -9.8 -15.3 -19.0 eor
4 4271 19470109 <NA> <NA> <NA> 5 <NA> <NA> <NA> <NA> <NA> 2.4 1017.5 -9.9 84.0 -8.7 -13.7 -19.0 eor

Alternativ können wir die Daten direkt aus der CSV mit NA Werten laden, indem wir den Parameter na_values angeben.

In [8]:
wetter = pd.read_csv("../data/Wetter/warnemuende_1960.csv", sep=';', na_values=['-999','NA'])

Jetzt haben wir einen Datensatz, in dem die fehlenden Werte richtig codiert sind. Jetzt ist es sinnvoll zu evaluieren, wie viele Daten fehlen. Wir können mit der Methode count beginnen: Diese Methode gibt die Anzahl der gültigen Beobachtungen für ein Dataframe oder eine einzelne Variable an. Und "gültig" bedeutet hier alles außer np.nan. Die Anzahl der gültigen Beobachtungen für jede Variable ist:

In [9]:
wetter.count()
Out[9]:
STATIONS_ID    7209
MESS_DATUM     7209
QN_3           4656
FX                0
FM             4656
QN_4           7209
RSK            5752
RSKF           5752
SDK            5752
SHK_TAG        5752
NM             5752
VPM            7209
PM             7209
TMK            7209
UPM            7209
TXK            7209
TNK            7209
TGK            7209
eor            7209
dtype: int64

Wir können sehen, dass einige Variablen, z.B. TNK und TGK, gültige Werte für alle {glue}:wetter_size1 Fälle haben, während FX keinen einzigen gültigen Werte hat.

Alternativ können wir die Anzahl der fehlenden Werte direkt zählen. Hierfür können wir zwei Methoden kombinieren: isna (oder isnull) gibt für jede Beobachtung an, ob sie fehlt (wahr oder falsch), und sum addiert diese Wahrheitswerte und Falschwerte, wobei Wahrheitswerte in Einsen und Falschwerte in Nullen umgewandelt werden. Auf diese Weise kann dieser Ansatz wie count die Anzahl der fehlenden Werte für jede Variable in einem Dataframe oder nur für eine einzelne Variable in einer Serie produzieren. So können wir die Anzahl der fehlenden Werte wie folgt erhalten:

In [10]:
wetter.isna().sum()
Out[10]:
STATIONS_ID       0
MESS_DATUM        0
QN_3           2553
FX             7209
FM             2553
QN_4              0
RSK            1457
RSKF           1457
SDK            1457
SHK_TAG        1457
NM             1457
VPM               0
PM                0
TMK               0
UPM               0
TXK               0
TNK               0
TGK               0
eor               0
dtype: int64

Offensichtlich können wir das gleiche Ergebnis erzielen, indem wir die Anzahl gültigen Werte von der Anzahl der Zeilen abziehen

In [11]:
wetter.shape[0] - wetter.count()
Out[11]:
STATIONS_ID       0
MESS_DATUM        0
QN_3           2553
FX             7209
FM             2553
QN_4              0
RSK            1457
RSKF           1457
SDK            1457
SHK_TAG        1457
NM             1457
VPM               0
PM                0
TMK               0
UPM               0
TXK               0
TNK               0
TGK               0
eor               0
dtype: int64

Diese beiden Beispiele zeigen count und isna, die auf Dataframes angewendet werden. Wenn wir nur die Anzahl der fehlenden Werte in einer einzelnen Variablen zählen wollen, können wir einfach diese Variable auswählen. Zum Beispiel auf die Spalte QN_3:

In [12]:
wetter.QN_3.count()
Out[12]:
4656

Ein guter Weg, um fehlende Beobachtungen zu entfernen, ist die Verwendung der Methode dropna. Standardmäßig werden alle Zeilen entfernt, die einen fehlenden Wert enthalten.

In [13]:
t = wetter.dropna()
t.shape
Out[13]:
(0, 19)

Wir haben keine Beobachtungen mehr! Es gibt keine einzige Zeile, die gültige Werte in allen Zellen hat. Während dieser Fehler hier leicht zu erkennen ist---da nachfolgende Analysen ohne Werte scheitern werden---können solche Probleme insbesondere bei großen Datensätzen und in automatisierten Datenanalyseverfahren zu Fehlern und falschen Ergebnissen führen, die nicht augenscheinlich falsch sind. Stellen Sie sich den Fall vor, dass das Entfernen von fehlenden Werten unseren Datensatz um 90 % verkleinert, weil eine irrelevante Variable größtenteils fehlt. Es sei denn, wir überprüfen die resultierende Anzahl von Zeilen, könnten wir unsere Analyse auf einem völlig falschen Subset abschließen.

Wie die obigen Tabellen nahelegen, ist der Hauptverursacher die Spalte FX die keine einzigen gültigen Wert enthält. In solchen Fällen ist es sinnvoll solche Spalten komplett zu entfernen. Dies können wir auch mit der Methode dropna machen, indem wir sie nur auf Spalten anwenden (axis=1) und diese entfernen, wenn alle Werte darin fehlen (how="all"):

In [14]:
t = wetter.dropna(axis=1, how="all")
t.shape
Out[14]:
(7209, 18)

Wenn unsere Analyse nur bestimmte Variablen erfordert, sollten wir nicht alle solchen Zeilen entfernen, sondern nur diejenigen Zeilen entfernen, in denen unsere wichtigen Variablen fehlen. Wollen wir z.B. nur die Temperatur (TMK) und Luftfeuchtigkeit (UPM) analysieren, können wir die fehlenden Zeilen nur in diesen beiden Variablen entfernen, indem wir das Argument subset verwenden:

In [15]:
w = t.dropna(subset=["TMK", "UPM"])
w.shape
Out[15]:
(7209, 18)
In [16]:
w.head()
Out[16]:
STATIONS_ID MESS_DATUM QN_3 FM QN_4 RSK RSKF SDK SHK_TAG NM VPM PM TMK UPM TXK TNK TGK eor
0 4271 19470101 NaN NaN 5 NaN NaN NaN NaN NaN 4.1 1019.3 -2.1 78.0 -1.5 -3.3 -5.8 eor
1 4271 19470103 NaN NaN 3 NaN NaN NaN NaN NaN 3.6 1032.4 -4.8 80.0 -2.4 -6.2 -4.5 eor
2 4271 19470105 NaN NaN 5 NaN NaN NaN NaN NaN 1.9 1031.9 -12.9 79.0 -9.4 -14.0 -16.0 eor
3 4271 19470108 NaN NaN 5 NaN NaN NaN NaN NaN 1.9 1023.1 -12.9 82.0 -9.8 -15.3 -19.0 eor
4 4271 19470109 NaN NaN 5 NaN NaN NaN NaN NaN 2.4 1017.5 -9.9 84.0 -8.7 -13.7 -19.0 eor

Wenn unsere Analyse nur bestimmte Variablen erfordert, sollten wir nicht alle solchen Zeilen entfernen, sondern nur diejenigen Zeilen entfernen, in denen unsere wichtigen Variablen fehlen. Stellen Sie sich vor, wir möchten die Variablen age und home.dest analysieren. Wir können die fehlenden Zeilen nur in diesen beiden Variablen entfernen, indem wir das Argument subset verwenden:

Falsche Beobachtungen¶

Bisher haben wir diskutiert, wie wir mit fehlenden Werten umgehen. Es gibt darüber hinaus weitere Gründe für fehlerhafte und ungewöhnliche Daten, wie Messfehler, Eingabefehler, Einheitenfehler, Skalierungsfehler, Berechnungsfehler, etc. Diese Werte könne Ergebnisse sehr stark verzerren. Deshalb ist es wichtig sie zu identifizieren und zu behandeln. Man unterscheidet die folgenden Arten:

  • Fehlende Werte: Sind Werte bei denen bekannt ist, dass sie fehlen.
  • Außerhalb des Wertebereichs (Out-of-range): Sind Werte die logisch ausschließbar sind. Zum Beispiel weil sie außerhalb des definierten Wertebereichs liegen (z.B. Temperatur < -273.15°C), außerhalb des Messbereichs liegen (z.B. Raumtemperatursensor > 60°C), oder offensichtlich Falsch sind (z.B. 200 Jahre alter Mensch).
  • Ausreißer: Sind Werte, die statistisch deutlich von den restlichen Daten abweichen. Sie können entweder viel größer oder viel kleiner sein als die meisten anderen Werte. Sie sind insbesondere dann ein Problem, wenn sie häufiger auftreten als statistisch erklärbare Werte.
  • Anomalien: Sind Werte, die Kausal erklärbar sind, aber vom erwarteten Verhalten abweichen, z.B. Wetteranomalien.

Diese fehlerhaften oder ungewöhnlichen Werte werden meist unterschiedlich behandelt:

  • Werte außerhalb des Wertebereichs werden meist als fehlende Werte codiert und genauso behandelt, weil sie ja nicht erklärbar auftreten können.
  • Die Behandlung von Ausreißer ist schwieriger, da sie statistisch vorkommen können und somit erklärbar wären. Man behandelt sie meist nur dann, wenn sie statistisch ungewöhnlich häufig auftreten (ein Indiz für Fehler) oder aber z.B. beim Berechnen von stabilen ML-Modellen stören, da sie sehr starke Varianzen erzeugen.
  • Anomalien sind ungewöhnlich, aber erklärbar und häufig ist es ein Ziel von ML-Modellen diese zu identifizieren (Anomalierkennung). Deshalb entfernt man sie nur selten, wenn man anomaliefreie Datensätze braucht, um ML-Modelle zu trainieren die das Normalverhalten vorhersagen (z.B. zur Anomalieerkennung).

Sie können die Ergebnisse statistischer Analysen verzerren und die Modellierung erschweren. Daher ist es wichtig, Ausreißer zu identifizieren und den richtigen Umgang mit ihnen zu wählen.

Der erste Ansatz ist die Wertebereiche der Daten zu prüfen. Hier kann man deskriptive Methoden benutzen, um eventuelle verdächtige Werte zu erkennen. Bei numerischen Werten ist der offensichtlichste Ausgangspunkt, den Wertebereich zu überprüfen. Zum Beispiel - was ist der kleinste und größte Wert in unserem Wetter Datensatz:

Prüfen wir zum Beispiel die Mittlere Tagestemperatur (TMK):

In [17]:
wetter.TMK.min(), wetter.TMK.max()
Out[17]:
(-14.8, 27.8)

Die Werte liegen durchaus im realistischen Bereich und erfordern keine Änderung notwendig ist. Schauen wir auf die relative Luftfeuchtigkeit (UPM):

In [18]:
wetter.UPM.min(), wetter.UPM.max()
Out[18]:
(42.0, 1000.0)

Dann stellen wir fest, dass diese zwischen 42% und 1000% liegt. Letzteres ist nicht möglich und könnte ein Tippfehler sein. Bei solchen fehlerhaften Werten wissen wir allerdings nicht, was der originale Wert war, deshalb ist es sinnvoll ihn mit pd.NA ersetzen. Hierfür können wir die Funktion mask nutzen:

In [8]:
wetter["UPM"]=wetter.UPM.mask(cond=wetter.UPM > 100, other=pd.NA)

Wenn wir jetzt die Min und Max bestimmen, sehen wir, dass die Werte sich jetzt im Definitionsbereich liegen:

In [20]:
wetter.UPM.min(), wetter.UPM.max()
Out[20]:
(42.0, 100.0)

Wie wir mit Ausreißern und Anomalien umgehen, werden wir später behandeln.

Fehlende Werte Ersetzen¶

Manchmal möchten man fehlenden Werte durch neuen Wert ersetzen. Hier unterscheidet man unterschiedliche Fälle:

  • Fehlende Werte mit sinnvollen Standartwerten: Es gibt Datensätze, wo man durchaus sinnvolle Standartwerte annehmen kann für fehlende Werte. Zum Beispiel, zählen wir die Unfälle auf Baustellen pro Tag. Da diese nur registriert werden, wenn welche auftreten, ist für die anderen Tage anzunehmen (aber nicht garantiert), dass keine aufgetreten sind.

    Man könnte zum Beispiel annehmen, dass die Schneetiefe in unserem Wetterdatensatz 0 ist (Das wäre allerdings falsch), wenn sie nicht angegeben ist. In diesem Fall können wir die fehlenden Werte pd.NA mit fillna ersetzen:

In [21]:
wetter.SHK_TAG.fillna(value=0)
Out[21]:
0       0.0
1       0.0
2       0.0
3       0.0
4       0.0
       ... 
7204    0.0
7205    0.0
7206    0.0
7207    0.0
7208    0.0
Name: SHK_TAG, Length: 7209, dtype: float64

Wenige Fehlende Werte Ersetzen¶

Bei Zeitreihen, wie den Wetterdaten, kann man fehlende Werte zwischendurch ersetzen, wenn man davon ausgehen kann, dass sie sich nicht schnell ändern. Dazu nutzt man Methoden der Interpolation. Pandas unterstützt

  • ffill - Ersetzt NA/NaN-Werte, indem Sie die letzte gültige Beobachtung verwenden, um die Lücke zu füllen.
  • bfill - Ersetzt NA/NaN-Werte, indem Sie die nächste gültige Beobachtung nutzt, um die Lücke zu füllen.
  • interpolate - Ersetzt NA/NaN-Werte mit einer Interpolationsmethode, die mit dem Parameter method angegeben wird:
    • linear - Interpoliert linear zwischen dem letzten und dem nächsten Wert.
    • nearest - Wählt entweder den letzten und dem nächsten Wert, je nachdem welcher zeitlich näher liegt.
    • polynomial - Interpoliert zwischen dem letzten und dem nächsten Wert mit einem Polynom der agebenen Ordnung (z.B. order=5).
    • cubic - Interpoliert zwischen dem letzten und dem nächsten Wert mit einem cubischen Polynom (das Überschwingen vermeidet).

Bei allen diesen Methoden ist empfohlen den Parameter limit zu nutzen. Er bestimmt wie viele nacheinander folgende Werte ersetzt werden dürfen (andere bleiben NA). So mag es akzeptabel sein, ein einzelner Tag zu interpolieren, allerdings nicht eine ganze Woche, da der anzunehmende Interpolationsfehler zu groß wird.

Schauen wir uns die mittlere Temperatur an:

In [22]:
wetter.TMK[:10]
Out[22]:
0    -2.1
1    -4.8
2   -12.9
3   -12.9
4    -9.9
5    -9.1
6   -10.8
7    -9.3
8    -1.1
9     2.6
Name: TMK, dtype: float64

Hier fehlen mehrere Werte. Wir können diese interpolieren mit:

In [9]:
wetter["TMK"]=wetter.TMK.interpolate(method="cubic", limit=1)
wetter.TMK[:10]
Out[9]:
0    -2.1
1    -4.8
2   -12.9
3   -12.9
4    -9.9
5    -9.1
6   -10.8
7    -9.3
8    -1.1
9     2.6
Name: TMK, dtype: float64

Fehlende Werte Am Anfang und Ende¶

Fehlende Werte am Anfang oder Ende einer Zeitreihe kann diese nicht Interpolieren, da Information über Vorgängerwerte oder Nachfolgerwerte fehlen. In solchen Fällen spricht man von Extrapolation. Prinzipiell kann man hier die Funktionen ffill und bfill verwenden. Da halt allerdings Randwertinformationen fehlen ist der anzunehmende Extrapolationsfehler meist groß, weshalb unbedingt limit zu verwenden ist.

In [10]:
wetter["RSK"]=wetter.RSK.ffill(limit=1)

Fehlende Datumswerte hinzufügen¶

Gerade bei der Analyse von Zeitreihen ist es oft wichtig das Messungen in festem Abstand (Equidistant) aufgenommen werden. Um das zu prüfen müssen wir als erstes die Datumsangabe in unserm Wetterdatensatz in einen nutzbaren Datentyp für Zeiten umwandeln. Dies können wir mit der Funktion pd.to_datetime umwandeln. Dafür müssen wir die originale Datumsspalte MESS_DATUM in einen String umwandeln, der von to_datetime verwendet werden kann und ein String der das format repräsentiert (siehe Umgang mit Zeitstempeln):

In [25]:
wetter["DATUM_DT"] = pd.to_datetime(wetter.MESS_DATUM.astype(str), format="%Y%m%d")

Als nächstes empfiehlt es sich den Datensatz nach der Zeit zu sortieren:

In [26]:
wetter.sort_values(by="DATUM_DT", inplace=True) # Sortier nach der Spalte "MESS_DATUM" und ersetzt den Dataframe direkt (inplace)

Dann können wir prüfen, mit welcher Häufigkeit, welche zeitlichen Differenzen auftreten. Wir können dafür die diff Funktion nutzen, um die Zeitdifferenz zu bestimmen. Diese verketten wir mit value_counts. um die Häufigkeit der Differenzen zu berechnen.

In [27]:
wetter.DATUM_DT.diff().value_counts()
Out[27]:
DATUM_DT
1 days    7205
2 days       2
3 days       1
Name: count, dtype: int64

Wir sehen, dass in unserem Datensatz mehre Messwerte fehlen, da Differenz von mehr als 1 Tag vorkommen.

Um diese Daten wiederherzustellen können wir die Funktion resample benutzen. Dafür müssen wir zuerst die Datumsspalte zum Index des Dataframes machen, so dass eine richtige mehrdimensionale Zeitreihe draus wird.

In [28]:
wetter.set_index("DATUM_DT", inplace=True)

Jetzt können wir mit resample den Dataframe in eine regelmäßige tägliche Auflösung bringen. Dabei müssen wir eine Funktion zur Interpolation der fehlenden Werte mit angeben.

In [29]:
wetter1D=wetter.resample('1d').ffill(limit=1)

Nun können wir den Index wieder zu eine Spalte machen

In [30]:
wetter1D.reset_index(inplace=True)

Der neue Dataframe enthält jetzt keine fehlenden Werte

In [31]:
wetter1D.DATUM_DT.diff().value_counts()
Out[31]:
DATUM_DT
1 days    7212
Name: count, dtype: int64

Fehlende Werte und mathematische Operationen¶

Die Methoden .sum und .mean von Pandas ignorieren fehlende Werte. Berechnen wir den Mittelwert der täglich Sonnenscheindauer (SDK):

In [32]:
wetter1D.SDK.mean()
Out[32]:
4.904363699582754

Sie liegt bei 4.9 Studen.

Wenn wir jedoch den Mittelwert entsprechend der Formel für den arithmetischen Mittelwert

$$ \bar{x} = \frac{1}{N} \sum_{i=1}^{N} x_i. $$

selbst berechnen

In [33]:
wetter1D.SDK.sum()/len(wetter1D.SDK)
Out[33]:
3.9109801746845974

Dann ergibt sich ein komplett anderes Ergebnis. Dies liegt daran, dass len fehlende Werte nicht ignoriert und daher die Summe durch die Gesamtzahl der Fälle teilen, inklusive der fehlenden Fälle.

In dem Fall wäre die Berechnung mit count richtig, da dann NA Werte ignoriert werden.

In [34]:
wetter1D.SDK.sum()/wetter1D.SDK.count()
Out[34]:
4.904363699582754

Wenn wir jedoch einen fehlenden Wert mit einer Zahl vergleichen, ergibt sich False und nicht ein fehlender Wert pd.NA, was formal richtig wäre, da für die fehlenden Werte ja keine Aussage getroffen werden kann. Das kann sehr schnell zu falschen Ergebnissen führen. Wollen wir zum Beispiel die Anzahl der Sonnentage mit mehr als 8 Stunden Sonnenschein bestimmen, so ist es intuitiv zu berechnen:

In [35]:
sonnentage = wetter1D.SDK < 8
sonnentage.value_counts(dropna=False)
Out[35]:
SDK
True     4180
False    3033
Name: count, dtype: int64

Das ist allerdings Falsch. Wir haben die Informationen über die fehlenden Werte im Prozess verloren und Tage mit fehlenden Werten automatisch zu keinem Sonnentag gemacht. Die richtige Berechnung kann man mit der Funktion map bestimmen, da mit dem Parameter na_action='ignore' enthaltene NA Werte belassen werden:

In [36]:
sonnentage = wetter1D.SDK.map(lambda x: x < 8, na_action='ignore')
sonnentage.value_counts(dropna=False)
Out[36]:
SDK
True     4180
False    1572
NaN      1461
Name: count, dtype: int64

Dies unterstreicht die Bedeutung der Behandlung von fehlenden Werten in Daten und das man sich mit dem Umgang der Standardfunktionalität von Pandas mit NA vertraut machen sollte.

Konvertierung von Variablen¶

Konvertierung von kategorischen Variablen¶

Manchmal stoßen wir auf Situationen, in denen die in den Daten bereitgestellten Kategorien nicht gut zu den Anforderungen unserer Analyse passen. Zwei häufige Fälle sind, wenn die Originaldaten zu granularen Informationen enthalten (zum Beispiel, wenn wir an ein paar breiten Wirtschaftssektoren interessiert sind, aber die Daten 27 Branchen auflisten) oder Kategorien zu ausführlich sind (zum Beispiel kann der Beruf als "Handwerker, Vorarbeiter und ähnliche" aufgeführt sein, während wir lieber den kürzeren Begriff "qualifiziert" verwenden würden). In diesen Fällen müssen wir die ursprünglichen Kategorien in neue umwandeln und möglicherweise mehrere ursprüngliche Kategorien in eine einzige neue Kategorie zusammenführen.

In diesem Fall gibt es mehrere Möglichkeiten, wie man vorgehen kann. Wir zeigen die Optionen anhand des Falls, in dem die Daten numerisch codiert sind, aber nicht alle Codes echte numerische Werte darstellen.

(convert-categorical-variables-using-pdcut)=

Verwenden von pd.cut¶

pd.cut ist eine dedizierte Funktion von Pandas, um kontinuierliche numerische Daten in vordefinierte Intervalle zu schneiden. Es wird wie folgt aufgerufen:

pd.cut(x, bins, right=True, labels=None)

wo x die Variable ist, die geschnitten werden soll, bins sind die Intervallgrenzen, labels sind die Intervallnamen und right gibt an, ob der rechte Grenzwert im Intervall enthalten ist (right = True bedeutet, dass die Grenzen rechts offen sind). Es ist hauptsächlich für das Aufteilen von kontinuierlichen Werten gedacht, kann aber auch leicht für diskrete Werte verwendet werden. Lassen Sie uns diese Daten in gewünschte Intervalle aufteilen:

In [37]:
wetter1D["TemperaturKlasse"]=pd.cut(wetter1D.TMK,
        bins = [-np.inf, 13, 18, 24, 30, np.inf],
        labels = ["Cold", "Cool", "Mild", "Warm", "Hot"],
        right=False)
In [38]:
wetter1D["TemperaturKlasse"].value_counts(dropna=False)
Out[38]:
TemperaturKlasse
Cold    4861
Cool    1836
Mild     503
Warm      12
NaN        1
Hot        0
Name: count, dtype: int64

Verwendung von np.where¶

Eine weitere Option ist die Verwendung von np.where, einer vektorisierten Version einer if-else-Anweisung. np.where nimmt drei Argumente entgegen: eine vektorisierte logische Bedingung, den Wert, wenn die Bedingung wahr ist, und den Wert, wenn sie falsch ist. Wir können die Funktion verschachteln, um alternative Bedingungen zu prüfen. Zum Beispiel wollen wir die Heizgradtage bestimmen (Mittlere Temperatur < 15°C) und die Kühlgradtage (Mittlere Temperatur > 22°C).

In [39]:
wetter1D["HeizKuehlTage"] = np.where(wetter1D.TMK > 22, "Kühlgradtag", np.where(wetter1D.TMK < 15, "Heizgradtag", "Normaltag"))
wetter1D["HeizKuehlTage"].value_counts(dropna=False)
Out[39]:
HeizKuehlTage
Heizgradtag    5611
Normaltag      1559
Kühlgradtag      43
Name: count, dtype: int64

Es ist zu beachten, dass die logische Bedingung in np.where wieder NA Werte auf False mapt. Deshalb sollte man das wieder gezielt behandeln:

In [40]:
wetter1D["HeizKuehlTage"] = np.where(wetter1D.TMK.isna(), pd.NA, np.where(wetter1D.TMK > 22, "Kühlgradtag", np.where(wetter1D.TMK < 15, "Heizgradtag", "Normaltag")))
wetter1D["HeizKuehlTage"].value_counts(dropna=False)
Out[40]:
HeizKuehlTage
Heizgradtag    5611
Normaltag      1558
Kühlgradtag      43
<NA>              1
Name: count, dtype: int64

Mit der map Funktion lässt sich das auch realisieren:

In [41]:
wetter1D["HeizKuehlTage"] = wetter1D.TMK.map(lambda x: "Heizgradtag" if x < 15 else "Kühlgradtag" if x > 22 else "Normaltag", na_action='ignore')

Ersetzen ausgewählter Werte im Dataframe¶

Häufig ist es notwendig bestimmte Werte gezielt zu ersetzen, auf Basis einer logischen Bedingung zu modifizieren. Dies kann man indem man den .loc-Index eines Dataframes nutzt. Das kann man auch nutzen, um neue Spalten zu berechnen. Wir erstellen eine neue leere Spalte und passen sie anschließend nach unseren Bedürfnissen an. Beachten Sie, dass dies einen gemischten Indexierungsansatz erfordert. Wir werden die Zeilen anhand eines logischen Vektors (logische Bedingung) und die Spalten anhand ihres Namens indizieren. Die Spalte QN_3 im Datensatz zum Beispiel codiert die Datenqualität, mit den folgenden kategorischen Werten.

In [42]:
wetter1D["QNS_4"] = pd.NA
wetter1D.loc[wetter1D.QN_4 ==  1, "QNS_4"] = "nur formale Prüfung"
wetter1D.loc[wetter1D.QN_4 ==  2, "QNS_4"] = "nach individuellen Kriterien geprüft"
wetter1D.loc[wetter1D.QN_4 ==  3, "QNS_4"] = "automatische Prüfung und Korrektur"
wetter1D.loc[wetter1D.QN_4 ==  5, "QNS_4"] = "historische, subjektive Verfahren"
wetter1D.loc[wetter1D.QN_4 ==  7, "QNS_4"] = "geprüft, gepflegt, nicht korrigiert"
wetter1D.loc[wetter1D.QN_4 ==  8, "QNS_4"] = "Qualitätsicherung ausserhalb ROUTINE"
wetter1D.loc[wetter1D.QN_4 ==  9, "QNS_4"] = "nicht alle Parameter korrigiert"
wetter1D.loc[wetter1D.QN_4 == 10, "QNS_4"] = "Qualitätsprüfung und Korrektur beendet"

wetter1D["QNS_4"].value_counts(dropna=False)
Out[42]:
QNS_4
historische, subjektive Verfahren       7202
automatische Prüfung und Korrektur         3
nur formale Prüfung                        2
nach individuellen Kriterien geprüft       2
Qualitätsicherung ausserhalb ROUTINE       2
<NA>                                       1
nicht alle Parameter korrigiert            1
Name: count, dtype: int64

Für solche komplexeren Mappings kann man auch die map Funktion verwenden und in dieser eine speziell definierte Mappingfunktion.

In [43]:
def QN_Mapping(x):
    if x == 1: return "nur formale Prüfung"
    if x == 2: return "nach individuellen Kriterien geprüft"
    if x == 3: return "automatische Prüfung und Korrektur"
    if x == 5: return "historische, subjektive Verfahren"
    if x == 7: return "geprüft, gepflegt, nicht korrigiert"
    if x == 8: return "Qualitätsicherung ausserhalb ROUTINE"
    if x == 9: return "nicht alle Parameter korrigiert"
    if x == 10: return "Qualitätsprüfung und Korrektur beendet"
    return pd.NA

wetter1D["QNS_4"] = wetter1D.QN_4.map(QN_Mapping, na_action='ignore')

Konvertierung kategorischer Variablen in Dummy-Variablen¶

Im vorherigen Abschnitt haben wir uns damit beschäftigt, Variablen in Kategorien umzuwandeln. In vielen Fällen (z.B. beim Einfügen kategorischer Variablen in statistische Modelle) müssen wir jedoch das Gegenteil tun - Kategorien in Vektoren von Zahlen umwandeln.

In [44]:
QN_Dummies = pd.get_dummies(wetter1D.QNS_4, prefix="QN")
QN_Dummies
Out[44]:
QN_Qualitätsicherung ausserhalb ROUTINE QN_automatische Prüfung und Korrektur QN_historische, subjektive Verfahren QN_nach individuellen Kriterien geprüft QN_nicht alle Parameter korrigiert QN_nur formale Prüfung
0 False False True False False False
1 False False True False False False
2 False True False False False False
3 False True False False False False
4 False False True False False False
... ... ... ... ... ... ...
7208 False False True False False False
7209 False False True False False False
7210 False False True False False False
7211 False False True False False False
7212 False False True False False False

7213 rows × 6 columns

Um Kategorien in numerische Werte umzuwandeln gibt es die Funktion pd.factorize. Hier wird eine numerische Serie erstellt der die Kategorien als Index angehängt werden, ähnlich der originalen numerischen Kodierung.

In [45]:
QN_Factors_Values, QN_Factors_Mapping = pd.factorize(wetter1D.QNS_4)
QN_Factors_Values, QN_Factors_Mapping
Out[45]:
(array([0, 0, 1, ..., 0, 0, 0], dtype=int64),
 Index(['historische, subjektive Verfahren',
        'automatische Prüfung und Korrektur', 'nur formale Prüfung',
        'nach individuellen Kriterien geprüft',
        'Qualitätsicherung ausserhalb ROUTINE',
        'nicht alle Parameter korrigiert'],
       dtype='object'))

Zu beachten ist, dass die Funktion ein Tupel mit den numerischen Werten und dem Mapping zurück gibt. Wir können die Werte als neue Spalte dem Dataframe hinzufügen

In [46]:
wetter1D["QNF_4"] = QN_Factors_Values

Kombinieren von Daten in Dataframes¶

Eine häufige Aufgabe besteht darin, einen Dataframe aus Serien zu erstellen, neue Spalten zu einem Dataframe hinzuzufügen und Dataframes zu einem breiteren Dataframe zu kombinieren. Einige der Aufgaben können entweder durch Erstellen einer neuen Variablen oder alternativ durch Erstellen eines Dataframes aus vorhandenen Serien erreicht werden. Dieser Ansatz scheitert jedoch, wenn Sie Dataframes anstelle von einzelnen Serien kombinieren möchten. Im Folgenden diskutieren wir pd.concat, ein Tool, das explizit für diese Aufgabe entwickelt wurde.

Verknüpfen von Daten mit pd.concat¶

pd.concat kann Serien und Dataframes auf verschiedene Weise kombinieren. Lassen Sie uns dies anhand einiger Beispiele demonstrieren.

So wollen wir zum Beispiel unseren originalen Wetter Dataframe, um die erstellten Spalten im QN_Dummies erweitern.

In [47]:
wetter_ext = pd.concat((wetter1D, QN_Dummies), axis=1)
wetter_ext
Out[47]:
DATUM_DT STATIONS_ID MESS_DATUM QN_3 FX FM QN_4 RSK RSKF SDK ... TemperaturKlasse HeizKuehlTage QNS_4 QNF_4 QN_Qualitätsicherung ausserhalb ROUTINE QN_automatische Prüfung und Korrektur QN_historische, subjektive Verfahren QN_nach individuellen Kriterien geprüft QN_nicht alle Parameter korrigiert QN_nur formale Prüfung
0 1947-01-01 4271.0 19470101.0 NaN NaN NaN 5.0 NaN NaN NaN ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False
1 1947-01-02 4271.0 19470101.0 NaN NaN NaN 5.0 NaN NaN NaN ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False
2 1947-01-03 4271.0 19470103.0 NaN NaN NaN 3.0 NaN NaN NaN ... Cold Heizgradtag automatische Prüfung und Korrektur 1 False True False False False False
3 1947-01-04 4271.0 19470103.0 NaN NaN NaN 3.0 NaN NaN NaN ... Cold Heizgradtag automatische Prüfung und Korrektur 1 False True False False False False
4 1947-01-05 4271.0 19470105.0 NaN NaN NaN 5.0 NaN NaN NaN ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
7208 1966-09-26 4271.0 19660926.0 5.0 NaN 6.6 5.0 0.3 1.0 2.2 ... Cool Heizgradtag historische, subjektive Verfahren 0 False False True False False False
7209 1966-09-27 4271.0 19660927.0 5.0 NaN 10.0 5.0 0.0 1.0 6.1 ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False
7210 1966-09-28 4271.0 19660928.0 5.0 NaN 4.5 5.0 0.0 0.0 9.9 ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False
7211 1966-09-29 4271.0 19660929.0 5.0 NaN 3.3 5.0 0.0 0.0 7.8 ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False
7212 1966-09-30 4271.0 19660930.0 5.0 NaN 3.5 5.0 0.0 0.0 7.8 ... Cold Heizgradtag historische, subjektive Verfahren 0 False False True False False False

7213 rows × 30 columns

Dieses Beispiel zeigt die wichtigsten Argumente von pd.concat. Das erste Argument ist ein beliebig langes Tuple von Daten (Serien oder Dataframes), die wir zusammenführen möchten (alternativ kann auch eine Liste verwendet werden). Wir setzen den Parameter axis=1, um anzugeben, dass wir die Serien spaltenweise zusammenführen möchten, nicht zeilenweise.

Wenn die Dataframes kompatibel sind, können wir sie auch zeilenweise kombinieren, indem wir axis=0 angeben.

Zum Beispiel wollen wir unseren Wetterdatensatz um die Werte nach 1960 erweitern.

In [48]:
wetter_ab_1960 = pd.read_csv("../data/Wetter/warnemuende_ab_1960.csv", sep=';', na_values=['-999','NA'])
wetter_ab_1960["DATUM_DT"] = pd.to_datetime(wetter_ab_1960.MESS_DATUM.astype(str), format="%Y%m%d")
wetter_ab_1960
Out[48]:
STATIONS_ID MESS_DATUM QN_3 FX FM QN_4 RSK RSKF SDK SHK_TAG NM VPM PM TMK UPM TXK TNK TGK eor DATUM_DT
0 4271 19600101 5.0 NaN 4.1 5 1.4 1.0 0.000 0.0 7.8 8.9 1003.40 7.0 90.00 7.9 4.9 4.1 eor 1960-01-01
1 4271 19600102 5.0 NaN 2.4 5 1.4 1.0 0.000 0.0 8.0 9.7 1004.80 6.8 97.00 8.0 6.3 5.2 eor 1960-01-02
2 4271 19600103 5.0 NaN 1.7 5 0.1 1.0 0.000 0.0 7.8 8.8 1014.20 5.2 99.00 6.7 4.8 4.0 eor 1960-01-03
3 4271 19600104 5.0 NaN 2.5 5 0.0 1.0 0.000 0.0 8.0 8.3 1027.10 4.3 98.00 5.3 3.3 3.4 eor 1960-01-04
4 4271 19600105 5.0 NaN 7.1 5 1.6 1.0 0.000 0.0 7.8 9.1 1016.00 5.9 95.00 9.3 3.8 3.5 eor 1960-01-05
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
23439 4271 20240304 1.0 11.0 3.1 1 0.0 0.0 8.667 0.0 3.9 8.3 1010.80 7.1 81.54 12.0 3.1 1.8 eor 2024-03-04
23440 4271 20240305 1.0 10.2 3.5 1 0.0 6.0 1.183 0.0 5.7 6.6 1017.63 3.7 82.50 6.0 2.1 0.7 eor 2024-03-05
23441 4271 20240306 1.0 8.1 2.7 1 0.0 0.0 1.383 0.0 7.7 7.1 1024.36 4.2 85.96 6.2 2.8 2.4 eor 2024-03-06
23442 4271 20240307 1.0 7.4 2.7 1 0.0 0.0 7.783 0.0 3.1 6.1 1026.72 4.4 74.04 9.5 0.6 -1.1 eor 2024-03-07
23443 4271 20240308 1.0 8.2 2.7 1 0.0 0.0 5.667 0.0 2.8 6.1 1020.17 2.5 84.29 8.9 -0.4 -3.1 eor 2024-03-08

23444 rows × 20 columns

Es ist wichtig zu erkennen, dass beim horizontalen Verketten von Daten pd.concat die Beobachtungen nicht zeilenweise, sondern nach Index kombiniert. Wenn wir beispielsweise versuchen beide Dataframes direkt zu kombinieren so wird das nicht gut ausgehen:

In [49]:
wetter_all=pd.concat((wetter1D, wetter_ab_1960), axis=0)
wetter_all
Out[49]:
DATUM_DT STATIONS_ID MESS_DATUM QN_3 FX FM QN_4 RSK RSKF SDK ... TMK UPM TXK TNK TGK eor TemperaturKlasse HeizKuehlTage QNS_4 QNF_4
0 1947-01-01 4271.0 19470101.0 NaN NaN NaN 5.0 NaN NaN NaN ... -2.1 78.00 -1.5 -3.3 -5.8 eor Cold Heizgradtag historische, subjektive Verfahren 0.0
1 1947-01-02 4271.0 19470101.0 NaN NaN NaN 5.0 NaN NaN NaN ... -2.1 78.00 -1.5 -3.3 -5.8 eor Cold Heizgradtag historische, subjektive Verfahren 0.0
2 1947-01-03 4271.0 19470103.0 NaN NaN NaN 3.0 NaN NaN NaN ... -4.8 80.00 -2.4 -6.2 -4.5 eor Cold Heizgradtag automatische Prüfung und Korrektur 1.0
3 1947-01-04 4271.0 19470103.0 NaN NaN NaN 3.0 NaN NaN NaN ... -4.8 80.00 -2.4 -6.2 -4.5 eor Cold Heizgradtag automatische Prüfung und Korrektur 1.0
4 1947-01-05 4271.0 19470105.0 NaN NaN NaN 5.0 NaN NaN NaN ... -12.9 79.00 -9.4 -14.0 -16.0 eor Cold Heizgradtag historische, subjektive Verfahren 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
23439 2024-03-04 4271.0 20240304.0 1.0 11.0 3.1 1.0 0.0 0.0 8.667 ... 7.1 81.54 12.0 3.1 1.8 eor NaN NaN NaN NaN
23440 2024-03-05 4271.0 20240305.0 1.0 10.2 3.5 1.0 0.0 6.0 1.183 ... 3.7 82.50 6.0 2.1 0.7 eor NaN NaN NaN NaN
23441 2024-03-06 4271.0 20240306.0 1.0 8.1 2.7 1.0 0.0 0.0 1.383 ... 4.2 85.96 6.2 2.8 2.4 eor NaN NaN NaN NaN
23442 2024-03-07 4271.0 20240307.0 1.0 7.4 2.7 1.0 0.0 0.0 7.783 ... 4.4 74.04 9.5 0.6 -1.1 eor NaN NaN NaN NaN
23443 2024-03-08 4271.0 20240308.0 1.0 8.2 2.7 1.0 0.0 0.0 5.667 ... 2.5 84.29 8.9 -0.4 -3.1 eor NaN NaN NaN NaN

30657 rows × 24 columns

Obwohl beide Dataframes eine unterschiedliche Anzahl an Spalten haben (Es fehlen bei wetter_ab_1960 die Spalten, die wir hinzugefügt haben), kann concat beide Dataframes kombinieren. Hierbei werden fehlende Spalten mit np.NaN aufgefüllt. Deshalb ist es sinnvoll erst zusammenzufügen, bevor man abgeleitete Werte berechnet. Dies können wir korrigieren, indem wir die fehlenden Werte neu berechnen.

In [50]:
wetter_all["TemperaturKlasse"]=pd.cut(wetter_all.TMK, bins = [-np.inf, 13, 18, 24, 30, np.inf], labels = ["Cold", "Cool", "Mild", "Warm", "Hot"], right=False)
wetter_all["HeizKuehlTage"] = wetter_all.TMK.map(lambda x: "Heizgradtag" if x < 15 else "Kühlgradtag" if x > 22 else "Normaltag", na_action='ignore')
wetter_all["QNS_4"]    = wetter_all.QN_4.map(QN_Mapping, na_action='ignore')
wetter_all["QNF_4"], _ = pd.factorize(wetter_all.QNS_4)

Dataframe kombinieren¶

Meist will man nicht nur Daten kombinieren, die nicht gleich groß sind oder den gleichen Index haben. Zum Beispiel wollen wir die Wetterdaten mit den Energiedaten der Universität Rostock kombinieren, um diese später zu analysieren.

In [51]:
uros_egy=pd.read_csv("../data/UROS/Energy1D_kW.csv", parse_dates=["Date"])
uros_egy.head()
Out[51]:
Date EV_HT_740 EV_NT_740 E_AV_Lab E_SV_Lab ES_Lab
0 2020-12-30 NaN NaN NaN NaN NaN
1 2020-12-31 NaN NaN 1256.0 291.0 5.0
2 2021-01-01 0.0 4080.0 1221.0 290.0 1.0
3 2021-01-02 1170.0 2630.0 1243.0 284.0 2.0
4 2021-01-03 0.0 3750.0 1222.0 283.0 2.0

Die Daten können wir mit der Methode pd.merge() kombinieren, die wie ein SQL Join funktioniert. Hierbei können wir die Spalten die gematcht werden sollen mit left_on und right_on spezifizieren (oder on wenn die Spalten gleich sind). Mit how="left" spezifizieren wir, dass nur die Zeilen genommen werden sollen, die im Energiedatensatz vorhanden sind.

In [52]:
uros_egy_weater=pd.merge(uros_egy, wetter_all, right_on="DATUM_DT", left_on="Date", how="left")
uros_egy_weater.head()
Out[52]:
Date EV_HT_740 EV_NT_740 E_AV_Lab E_SV_Lab ES_Lab DATUM_DT STATIONS_ID MESS_DATUM QN_3 ... TMK UPM TXK TNK TGK eor TemperaturKlasse HeizKuehlTage QNS_4 QNF_4
0 2020-12-30 NaN NaN NaN NaN NaN 2020-12-30 4271.0 20201230.0 10.0 ... 4.0 81.0 4.6 2.9 2.2 eor Cold Heizgradtag nicht alle Parameter korrigiert 5
1 2020-12-31 NaN NaN 1256.0 291.0 5.0 2020-12-31 4271.0 20201231.0 10.0 ... 3.4 83.0 4.4 2.3 0.9 eor Cold Heizgradtag nicht alle Parameter korrigiert 5
2 2021-01-01 0.0 4080.0 1221.0 290.0 1.0 2021-01-01 4271.0 20210101.0 10.0 ... 2.0 90.0 3.0 1.1 0.3 eor Cold Heizgradtag nicht alle Parameter korrigiert 5
3 2021-01-02 1170.0 2630.0 1243.0 284.0 2.0 2021-01-02 4271.0 20210102.0 10.0 ... 3.3 95.0 4.0 2.6 2.0 eor Cold Heizgradtag nicht alle Parameter korrigiert 5
4 2021-01-03 0.0 3750.0 1222.0 283.0 2.0 2021-01-03 4271.0 20210103.0 10.0 ... 3.5 81.0 4.6 2.4 0.8 eor Cold Heizgradtag nicht alle Parameter korrigiert 5

5 rows × 30 columns

Duplikate entfernen¶

Nachdem wir beide Datensätze zusammengefügt haben, ergibt sich das neue Problem, duplizierter Daten. Das kann in Datensätzen vorkommen, so dass es immer sinnvoll ist, auf Duplikate zu prüfen. Hierfür gibt es die Funktion duplicated. Sie gibt einen boolesche Serie zurück, die Wahr ist für Zeilen, wo alle Werte doppelt vorkommen.

In [53]:
sum(wetter_all.duplicated())
Out[53]:
2465

Das ist nur sinvoll, wenn sich Zeilen vollständig wiederholen. In unserm Fall ist das Datum, der eigentliche Index und es ist empfohlen duplicated auf diesen anzuwenden. Wenn wir unseren zusammengefügte Dataframe prüfen, so sehen wir, dass wir einige Duplikate haben, d.h. Daten für die mehr als eine Messung existiert.

In [54]:
sum(wetter_all.duplicated(subset=['DATUM_DT']))
Out[54]:
2465

Um diese herauszufiltern können wir die Funktion drop_duplicates nutzen, die ähnliche Parameter verwendet.

In [55]:
wetter_all.drop_duplicates(subset=['DATUM_DT'], inplace=True)
wetter_all.shape
Out[55]:
(28192, 24)

Damit haben wir jetzt einen gereinigten Dataframe, den wir uns für die weitere Verwendung abspeichern.

In [56]:
wetter_all.to_csv("../data/Wetter/warnemuende_clean.csv", sep=';', index=False)
uros_egy_weater.to_csv("../data/UROS/Energy1D_weather_clean.csv", index=False)

Dataframes Transformieren¶

Bisher haben wir mit einem Datensatz in Spaltendarstellung gearbeitet. Das ist für uns Menschen eine gewohnte tabellarische Darstellung. Für viele Berechnungen und Visualisierungen, ist das allerdings nicht immer das beste Format. Da braucht man häufig die Daten in einer Zeilendarstellung.

Dataframe Format in Spalten und Zeilendarstellung

In [ ]:
wetter_melted = wetter_all.melt(id_vars=['DATUM_DT', "TemperaturKlasse"], value_vars=["TXK","TMK","TNK"])
wetter_melted
Out[ ]:
DATUM_DT TemperaturKlasse variable value
0 1947-01-01 Cold TXK -1.5
1 1947-01-02 Cold TXK -1.5
2 1947-01-03 Cold TXK -2.4
3 1947-01-04 Cold TXK -2.4
4 1947-01-05 Cold TXK -9.4
... ... ... ... ...
84571 2024-03-04 Cold TNK 3.1
84572 2024-03-05 Cold TNK 2.1
84573 2024-03-06 Cold TNK 2.8
84574 2024-03-07 Cold TNK 0.6
84575 2024-03-08 Cold TNK -0.4

84576 rows × 4 columns

Aus der Zeilendarstellung könnten wir jetzt ein einzelnes ML-Modell bauen, was den Zusammenhang zwischen der Temperaturklasse und den Werten in value lernt und variable nur als Unterscheidungskriterium nutzt. Alternativ können wir damit auch die Zeitreihen einfacher darstellen. So lässt sich damit ein sehr übersichtliches Diagramm zum Vergleich aller Zeitreihen erstellen. Im nächsten Abschnitt Visuelle Datenanalyse gehen wir detaillierte darauf ein.

In [58]:
import plotly.express as px

px.line(wetter_melted, x="DATUM_DT", y="value", facet_row ="variable")

Diese Zeilendarstellung wird auch oft bei komplexen Dataframes mit vielen kategorischen Variablen benutzt. So gibt es viele Datensätze des Statistischen Bundesamtes auf dem GENESIS-Portal im CSV-Flat format, was deutlich einfacher zu verarbeiten ist. Allerdings muss dann der Datensatz aus der Reihendarstellung in eine Spaltendarstellung überführt werden.

Hierfür bietet Pandas die Methode pivot an. Jeder, der sich gewundert hat, wofür man die Pivot-Darstellung in Excel nutzt, hat jetzt eine Antwort. Ähnlich zur melt-Methode, müssen wir bei der pivot-Methode die Spalten identifizieren, welche die Werte values enthält, die Spalte(n) columns welche in neue Spaltennamen umzuwandeln sind und die beizubehaltenden Indexe index.

In [59]:
wetter_hardened = wetter_melted.pivot(columns='variable', index=['DATUM_DT', "TemperaturKlasse"], values='value')
wetter_hardened
Out[59]:
variable TMK TNK TXK
DATUM_DT TemperaturKlasse
1947-01-01 Cold -2.1 -3.3 -1.5
1947-01-02 Cold -2.1 -3.3 -1.5
1947-01-03 Cold -4.8 -6.2 -2.4
1947-01-04 Cold -4.8 -6.2 -2.4
1947-01-05 Cold -12.9 -14.0 -9.4
... ... ... ... ...
2024-03-04 Cold 7.1 3.1 12.0
2024-03-05 Cold 3.7 2.1 6.0
2024-03-06 Cold 4.2 2.8 6.2
2024-03-07 Cold 4.4 0.6 9.5
2024-03-08 Cold 2.5 -0.4 8.9

28192 rows × 3 columns

Mit der bekannten Methode reset_index() wandeln wir den Index in Spalten um:

In [60]:
wetter_hardened.reset_index()
Out[60]:
variable DATUM_DT TemperaturKlasse TMK TNK TXK
0 1947-01-01 Cold -2.1 -3.3 -1.5
1 1947-01-02 Cold -2.1 -3.3 -1.5
2 1947-01-03 Cold -4.8 -6.2 -2.4
3 1947-01-04 Cold -4.8 -6.2 -2.4
4 1947-01-05 Cold -12.9 -14.0 -9.4
... ... ... ... ... ...
28187 2024-03-04 Cold 7.1 3.1 12.0
28188 2024-03-05 Cold 3.7 2.1 6.0
28189 2024-03-06 Cold 4.2 2.8 6.2
28190 2024-03-07 Cold 4.4 0.6 9.5
28191 2024-03-08 Cold 2.5 -0.4 8.9

28192 rows × 5 columns

f  r  a  g  e  n  ?