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 |
---|---|
|
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. |
|
füllt fehlende Daten mit einem Wert oder mit einer Interpolationsmethode wie |
|
gibt boolesche Werte zurück, die angeben, welche Werte fehlen/ |
|
negiert |
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.
[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]:
wake 16496
user 16416
interval 16274
sleep 16226
update 16213
test 16068
Name: note, 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] * .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]:
0.0 75735
1.0 38364
Name: latest, 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]:
0.0 108033
1.0 38364
Name: latest, 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 |
---|---|
|
Skalarwert oder dict-atähnliches Objekt, das zum Auffüllen fehlender Werte verwendet wird |
Methode |
Interpolation; standardmäßig |
|
Aufzufüllende Achse; Voreinstellung |
|
ändert das aufrufende Objekt, ohne eine Kopie zu erzeugen |
|
Für das Auffüllen in Vorwärts- und Rückwärtsrichtung, maximale Anzahl von aufeinanderfolgenden Perioden zum Auffüllen |