Verwalten fehlender Daten mit pandas#

Fehlende Daten treten häufig bei Datenanalysen auf. pandas vereinfacht die Arbeit mit fehlenden Daten so weit wie möglich. Zum Beispiel schließen alle deskriptiven Statistiken von pandas-Objekten standardmäßig fehlende Daten aus. pandas verwendet für numerische Daten den Fließkommawert NaN (Not a Number), um fehlende Daten darzustellen.

In pandas wurde eine der Programmiersprache R entlehnte Konvention übernommen und fehlende Daten als NA bezeichnet, was für not available (engl.: nicht verfügbar) steht. In statistischen Anwendungen können NA-Daten entweder Daten sein, die nicht existieren oder die zwar existieren, aber nicht beobachtet wurden (z.B. durch Probleme bei der Datenerfassung). Auch das None-Objekt von Python wird in nicht-numerischen Arrays als NA behandelt.

Methoden zum Handling von NA-Objekten:

Argument

Beschreibung

dropna

filtert Achsenbeschriftungen auf der Grundlage, ob Werte für die einzelnen Label fehlende Daten aufweisen, wobei unterschiedliche Schwellenwerte für die zu tolerierende Menge fehlender Daten gelten.

fillna

füllt fehlende Daten mit einem Wert oder mit einer Interpolationsmethode wie ffill' oder bfill auf.

isna

gibt boolesche Werte zurück, die angeben, welche Werte fehlen/NA sind.

notna

negiert isna und gibt True für nicht-NA-Werte und False für NA-Werte zurück.

In diesem Notebook werden einige Möglichkeiten vorgestellt, wie fehlende Daten mit pandas DataFrames verwaltet werden können. Weitere Informationen findet ihr in der Pandas-Dokumentation: Working with missing data und Missing data cookbook.

Siehe auch:

[1]:
import pandas as pd
[2]:
df = pd.read_csv("https://raw.githubusercontent.com/kjam/data-cleaning-101/master/data/iot_example_with_nulls.csv")

1. Überprüfen der Daten#

Bei der Bereinigung von Daten für die Analyse ist es oft wichtig, die fehlenden Daten selbst zu analysieren, um Probleme bei der Datenerfassung oder potenzielle Verzerrungen in den Daten aufgrund der fehlenden Daten zu ermitteln. Zunächst lassen wir uns die ersten 20 Datensätze anzeigen:

[3]:
df.head(20)
[3]:
timestamp username temperature heartrate build latest note
0 2017-01-01T12:00:23 michaelsmith 12.0 67 4e6a7805-8faa-2768-6ef6-eb3198b483ac 0.0 interval
1 2017-01-01T12:01:09 kharrison 6.0 78 7256b7b0-e502-f576-62ec-ed73533c9c84 0.0 wake
2 2017-01-01T12:01:34 smithadam 5.0 89 9226c94b-bb4b-a6c8-8e02-cb42b53e9c90 0.0 NaN
3 2017-01-01T12:02:09 eddierodriguez 28.0 76 NaN 0.0 update
4 2017-01-01T12:02:36 kenneth94 29.0 62 122f1c6a-403c-2221-6ed1-b5caa08f11e0 NaN NaN
5 2017-01-01T12:03:04 bryanttodd 13.0 86 0897dbe5-9c5b-71ca-73a1-7586959ca198 0.0 interval
6 2017-01-01T12:03:51 andrea98 17.0 81 1c07ab9b-5f66-137d-a74f-921a41001f4e 1.0 NaN
7 2017-01-01T12:04:35 scott28 16.0 76 7a60219f-6621-e548-180e-ca69624f9824 NaN interval
8 2017-01-01T12:05:05 hillpamela 5.0 82 a8b87754-a162-da28-2527-4bce4b3d4191 1.0 NaN
9 2017-01-01T12:05:41 moorejeffrey 25.0 63 585f1a3c-0679-0ffe-9132-508933c70343 0.0 wake
10 2017-01-01T12:06:21 njohnson NaN 63 e09b6001-125d-51cf-9c3f-9cb686c19d02 NaN NaN
11 2017-01-01T12:06:53 gsutton 29.0 80 607c9f6e-2bdf-a606-6d16-3004c6958436 1.0 update
12 2017-01-01T12:07:41 jessica48 22.0 83 03e1a07b-3e14-412c-3a69-6b45bc79f81c NaN update
13 2017-01-01T12:08:08 hornjohn 16.0 73 NaN 0.0 interval
14 2017-01-01T12:08:35 gramirez 24.0 73 NaN 0.0 wake
15 2017-01-01T12:09:05 schmidtsamuel NaN 78 b9890c1e-79d5-8979-63ae-6c08a4cd476a 0.0 NaN
16 2017-01-01T12:09:48 derrick47 NaN 63 b60bd7de-4057-8a85-f806-e6eec1350338 NaN interval
17 2017-01-01T12:10:23 beckercharles 12.0 61 b1dacc73-c8b7-1d7d-ee02-578da781a71e 0.0 test
18 2017-01-01T12:10:57 ipittman 11.0 69 1aef7db8-9a3e-7dc9-d7a5-781ec0efd200 NaN user
19 2017-01-01T12:11:34 sabrina65 22.0 82 8075d058-7dae-e2ec-d47e-58ec6d26899b 1.0 NaN

