TDDA: Test-Driven Data Analysis¶
TDDA verwendet Dateieingaben (wie NumPy-Arrays oder Pandas DataFrames) und eine Reihe von Einschränkungen (engl.: constraints), die als JSON-Datei gespeichert werden.
Reference Test unterstützt die Erstellung von Referenztests, die entweder auf
unittest
oderpytest
basieren.Constraints wird verwendet, um Constraints aus einem (Pandas)-DataFrame zu ermitteln, sie als JSON auszuschreiben und zu überprüfen, ob Datensätze die Constraints in der Constraints-Datei erfüllen. Es unterstützt auch Tabellen in einer Vielzahl von relationalen Datenbanken.
Rexpy ist ein Werkzeug zur automatischen Ableitung von regulären Ausdrücken aus einer Spalte in einem Pandas DataFrame oder aus einer (Python)-Liste von Beispielen.
1. Importe¶
[1]:
import pandas as pd
import numpy as np
from tdda.constraints import discover_df, verify_df
[2]:
df = pd.read_csv("https://raw.githubusercontent.com/kjam/data-cleaning-101/master/data/iot_example.csv")
2. Daten überprüfen¶
Mit pandas.DataFrame.sample lassen wir uns die ersten zehn Datensätze anzeigen:
[3]:
df.sample(10)
[3]:
timestamp | username | temperature | heartrate | build | latest | note | |
---|---|---|---|---|---|---|---|
123221 | 2017-02-19T17:27:43 | wilsonpatricia | 20 | 60 | 2452cba2-623a-9aaf-4620-b1a00a707088 | 0 | NaN |
80684 | 2017-02-02T18:32:06 | vmiller | 27 | 79 | 877d2246-f54d-2d6b-8991-5698199def39 | 1 | user |
108810 | 2017-02-13T23:50:44 | sandraobrien | 9 | 73 | 2c185e6b-ad8a-bbc3-5b8a-a7f0cff3b3b5 | 1 | user |
47330 | 2017-01-20T10:00:25 | rcline | 18 | 63 | c8b03e53-db4a-bbb7-1d06-efb20eca17ec | 0 | NaN |
25842 | 2017-01-11T19:32:20 | colton99 | 20 | 72 | 1d99cb10-0189-3bc2-d7e3-4fee382387ef | 0 | sleep |
31413 | 2017-01-14T01:06:46 | zjimenez | 11 | 89 | eb2bfbd3-6a61-cfae-afcc-6879319aebad | 1 | user |
115347 | 2017-02-16T14:11:28 | alvin94 | 23 | 87 | 9924687d-959e-99cf-6510-712904df2583 | 0 | wake |
138131 | 2017-02-25T16:45:28 | chelsea05 | 7 | 66 | f995acb5-fff8-3ff7-0581-152e81988b81 | 1 | user |
97693 | 2017-02-09T13:31:43 | thomasknight | 18 | 66 | a9180bc3-90a3-88bf-36e5-a67549147b28 | 1 | user |
33904 | 2017-01-15T01:02:00 | paulwall | 8 | 66 | 56b09285-495a-23ef-31f7-489fad16096f | 1 | sleep |
Und mit pandas.DataFrame.dtypes lassen wir uns die Datentypen für die einzelnen Spalten anzeigen:
[4]:
df.dtypes
[4]:
timestamp object
username object
temperature int64
heartrate int64
build object
latest int64
note object
dtype: object
3. Erstellen eines constraints-Objekt¶
Mit discover_constraints
kann ein Vonstraints-Objekt erzeugt werden.
[5]:
constraints = discover_df(df)
[6]:
constraints
[6]:
<tdda.constraints.base.DatasetConstraints at 0x144fd7250>
[7]:
constraints.fields
[7]:
Fields([('timestamp', <tdda.constraints.base.FieldConstraints at 0x15fec8790>),
('username', <tdda.constraints.base.FieldConstraints at 0x15fec8bd0>),
('temperature',
<tdda.constraints.base.FieldConstraints at 0x15fec9050>),
('heartrate', <tdda.constraints.base.FieldConstraints at 0x15fec9750>),
('build', <tdda.constraints.base.FieldConstraints at 0x15fec9b10>),
('latest', <tdda.constraints.base.FieldConstraints at 0x15feca390>),
('note', <tdda.constraints.base.FieldConstraints at 0x15feca710>)])
4. Schreiben der Constraints in eine Datei¶
[8]:
with open("ignore-iot_constraints.tdda", "w") as f:
f.write(constraints.to_json())
Wenn wir uns die Datei genauer betrachten können wir erkennen, dass z.B. für die timestamp
-Spalte eine Zeichenkette mit 19 Zeichen erwartet wird und temperature
Integer mit Werten von 5–29 erwartet.
[9]:
cat ignore-iot_constraints.tdda
{
"creation_metadata": {
"local_time": "2023-08-19 13:13:48",
"utc_time": "2023-08-19 11:11:48",
"creator": "TDDA 2.0.09",
"host": "fay.fritz.box",
"user": "veit",
"n_records": 146397,
"n_selected": 146397
},
"fields": {
"timestamp": {
"type": "string",
"min_length": 19,
"max_length": 19,
"max_nulls": 0,
"no_duplicates": true
},
"username": {
"type": "string",
"min_length": 3,
"max_length": 21,
"max_nulls": 0
},
"temperature": {
"type": "int",
"min": 5,
"max": 29,
"sign": "positive",
"max_nulls": 0
},
"heartrate": {
"type": "int",
"min": 60,
"max": 89,
"sign": "positive",
"max_nulls": 0
},
"build": {
"type": "string",
"min_length": 36,
"max_length": 36,
"max_nulls": 0,
"no_duplicates": true
},
"latest": {
"type": "int",
"min": 0,
"max": 1,
"sign": "non-negative",
"max_nulls": 0
},
"note": {
"type": "string",
"min_length": 4,
"max_length": 8,
"allowed_values": [
"interval",
"sleep",
"test",
"update",
"user",
"wake"
]
}
}
}
5. Überprüfen von Dataframes¶
Hierfür lesen wir zunächst eine neue csv-Datei mit Pandas ein und lassen uns dann zehn Datensätze exemplarisch ausgeben:
[10]:
new_df = pd.read_csv("https://raw.githubusercontent.com/kjam/data-cleaning-101/master/data/iot_example_with_nulls.csv")
new_df.sample(10)
[10]:
timestamp | username | temperature | heartrate | build | latest | note | |
---|---|---|---|---|---|---|---|
126044 | 2017-02-20T20:33:42 | karenrichards | NaN | 79 | 284fab65-9fcc-18e4-8838-6e89ac938f77 | NaN | NaN |
54844 | 2017-01-23T10:00:53 | karen37 | NaN | 76 | 6ea2310d-b136-dfae-3e4a-730cb01a6881 | 1.0 | wake |
15484 | 2017-01-07T16:36:43 | carterjill | 24.0 | 61 | 3b524f97-4a6a-156e-e182-760818cc5c6b | 0.0 | interval |
3709 | 2017-01-02T23:50:12 | ebenton | 18.0 | 82 | f23b7b48-0ad1-18b5-c5a8-033d66d47007 | 1.0 | NaN |
131978 | 2017-02-23T05:37:46 | cameron67 | NaN | 87 | 2806ee39-a668-d0f7-44b9-9255188d51df | 1.0 | interval |
61302 | 2017-01-25T23:57:04 | rebecca88 | 19.0 | 71 | NaN | NaN | interval |
130493 | 2017-02-22T15:15:56 | cgriffin | 29.0 | 81 | NaN | 1.0 | NaN |
121685 | 2017-02-19T02:53:16 | johnberg | 16.0 | 68 | e3b81408-7c4f-78b5-c373-2ee086e6dbbf | NaN | NaN |
87150 | 2017-02-05T08:17:46 | kelly71 | 28.0 | 68 | 99842995-cc89-638c-9067-a7d92e450097 | 0.0 | interval |
138723 | 2017-02-25T22:22:49 | ayoung | 22.0 | 75 | dca3e5f6-c05a-c490-8384-4f7cfda4e19a | 0.0 | NaN |
Wir sehen mehrere Felder, die als NaN
ausgegeben werden. Um dies nun systematisch zu analysieren, wenden wir verify_df auf unseren neuen DataFrame an. Dabei gibt passes
gibt die Anzahl der bestandenen, failures
die Anzahl der fehlgeschlagenen Constraints zurück.
[11]:
v = verify_df(new_df, "ignore-iot_constraints.tdda")
[12]:
v
[12]:
<tdda.constraints.pd.constraints.PandasVerification at 0x174786fd0>
[13]:
v.passes
[13]:
30
[14]:
v.failures
[14]:
3
Wir können uns auch anzeigen lassen, in welchen Spalten welche Constraints bestanden und fehlgeschlagen sind:
[15]:
print(str(v))
FIELDS:
timestamp: 0 failures 5 passes type ✓ min_length ✓ max_length ✓ max_nulls ✓ no_duplicates ✓
username: 0 failures 4 passes type ✓ min_length ✓ max_length ✓ max_nulls ✓
temperature: 1 failure 4 passes type ✓ min ✓ max ✓ sign ✓ max_nulls ✗
heartrate: 0 failures 5 passes type ✓ min ✓ max ✓ sign ✓ max_nulls ✓
build: 1 failure 4 passes type ✓ min_length ✓ max_length ✓ max_nulls ✗ no_duplicates ✓
latest: 1 failure 4 passes type ✓ min ✓ max ✓ sign ✓ max_nulls ✗
note: 0 failures 4 passes type ✓ min_length ✓ max_length ✓ allowed_values ✓
SUMMARY:
Constraints passing: 30
Constraints failing: 3
Alternativ können wir uns diese Ergebnisse auch tabellarisch anzeigen lassen:
[16]:
v.to_frame()
[16]:
field | failures | passes | type | min | min_length | max | max_length | sign | max_nulls | no_duplicates | allowed_values | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | timestamp | 0 | 5 | True | NaN | True | NaN | True | NaN | True | True | NaN |
1 | username | 0 | 4 | True | NaN | True | NaN | True | NaN | True | NaN | NaN |
2 | temperature | 1 | 4 | True | True | NaN | True | NaN | True | False | NaN | NaN |
3 | heartrate | 0 | 5 | True | True | NaN | True | NaN | True | True | NaN | NaN |
4 | build | 1 | 4 | True | NaN | True | NaN | True | NaN | False | True | NaN |
5 | latest | 1 | 4 | True | True | NaN | True | NaN | True | False | NaN | NaN |
6 | note | 0 | 4 | True | NaN | True | NaN | True | NaN | NaN | NaN | True |