11. Datenbereinigung#
Im Kapitel zur Phase Data Understanding haben wir am Beispiel der Eisverkäufe eines Supermarkts eine Reihe typischer Datenqualitätsprobleme gesehen: ein falscher Wochentag, doppelte und fehlende Einträge, eine uneinheitliche Kodierung, ein negativer Umsatz und ein extremer Ausreißer. Dort ging es darum, solche Probleme zu erkennen. In diesem Kapitel beheben wir sie.
Datenbereinigung ist selten spektakulär, nimmt in realen Projekten aber einen großen Teil der Zeit ein. Sie wirkt sich direkt auf alles aus, was danach kommt: Die meisten Algorithmen in scikit-learn brechen bei fehlenden Werten mit einer Fehlermeldung ab, und ein einzelner Ausreißer kann eine lineare Regression spürbar verzerren.
Drei Grundsätze begleiten uns durch das Kapitel:
Die Rohdaten bleiben unverändert. Wir lesen sie ein, bereinigen im Code und speichern das Ergebnis in eine neue Datei. So bleibt jeder Schritt nachvollziehbar und lässt sich später anders entscheiden.
Jede Korrektur ist eine Entscheidung. Ob ein negativer Umsatz ein Erfassungsfehler oder eine Retoure ist, steht nicht in den Daten. Das kann nur der Fachbereich beantworten. Deshalb halten wir fest, was wir warum geändert haben.
Erst verstehen, dann korrigieren. Ein Wert, der auffällig aussieht, ist nicht automatisch falsch.
Wir verwenden den simulierten Datensatz über Eisverkäufe aus dem Kapitel Data Understanding, diesmal als CSV-Datei, wie sie aus einem Kassensystem exportiert worden sein könnte. Die Übungen am Ende greifen wieder auf die Daten über weltweite Systeme des öffentlichen Nahverkehrs von https://www.citylines.co zurück.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
11.1. Erster Überblick#
Beim Einlesen geben wir mit parse_dates an, dass die Spalte date Kalenderdaten enthält, und setzen sie mit index_col direkt als Index. Ohne parse_dates würde pandas die Daten als Text einlesen. Mehr zum Umgang mit Datumswerten folgt im Kapitel über Zeitreihen.
ice = pd.read_csv('data/ice_sales_raw.csv', parse_dates=['date'], index_col='date')
ice.head()
| dayname | temperature | ice_sales_eur | |
|---|---|---|---|
| date | |||
| 2021-01-01 | Friday | 0.0 | 403.01 |
| 2021-01-02 | Saturday | 0.0 | -400.32 |
| 2021-01-04 | Tuesday | 0.0 | 410.21 |
| 2021-01-05 | Tuesday | 0.0 | 390.94 |
| 2021-01-06 | Wednesday | 0.0 | 400.85 |
Die Methode info ist ein guter erster Schritt bei jedem neuen Datensatz. Sie zeigt die Anzahl der Zeilen, den Datentyp jeder Spalte und wie viele Werte pro Spalte vorhanden sind:
ice.info()
<class 'pandas.DataFrame'>
DatetimeIndex: 313 entries, 2021-01-01 to 2021-12-31
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 dayname 313 non-null str
1 temperature 312 non-null float64
2 ice_sales_eur 312 non-null float64
dtypes: float64(2), str(1)
memory usage: 9.8 KB
Wir haben 313 Einträge, aber bei temperature und ice_sales_eur jeweils nur 312 Werte. Es fehlt also mindestens je ein Wert. Die Datentypen passen: Zahlen wurden als float64 erkannt, der Wochentag als Text. Das ist keineswegs selbstverständlich. Steht in einer Zahlenspalte auch nur ein einziger Text wie "n/a" oder "k.A.", liest pandas die ganze Spalte als Text ein. Ein unerwarteter Datentyp ist deshalb oft der erste Hinweis auf ein Qualitätsproblem.
Die Methode describe kennen wir schon aus der Datenexploration:
ice.describe()
| temperature | ice_sales_eur | |
|---|---|---|
| count | 312.000000 | 312.000000 |
| mean | 15.047756 | 1168.698269 |
| std | 11.436156 | 1779.304736 |
| min | -99.000000 | -400.320000 |
| 25% | 5.200000 | 646.302500 |
| 50% | 17.050000 | 1111.705000 |
| 75% | 23.900000 | 1495.770000 |
| max | 28.300000 | 31432.250000 |
Schon hier fallen drei Dinge auf, auf die wir später zurückkommen: Die minimale Temperatur liegt bei -99 Grad, was für einen Supermarkt in Deutschland nicht plausibel ist. Der kleinste Umsatz ist negativ. Und der größte Umsatz ist mit über 31.000 Euro fast 30-mal so hoch wie der Median.
11.2. Duplikate#
Die Methode duplicated markiert jede Zeile, die mit einer früheren Zeile in allen Spalten übereinstimmt. Mit keep=False werden alle Vorkommen markiert, nicht nur die Wiederholungen. So sehen wir die betroffenen Zeilen nebeneinander:
ice[ice.duplicated(keep=False)]
| dayname | temperature | ice_sales_eur | |
|---|---|---|---|
| date | |||
| 2021-12-31 | Fri | 5.2 | 590.21 |
| 2021-12-31 | Fri | 5.2 | 590.21 |
Hier ist Vorsicht geboten: duplicated vergleicht nur die Spalten, den Index aber nicht. Zwei Tage mit zufällig gleicher Temperatur und gleichem Umsatz würden als Duplikat erkannt, zwei Einträge für denselben Tag mit unterschiedlichen Werten dagegen nicht. Entscheidend ist die Frage, was eine Zeile darstellt. In unserem Datensatz beschreibt jede Zeile einen Verkaufstag, das Datum muss also eindeutig sein. Das prüfen wir direkt am Index:
ice[ice.index.duplicated(keep=False)]
| dayname | temperature | ice_sales_eur | |
|---|---|---|---|
| date | |||
| 2021-12-31 | Fri | 5.2 | 590.21 |
| 2021-12-31 | Fri | 5.2 | 590.21 |
In unserem Fall liefern beide Prüfungen dasselbe Ergebnis, den 31.12.2021. Wir behalten den ersten Eintrag:
ice = ice[~ice.index.duplicated(keep='first')]
len(ice)
312
Nicht jede doppelte Zeile ist ein Fehler. In einer Tabelle mit Kassenbons können zwei identische Einkäufe völlig korrekt sein, wenn jemand zweimal dasselbe kauft. Bei den Liniennetzen von citylines kann es die Linie “S1” in einer Stadt mehrfach geben, etwa mit einem historischen und einem aktuellen Verlauf. Ob Duplikate entfernt werden, hängt also davon ab, was eine Zeile bedeutet und welche Spalten sie eindeutig identifizieren.
11.3. Fehlende Werte#
Platzhalter erkennen#
Fehlende Werte sind nicht immer als NaN gekennzeichnet. Viele Systeme verwenden Platzhalter wie -99, 0, 9999, einen leeren Text oder "n/a". Für pandas sind das ganz normale Werte, die in jede Berechnung einfließen. Die -99 Grad aus der Übersicht sind ein solcher Platzhalter. Wir ersetzen sie durch np.nan:
ice['temperature'] = ice['temperature'].replace(-99, np.nan)
Kennt man die Platzhalter schon vorher, kann man sie auch direkt beim Einlesen angeben, z.B. mit pd.read_csv(..., na_values={'temperature': [-99]}).
Einen Sonderfall haben wir bereits im Kapitel Data Understanding gesehen: In den ersten Wochen des Jahres liegt die Temperatur auffällig oft bei genau 0,0 Grad.
zero_days = ice[ice['temperature'] == 0.0]
print(f'{len(zero_days)} Tage mit exakt 0,0 Grad zwischen {zero_days.index.min():%d.%m.} und {zero_days.index.max():%d.%m.}')
29 Tage mit exakt 0,0 Grad zwischen 01.01. und 27.02.
Das sieht nach einem systematischen Fehler aus: Minusgrade wurden offenbar auf 0 abgeschnitten. Die Werte fehlen nicht, sie sind aber unvollständig. Den tatsächlichen Wert können wir aus den vorhandenen Daten nicht rekonstruieren. Hier bleibt uns, das Problem zu dokumentieren und mit dem Fachbereich zu klären, ob es sich lohnt, die Temperaturen von einem Wetterdienst zu beschaffen. Für eine Prognose von Eisverkäufen dürfte der Unterschied zwischen 0 und -5 Grad allerdings kaum ins Gewicht fallen.
Fehlende Zeilen#
Mit isna finden wir nur fehlende Werte in vorhandenen Zeilen. Fehlt ein ganzer Tag, taucht er nirgends auf. Bei Zeitreihen können wir das prüfen, indem wir die vorhandenen Daten mit dem erwarteten Kalender vergleichen. Der Supermarkt hat an allen Tagen außer sonntags geöffnet:
expected_days = pd.date_range('2021-01-01', '2021-12-31', freq='D')
expected_days = expected_days[expected_days.weekday != 6]
expected_days.difference(ice.index)
DatetimeIndex(['2021-06-08'], dtype='datetime64[us]', freq=None)
Der 08.06.2021 fehlt. Mit reindex richten wir den DataFrame am erwarteten Kalender aus. Für Tage, die es bisher nicht gab, entsteht eine neue Zeile mit NaN-Werten:
ice = ice.reindex(expected_days)
ice.index.name = 'date'
len(ice)
313
Überblick verschaffen#
Jetzt sind alle fehlenden Werte als NaN sichtbar. isna liefert einen DataFrame mit True an jeder leeren Stelle. Mit sum zählen wir sie pro Spalte, mit any(axis=1) finden wir alle Zeilen, in denen mindestens ein Wert fehlt:
ice.isna().sum()
dayname 1
temperature 3
ice_sales_eur 2
dtype: int64
ice[ice.isna().any(axis=1)]
| dayname | temperature | ice_sales_eur | |
|---|---|---|---|
| date | |||
| 2021-03-15 | Monday | NaN | 733.7 |
| 2021-06-08 | NaN | NaN | NaN |
| 2021-06-09 | Wednesday | NaN | NaN |
Fehlende Werte behandeln#
Für den Umgang mit fehlenden Werten gibt es kein richtiges Standardverfahren, sondern verschiedene Möglichkeiten mit jeweils eigenen Nachteilen.
Am einfachsten ist es, die betroffenen Zeilen mit dropna zu entfernen. Bei wenigen fehlenden Werten ist das oft vertretbar. Es wird problematisch, wenn viele Zeilen betroffen sind oder wenn Werte nicht zufällig fehlen. Fällt die Kasse etwa bevorzugt an besonders vollen Tagen aus, fehlen gerade die umsatzstarken Tage, und das Modell unterschätzt die Nachfrage.
Ein konstanter Wert mit fillna ist nur sinnvoll, wenn seine Bedeutung eindeutig ist. Fehlt der Umsatz, weil die Filiale geschlossen war, ist 0 korrekt. Fehlt er, weil die Daten verloren gegangen sind, ist 0 falsch.
Häufig wird ein statistischer Ersatzwert wie Mittelwert oder Median verwendet. Die Zeile bleibt erhalten, allerdings wird die Streuung der Daten künstlich verringert. Und der Jahresmittelwert der Temperatur ist für einen Tag im Juni eine schlechte Schätzung. Besser ist es, den Ersatzwert innerhalb passender Gruppen zu berechnen, etwa pro Monat.
Bei Zeitreihen liegt es nahe, fehlende Werte aus den Nachbarwerten zu schätzen, entweder durch Übernahme des letzten Werts (ffill) oder durch Interpolation (interpolate). Für eine Größe wie die Temperatur, die sich von Tag zu Tag nur langsam ändert, ist das eine gute Wahl.
Manche Werte lassen sich schließlich aus anderen Spalten ableiten, wie der Wochentag aus dem Datum. Solche Werte sollte man nicht schätzen, sondern berechnen.
Unabhängig vom Verfahren kann es hilfreich sein, in einer zusätzlichen Spalte festzuhalten, welche Werte ersetzt wurden. Manchmal ist gerade die Tatsache, dass ein Wert fehlt, aussagekräftig.
Sobald wir später Modelle trainieren, dürfen Ersatzwerte wie Mittelwert oder Median nur aus den Trainingsdaten berechnet werden. Sonst fließen Informationen aus den Testdaten in das Training ein (vergleiche den Abschnitt zu Leakage im Kapitel Modeling des CRISP-DM-Prozesses). In scikit-learn übernimmt das der SimpleImputer innerhalb einer Pipeline.
In unserem Beispiel behandeln wir die beiden Spalten unterschiedlich. Die fehlenden Temperaturen interpolieren wir. Mit method='time' berücksichtigt die Interpolation die tatsächlichen Abstände zwischen den Tagen, also auch die fehlenden Sonntage:
ice['temperature_imputed'] = ice['temperature'].isna()
ice['temperature'] = ice['temperature'].interpolate(method='time')
ice.loc['2021-06-05':'2021-06-11']
| dayname | temperature | ice_sales_eur | temperature_imputed | |
|---|---|---|---|---|
| date | ||||
| 2021-06-05 | Saturday | 23.600000 | 1486.27 | False |
| 2021-06-07 | Monday | 23.100000 | 1451.51 | False |
| 2021-06-08 | NaN | 23.233333 | NaN | True |
| 2021-06-09 | Wednesday | 23.366667 | NaN | True |
| 2021-06-10 | Thursday | 23.500000 | 1462.45 | False |
| 2021-06-11 | Friday | 23.300000 | 1442.17 | False |
Beim Umsatz gehen wir anders vor. Er ist die Größe, die wir später vorhersagen wollen, also die Zielvariable. Einen geschätzten Wert als Zielvariable zu verwenden hieße, dem Modell unsere eigene Schätzung als Wahrheit beizubringen. Tage ohne Umsatz lassen wir deshalb vorerst als NaN stehen und entfernen sie am Ende der Bereinigung.
11.4. Uneinheitliche Kodierung#
Bei kategorischen Spalten verschafft value_counts einen schnellen Überblick über alle vorkommenden Werte:
ice['dayname'].value_counts()
dayname
Friday 52
Saturday 52
Tuesday 52
Wednesday 52
Thursday 52
Monday 51
Fri 1
Name: count, dtype: int64
Der Freitag ist einmal als “Fri” statt als “Friday” kodiert. Das ließe sich mit ice['dayname'].replace({'Fri': 'Friday'}) beheben. Der Wochentag ist aber eine abgeleitete Spalte, die sich aus dem Datum berechnen lässt. Ein Vergleich mit dem berechneten Wert findet auch Fehler, die value_counts nicht zeigt:
ice[ice['dayname'] != ice.index.day_name()]
| dayname | temperature | ice_sales_eur | temperature_imputed | |
|---|---|---|---|---|
| date | ||||
| 2021-01-04 | Tuesday | 0.000000 | 410.21 | False |
| 2021-06-08 | NaN | 23.233333 | NaN | True |
| 2021-12-31 | Fri | 5.200000 | 590.21 | False |
Neben dem “Fri” und der neu eingefügten Zeile für den 08.06. finden wir den 04.01.2021, der fälschlich als Dienstag eingetragen ist, obwohl es ein Montag war. Statt einzelne Werte zu reparieren, berechnen wir die ganze Spalte neu:
ice['dayname'] = ice.index.day_name()
In anderen Datensätzen sind uneinheitliche Kodierungen oft schwerer zu erkennen, z.B. unterschiedliche Groß- und Kleinschreibung, Leerzeichen am Ende oder Synonyme wie “S-Bahn”, “SBahn” und “Schnellbahn”. Mit den Methoden des str-Accessors wie strip und lower sowie einem Dictionary für replace lässt sich das meist in wenigen Zeilen vereinheitlichen. Wichtig ist, danach erneut mit value_counts zu prüfen.
11.5. Unplausible Werte und Ausreißer#
Hier unterscheiden wir zwei Fälle. Unplausible Werte widersprechen fachlichen Regeln, z.B. ein negatives Alter oder ein Produktionsdatum in der Zukunft. Ausreißer sind dagegen möglich, aber ungewöhnlich weit von den übrigen Werten entfernt.
Für Ausreißer haben wir bei den Boxplots im Kapitel Datenexploration eine gängige Faustregel kennengelernt: Als auffällig gilt, was mehr als das 1,5-fache des Interquartilsabstands (IQR) unter dem ersten oder über dem dritten Quartil liegt.
q1 = ice['ice_sales_eur'].quantile(0.25)
q3 = ice['ice_sales_eur'].quantile(0.75)
iqr = q3 - q1
lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
print(f'Unauffälliger Bereich: {lower:.2f} bis {upper:.2f} Euro')
ice[(ice['ice_sales_eur'] < lower) | (ice['ice_sales_eur'] > upper)]
Unauffälliger Bereich: -556.75 bis 2730.64 Euro
| dayname | temperature | ice_sales_eur | temperature_imputed | |
|---|---|---|---|---|
| date | ||||
| 2021-06-12 | Saturday | 23.7 | 31432.25 | False |
Die Regel findet den Umsatz von über 31.000 Euro am 12.06.2021, nicht aber den negativen Umsatz vom 02.01.2021. Die untere Grenze liegt ebenfalls im negativen Bereich. Statistische Regeln ersetzen also kein Fachwissen. Dass Tagesumsätze nicht negativ sein sollten, ist eine fachliche Regel, die wir explizit prüfen müssen:
ice[ice['ice_sales_eur'] < 0]
| dayname | temperature | ice_sales_eur | temperature_imputed | |
|---|---|---|---|---|
| date | ||||
| 2021-01-02 | Saturday | 0.0 | -400.32 | False |
Für beide Werte klären wir die Ursache mit dem Fachbereich. Nehmen wir an, der negative Wert ist eine Stornobuchung für einen falsch erfassten Betrag. Er beschreibt also keinen Verkauf, und wir setzen ihn auf NaN.
Beim Ausreißer ist die Lage weniger klar. Vielleicht war es ein Tippfehler bei einer manuellen Korrektur, vielleicht hat ein Eiscafé nebenan für ein Stadtfest eingekauft. Grundsätzlich stehen uns vier Möglichkeiten offen: den Wert behalten, ihn entfernen, ihn auf einen Grenzwert kappen (z.B. mit clip) oder ihn als fehlend markieren. Soll das Modell die normale Nachfrage vorhersagen, verzerrt ein einmaliger Großeinkauf das Ergebnis. Wir entfernen den Wert daher und halten die Entscheidung fest. Ginge es dagegen um die Planung von Lagerkapazitäten, wären gerade solche Spitzen interessant.
ice.loc[ice['ice_sales_eur'] < 0, 'ice_sales_eur'] = np.nan
ice.loc['2021-06-12', 'ice_sales_eur'] = np.nan
ice = ice.dropna(subset=['ice_sales_eur'])
ice.describe()
| temperature | ice_sales_eur | |
|---|---|---|
| count | 309.000000 | 309.000000 |
| mean | 15.447465 | 1077.707832 |
| std | 9.408788 | 453.576912 |
| min | 0.000000 | 372.410000 |
| 25% | 8.000000 | 687.040000 |
| 50% | 17.100000 | 1114.950000 |
| 75% | 23.900000 | 1493.670000 |
| max | 28.300000 | 1777.820000 |
Die IQR-Regel ist eine Heuristik, kein Urteil. Bei schiefen Verteilungen, wie wir sie bei den Längen der Liniennetze gesehen haben, markiert sie oft viele völlig korrekte Werte. Ausreißer sollten deshalb nie automatisch gelöscht werden, sondern immer einzeln oder anhand einer begründeten Regel.
11.6. Bereinigung reproduzierbar machen#
Bisher haben wir Schritt für Schritt in einzelnen Zellen gearbeitet. Das ist zum Erkunden gut, zum Wiederholen aber schlecht geeignet. Kommen nächsten Monat neue Daten hinzu oder wollen wir eine Entscheidung ändern, möchten wir die gesamte Bereinigung erneut ausführen, ohne Zellen in der richtigen Reihenfolge anklicken zu müssen. Deshalb fassen wir die Schritte in einer Funktion zusammen, die Rohdaten entgegennimmt und bereinigte Daten zurückgibt:
def clean_ice_sales(raw):
df = raw.copy()
# Ein Eintrag pro Tag: doppelte Tage entfernen
df = df[~df.index.duplicated(keep='first')]
# -99 ist ein Platzhalter für fehlende Temperaturmessungen
df['temperature'] = df['temperature'].replace(-99, np.nan)
# Fehlende Öffnungstage (alle außer Sonntag) ergänzen
days = pd.date_range(df.index.min(), df.index.max(), freq='D')
df = df.reindex(days[days.weekday != 6])
df.index.name = 'date'
# Temperatur zeitlich interpolieren, ersetzte Werte markieren
df['temperature_imputed'] = df['temperature'].isna()
df['temperature'] = df['temperature'].interpolate(method='time')
# Wochentag aus dem Datum berechnen statt Eingabe zu übernehmen
df['dayname'] = df.index.day_name()
# Stornobuchungen und einmaligen Großeinkauf entfernen (Rücksprache Fachbereich)
df.loc[df['ice_sales_eur'] < 0, 'ice_sales_eur'] = np.nan
df.loc[df.index == '2021-06-12', 'ice_sales_eur'] = np.nan
# Tage ohne Umsatz sind für die Prognose nicht verwendbar
return df.dropna(subset=['ice_sales_eur'])
raw = pd.read_csv('data/ice_sales_raw.csv', parse_dates=['date'], index_col='date')
ice_clean = clean_ice_sales(raw)
ice_clean.to_csv('data/ice_sales_clean.csv')
ice_clean.info()
<class 'pandas.DataFrame'>
DatetimeIndex: 309 entries, 2021-01-01 to 2021-12-31
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 dayname 309 non-null str
1 temperature 309 non-null float64
2 ice_sales_eur 309 non-null float64
3 temperature_imputed 309 non-null bool
dtypes: bool(1), float64(2), str(1)
memory usage: 10.0 KB
Die Kommentare in der Funktion beschreiben, was passiert. Für andere Projektbeteiligte ist aber vor allem wichtig, warum etwas passiert. Dafür eignet sich eine kurze Übersicht der Entscheidungen, die mit dem Fachbereich abgestimmt werden kann:
Problem |
Entscheidung |
Begründung |
|---|---|---|
31.12. doppelt |
Ersten Eintrag behalten |
Beide Einträge identisch, ein Eintrag pro Tag |
Temperatur -99 am 15.03. |
Als fehlend behandelt und interpoliert |
Platzhalter des Exportsystems |
Temperatur 0,0 im Januar/Februar |
Unverändert, dokumentiert |
Minusgrade abgeschnitten, nicht rekonstruierbar; geringer Einfluss erwartet |
08.06. fehlt, 09.06. ohne Werte |
Temperatur interpoliert, Tage ohne Umsatz entfernt |
Zielvariable wird nicht geschätzt |
Wochentag fehlerhaft/uneinheitlich |
Aus dem Datum neu berechnet |
Abgeleiteter Wert |
Negativer Umsatz am 02.01. |
Entfernt |
Stornobuchung, kein Verkauf |
Umsatz 31.432 Euro am 12.06. |
Entfernt |
Einmaliger Großeinkauf, nicht repräsentativ für normale Nachfrage |
11.7. Übungen#
Wir haben die fehlenden Temperaturen interpoliert. Vergleichen Sie diese Entscheidung mit zwei Alternativen:
- Lesen Sie die Rohdaten erneut ein, entfernen Sie das Duplikat, ersetzen Sie den Platzhalter -99 und ergänzen Sie die fehlenden Tage (wie in der Funktion
clean_ice_sales) - Berechnen Sie für die Tage mit fehlender Temperatur drei Ersatzwerte: den Jahresmittelwert, den Median des jeweiligen Monats und den interpolierten Wert. Für den Monatsmedian hilft Ihnen die Methode transform nach einer Gruppierung: sie liefert eine Series mit dem gleichen Index wie die ursprünglichen Daten
- Stellen Sie die drei Varianten in einem DataFrame gegenüber. Welche Variante halten Sie für am besten geeignet und warum?
#
Die Tabelle sections von citylines enthält für jeden Streckenabschnitt das Jahr der Eröffnung (opening) und der Stilllegung (closure). Im Kapitel Datentransformation werden wir diese Spalten verwenden, um nur aktuell befahrene Abschnitte zu berücksichtigen. Prüfen Sie vorher die Datenqualität:
- Laden Sie
data/sections.csv(mitindex_col='id') und bestimmen Sie die Anzahl fehlender Werte je Spalte - Untersuchen Sie mit
value_countsdie Werte vonopeningundclosure, die kleiner als 1800 oder größer als 2100 sind. Welche Platzhalter finden Sie? Was bedeuten sie vermutlich? Gibt es Werte, die eher nach einem Tippfehler aussehen? - Erstellen Sie eine bereinigte Kopie: Platzhalter werden einheitlich durch
NaNersetzt. Für Abschnitte, deren Stilllegungsjahr ein Platzhalter für "noch in Betrieb" ist, legen Sie eine zusätzliche Spaltein_operationan, damit diese Information nicht verloren geht - Halten Sie Ihre Entscheidungen in einer kurzen Tabelle fest wie im Beispiel oben. Welche Fragen würden Sie dem Anbieter der Daten stellen?
#
11.8. Abschluss#
Datenbereinigung lässt sich nicht vollständig automatisieren, weil fast jede Korrektur fachliches Wissen voraussetzt. Die Techniken aus diesem Kapitel helfen aber, Probleme systematisch zu finden und die Entscheidungen nachvollziehbar umzusetzen. Für Ihren eigenen Datensatz können Sie sich an folgenden Fragen orientieren:
Was stellt eine Zeile dar, und welche Spalten identifizieren sie eindeutig? Gibt es Duplikate?
Haben alle Spalten den erwarteten Datentyp? Wenn nicht, warum?
Wo fehlen Werte, und gibt es Platzhalter, die als normale Werte getarnt sind? Fehlen ganze Zeilen?
Sind kategorische Werte einheitlich kodiert?
Welche Werte widersprechen fachlichen Regeln, und welche sind statistisch auffällig?
Ist jede Entscheidung dokumentiert, und lässt sich die Bereinigung aus den Rohdaten heraus wiederholen?