Dann schauen wir uns an, von welchem Datentyp die Spalten sind:

[4]:
df.dtypes
[4]:
timestamp       object
username        object
temperature    float64
heartrate        int64
build           object
latest         float64
note            object
dtype: object

Wir können uns auch die Werte und deren Häufigkeit anzeigen lassen, z.B. für die Spalte note:

[5]:
df.note.value_counts()
[5]:
note
wake        16496
user        16416
interval    16274
sleep       16226
update      16213
test        16068
Name: count, dtype: int64

2. Entfernen aller Nullwerte (einschließlich der Angabe n/a)#

2.1 … mit pandas.read_csv#

pandas.read_csv filtert normalerweise bereits viele Werte heraus, die es als NA oder NaN erkennt. Weitere Werte können mit na_values angegeben werden.

[6]:
df = pd.read_csv(
    "https://raw.githubusercontent.com/kjam/data-cleaning-101/master/data/iot_example_with_nulls.csv",
    na_values=["n/a"],
)

2.2 … mit pandas.DataFrame.dropna#

Mit pandas.DataFrame.dropna lassen sich fehlende Werte löschen.

Um den Umfang der Löschungen zu analysieren lassen wir uns den Umfang des DataFrame vor und nach dem Läschen mit pandas.DataFrame.shape anzeigen:

[7]:
df.shape
[7]:
(146397, 7)
[8]:
df2 = df.dropna().shape

Wir würden mit pandas.DataFrame.dropna also mehr als 2/3 der Datensätze verlieren.

Im nächsten Versuch wollen wir analysieren, ob ganze Zeilen oder Spalten keine Daten enthalten. Dabei werden mit how='all' Zeilen oder Spalten entfernt, die keine Werte enthalten; axis=1 besagt, dass leere Zeilen entfernt werden sollen.

[9]:
df.dropna(how="all", axis=1).shape
[9]:
(146397, 7)

Auch dies bringt uns noch nicht weiter.

2.3 Alle Spalten finden, in denen alle Daten vorhanden sind#

[10]:
complete_columns = list(df.columns)
[11]:
complete_columns
[11]:
['timestamp',
 'username',
 'temperature',
 'heartrate',
 'build',
 'latest',
 'note']

2.4 Allte Spalten finden, in denen die meisten Daten vorhanden sind#

[12]:
list(df.dropna(thresh=int(df.shape[0] * 0.9), axis=1).columns)
[12]:
['timestamp', 'username', 'heartrate']

thresh erfordert eine bestimmte Anzahl von NA-Werten, in unserem Fall 90 %, bevor axis=1 eine Spalte ausblendet.

2.5 Alle Spalten mit fehlenden Daten finden#

Mit pandas.DataFrame.isnull können wir fehlende Werte finden und mit pandas.DataFrame.any erfahren wir, ob ein Element gültig ist, normalerweise über einer Spalte.

[13]:
incomplete_columns = list(df.columns[df.isnull().any()])
[14]:
incomplete_columns
[14]:
['temperature', 'build', 'latest', 'note']

Mit num_missing können wir uns nun die Anzahl der fehlenden Werte pro Spalte ausgeben lassen:

[15]:
for col in incomplete_columns:
    num_missing = df[df[col].isnull() == True].shape[0]
    print("number missing for column {}: {}".format(col, num_missing))
number missing for column temperature: 32357
number missing for column build: 32350
number missing for column latest: 32298
number missing for column note: 48704

Diese Werte können wir uns auch prozentual ausgeben lassen:

[16]:
for col in incomplete_columns:
    percent_missing = df[df[col].isnull() == True].shape[0] / df.shape[0]
    print("percent missing for column {}: {}".format(col, percent_missing))
percent missing for column temperature: 0.22102228870810195
percent missing for column build: 0.22097447352063226
percent missing for column latest: 0.22061927498514314
percent missing for column note: 0.332684412931959

2.6 Ersetzen fehlender Daten#

Um unsere Änderungen in der Spalte latest überprüfen zu können, verwenden wir pandas.Series.value_counts. Die Methode gibt eine Serie zurück, die die Anzahl der eindeutigen Werte enthält:

[17]:
df.latest.value_counts()
[17]:
latest
0.0    75735
1.0    38364
Name: count, dtype: int64

Jetzt ersetzen wir die fehlenden Werte in der Spalte latest durch 0 mit DataFrame.fillna:

[18]:
df.latest = df.latest.fillna(0)
[19]:
df.latest.value_counts()
[19]:
latest
0.0    108033
1.0     38364
Name: count, dtype: int64

2.7 Ersetzen fehlender Daten durch backfill#

Damit die Datensätze in ihrer zeitlichen Reihenfolge aufeinanderfolgen, setzen wir zunächst den Index für timestamp mit set_index:

[20]:
df = df.set_index("timestamp")
[21]:
df.head(20)
[21]:
username temperature heartrate build latest note
timestamp
2017-01-01T12:00:23 michaelsmith 12.0 67 4e6a7805-8faa-2768-6ef6-eb3198b483ac 0.0 interval
2017-01-01T12:01:09 kharrison 6.0 78 7256b7b0-e502-f576-62ec-ed73533c9c84 0.0 wake
2017-01-01T12:01:34 smithadam 5.0 89 9226c94b-bb4b-a6c8-8e02-cb42b53e9c90 0.0 NaN
2017-01-01T12:02:09 eddierodriguez 28.0 76 NaN 0.0 update
2017-01-01T12:02:36 kenneth94 29.0 62 122f1c6a-403c-2221-6ed1-b5caa08f11e0 0.0 NaN
2017-01-01T12:03:04 bryanttodd 13.0 86 0897dbe5-9c5b-71ca-73a1-7586959ca198 0.0 interval
2017-01-01T12:03:51 andrea98 17.0 81 1c07ab9b-5f66-137d-a74f-921a41001f4e 1.0 NaN
2017-01-01T12:04:35 scott28 16.0 76 7a60219f-6621-e548-180e-ca69624f9824 0.0 interval
2017-01-01T12:05:05 hillpamela 5.0 82 a8b87754-a162-da28-2527-4bce4b3d4191 1.0 NaN
2017-01-01T12:05:41 moorejeffrey 25.0 63 585f1a3c-0679-0ffe-9132-508933c70343 0.0 wake
2017-01-01T12:06:21 njohnson NaN 63 e09b6001-125d-51cf-9c3f-9cb686c19d02 0.0 NaN
2017-01-01T12:06:53 gsutton 29.0 80 607c9f6e-2bdf-a606-6d16-3004c6958436 1.0 update
2017-01-01T12:07:41 jessica48 22.0 83 03e1a07b-3e14-412c-3a69-6b45bc79f81c 0.0 update
2017-01-01T12:08:08 hornjohn 16.0 73 NaN 0.0 interval
2017-01-01T12:08:35 gramirez 24.0 73 NaN 0.0 wake
2017-01-01T12:09:05 schmidtsamuel NaN 78 b9890c1e-79d5-8979-63ae-6c08a4cd476a 0.0 NaN
2017-01-01T12:09:48 derrick47 NaN 63 b60bd7de-4057-8a85-f806-e6eec1350338 0.0 interval
2017-01-01T12:10:23 beckercharles 12.0 61 b1dacc73-c8b7-1d7d-ee02-578da781a71e 0.0 test
2017-01-01T12:10:57 ipittman 11.0 69 1aef7db8-9a3e-7dc9-d7a5-781ec0efd200 0.0 user
2017-01-01T12:11:34 sabrina65 22.0 82 8075d058-7dae-e2ec-d47e-58ec6d26899b 1.0 NaN

Anschließend verwenden wir pandas.DataFrame.groupby, um die Datensätze nach username zu gruppieren und dann die fehlenden Daten mit der backfill-Methode von pandas.core.groupby.DataFrameGroupBy.fillna zu füllen. limit definiert die maximale Anzahl aufeinanderfolgender NaN-Werte:

[22]:
df.temperature = df.groupby("username").temperature.fillna(
    method="backfill", limit=3
)
[23]:
for col in incomplete_columns:
    num_missing = df[df[col].isnull() == True].shape[0]
    print("number missing for column {}: {}".format(col, num_missing))
number missing for column temperature: 22633
number missing for column build: 32350
number missing for column latest: 0
number missing for column note: 48704

Argumente der Funktion fillna:

Argument

Beschreibung

value

Skalarwert oder dict-atähnliches Objekt, das zum Auffüllen fehlender Werte verwendet wird

Methode

Interpolation; standardmäßig ffill, wenn die Funktion ohne weitere Argumente aufgerufen wird

axis

Aufzufüllende Achse; Voreinstellung axis=0

inplace

ändert das aufrufende Objekt, ohne eine Kopie zu erzeugen

limit

Für das Auffüllen in Vorwärts- und Rückwärtsrichtung, maximale Anzahl von aufeinanderfolgenden Perioden zum Auffüllen