universal-data-cleaner 2.2.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {universal_data_cleaner-2.2.0/src/universal_data_cleaner.egg-info → universal_data_cleaner-2.4.0}/PKG-INFO +192 -2
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/README.md +191 -1
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/pyproject.toml +1 -1
- universal_data_cleaner-2.4.0/src/data_cleaner/__init__.py +86 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/_version.py +2 -2
- universal_data_cleaner-2.4.0/src/data_cleaner/artifacts.py +171 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/cleaner.py +336 -4
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/config.py +1 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/contract.py +19 -0
- universal_data_cleaner-2.4.0/src/data_cleaner/decisions.py +50 -0
- universal_data_cleaner-2.4.0/src/data_cleaner/integration.py +613 -0
- universal_data_cleaner-2.4.0/src/data_cleaner/validation.py +581 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0/src/universal_data_cleaner.egg-info}/PKG-INFO +192 -2
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/SOURCES.txt +6 -1
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/tests/test_data_cleaner.py +241 -14
- universal_data_cleaner-2.4.0/tests/test_validation.py +399 -0
- universal_data_cleaner-2.2.0/src/data_cleaner/__init__.py +0 -37
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/LICENSE +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/setup.cfg +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/_roles.py +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/profiling.py +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/data_cleaner/py.typed +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/dependency_links.txt +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/requires.txt +0 -0
- {universal_data_cleaner-2.2.0 → universal_data_cleaner-2.4.0}/src/universal_data_cleaner.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: universal-data-cleaner
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Production-ready, scikit-learn-compatible data cleaning transformer for pandas.
|
|
5
5
|
Author: Haseeb Khan
|
|
6
6
|
License-Expression: MIT
|
|
@@ -241,6 +241,165 @@ restored = DataContract.from_json(payload)
|
|
|
241
241
|
# restored = DataContract.from_yaml(contract.to_yaml())
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
+
## Expectations, joins, and referential integrity
|
|
245
|
+
|
|
246
|
+
### Expectations and validation reports
|
|
247
|
+
|
|
248
|
+
Run lightweight expectations against any batch and get a shareable report:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from data_cleaner import (
|
|
252
|
+
between,
|
|
253
|
+
has_dtype,
|
|
254
|
+
in_set,
|
|
255
|
+
matches_pattern,
|
|
256
|
+
not_null,
|
|
257
|
+
row_count_between,
|
|
258
|
+
unique,
|
|
259
|
+
validate,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
report = validate(
|
|
263
|
+
orders_new,
|
|
264
|
+
[
|
|
265
|
+
not_null("order_id"),
|
|
266
|
+
unique("order_id"),
|
|
267
|
+
in_set("status", ["new", "paid", "shipped"]),
|
|
268
|
+
between("amount", 0, 100_000),
|
|
269
|
+
matches_pattern("email", r"[^@]+@[^@]+\.[^@]+"),
|
|
270
|
+
has_dtype("created_at", "datetime"),
|
|
271
|
+
row_count_between(min_rows=1),
|
|
272
|
+
],
|
|
273
|
+
)
|
|
274
|
+
print(report.to_markdown())
|
|
275
|
+
report.raise_if_failed()
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`report.success` is true when no error-severity expectation failed.
|
|
279
|
+
`report.failures` includes warnings too, so use `severity="warning"` for soft
|
|
280
|
+
checks. `has_dtype` accepts broad kinds (`numeric`, `categorical`, `boolean`,
|
|
281
|
+
`datetime`) or an exact pandas dtype such as `float64`.
|
|
282
|
+
|
|
283
|
+
Contracts can validate a batch directly and return the same report type:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from data_cleaner import DataContract
|
|
287
|
+
|
|
288
|
+
contract = DataContract.from_frame(orders_train)
|
|
289
|
+
result = contract.validate(orders_new)
|
|
290
|
+
result.failures.to_markdown()
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`Contract` is a shorthand alias for `DataContract`.
|
|
294
|
+
|
|
295
|
+
### Join diagnostics
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from data_cleaner import diagnose_join
|
|
299
|
+
|
|
300
|
+
report = diagnose_join(orders, customers, on="customer_id", how="left")
|
|
301
|
+
|
|
302
|
+
report.safe # False on cardinality mismatch, left/right row multiplication, or null keys
|
|
303
|
+
report.cardinality # e.g. "1:m"
|
|
304
|
+
report.row_multiplier # output rows / left rows
|
|
305
|
+
report.unmatched_left
|
|
306
|
+
report.unmatched_right
|
|
307
|
+
report.warnings
|
|
308
|
+
report.raise_if_unsafe()
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`diagnose_join` defaults to `how="left"` and `validate="1:1"`. It reports
|
|
312
|
+
duplicate keys, null keys, unmatched rows, and the actual output row count
|
|
313
|
+
without materializing the full joined frame.
|
|
314
|
+
|
|
315
|
+
### Referential integrity
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from data_cleaner import check_referential_integrity
|
|
319
|
+
|
|
320
|
+
report = check_referential_integrity(
|
|
321
|
+
orders, customers, child_key="customer_id", parent_key="id"
|
|
322
|
+
)
|
|
323
|
+
report.orphan_rows
|
|
324
|
+
report.orphan_keys
|
|
325
|
+
report.raise_if_invalid()
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Null child keys are reported but allowed by default (`allow_null_keys=True`).
|
|
329
|
+
Duplicate parent keys make the relationship invalid because the parent key is
|
|
330
|
+
not a unique reference target.
|
|
331
|
+
|
|
332
|
+
### Business-key deduplication
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
from data_cleaner import deduplicate_by_business_key
|
|
336
|
+
|
|
337
|
+
deduped, report = deduplicate_by_business_key(
|
|
338
|
+
orders, key="order_id", keep="last", tie_breaker="updated_at"
|
|
339
|
+
)
|
|
340
|
+
report.removed_rows
|
|
341
|
+
report.removed # the actual rows that were dropped
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The returned frame preserves the input row order and index. `tie_breaker`
|
|
345
|
+
prefers non-null values, and null business keys are reported explicitly.
|
|
346
|
+
The input frame is never mutated.
|
|
347
|
+
|
|
348
|
+
## Audit trail, manifests, and fingerprints
|
|
349
|
+
|
|
350
|
+
Every learned or configured decision is recorded during `fit()` and exposed as
|
|
351
|
+
a tidy DataFrame:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
log = cleaner.decision_log()
|
|
355
|
+
# columns: column, action, reason, source, before, after
|
|
356
|
+
log[log["action"] == "cap_outliers"]
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
`source` says whether a rule was `learned` from the training data, supplied via
|
|
360
|
+
`config`, declared in a `contract`, or `inferred` by the library. Imputation
|
|
361
|
+
values, outlier bounds, datetime expansion, missing indicators, dropped
|
|
362
|
+
constants, and identifier handling are all explained instead of hidden.
|
|
363
|
+
|
|
364
|
+
Build a JSON-serializable manifest for logs or a model registry:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
import json
|
|
368
|
+
|
|
369
|
+
manifest = cleaner.to_manifest(X_train, include_fingerprint=True)
|
|
370
|
+
# library_version, artifact_version, environment, config, contract,
|
|
371
|
+
# input_schema, output_columns, fitted, decisions, fingerprint
|
|
372
|
+
|
|
373
|
+
json.dumps(manifest) # safe to ship to a registry or a log line
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`save()` embeds the manifest and the training fingerprint in the artifact, and
|
|
377
|
+
`load()` restores them:
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
cleaner.save("cleaner.joblib")
|
|
381
|
+
restored = DataCleaner.load("cleaner.joblib")
|
|
382
|
+
restored.artifact_manifest()
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Fingerprints are stable SHA-256 hashes of the schema plus summary statistics.
|
|
386
|
+
They never hash raw values, so they are safe to log:
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
from data_cleaner import (
|
|
390
|
+
assert_schema_stable,
|
|
391
|
+
compare_fingerprints,
|
|
392
|
+
fingerprint_dataframe,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
expected = fingerprint_dataframe(X_train)
|
|
396
|
+
same = compare_fingerprints(expected, fingerprint_dataframe(X_new))
|
|
397
|
+
assert_schema_stable(X_new, expected) # raises ValueError on schema/stat drift
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Set `CleanerConfig(fingerprint_on_fit=True)` to compute the training fingerprint
|
|
401
|
+
once during `fit()`, so `to_manifest(include_fingerprint=True)` needs no data.
|
|
402
|
+
|
|
244
403
|
## scikit-learn Pipeline
|
|
245
404
|
|
|
246
405
|
```python
|
|
@@ -279,11 +438,33 @@ predictions = pipeline.predict(X_test)
|
|
|
279
438
|
| --- | --- |
|
|
280
439
|
| `CleanerConfig` | Validated cleaning configuration |
|
|
281
440
|
| `DataCleaner` | Fit/transform cleaner with save/load and drift reporting |
|
|
441
|
+
| `CleaningDecision` | One auditable learned or configured decision |
|
|
282
442
|
| `ColumnSpec` | Declarative expectations and semantics for one column |
|
|
283
443
|
| `DataContract` | A set of column specs, with inference and (de)serialization |
|
|
444
|
+
| `Contract` | Shorthand alias for `DataContract` |
|
|
445
|
+
| `DataContract.validate()` | Check a batch and return a `ValidationReport` |
|
|
284
446
|
| `DriftReport` | Findings from a contract check, with a Markdown summary |
|
|
447
|
+
| `Expectation` | One reusable expectation produced by a factory |
|
|
448
|
+
| `validate` | Evaluate expectations against a frame |
|
|
449
|
+
| `not_null`, `unique`, `in_set`, `between` | Expectation factories |
|
|
450
|
+
| `matches_pattern`, `has_dtype` | Expectation factories |
|
|
451
|
+
| `row_count_between` | Frame-level row-count expectation |
|
|
452
|
+
| `ValidationReport` | Pass/fail results, failures table, Markdown, and raise helper |
|
|
453
|
+
| `diagnose_join` | Diagnose key duplication, cardinality, and row multiplication |
|
|
454
|
+
| `JoinDiagnosis` | Join report with multiplier, unmatched counts, and warnings |
|
|
455
|
+
| `check_referential_integrity` | Find orphan child keys and non-unique parent keys |
|
|
456
|
+
| `ReferentialIntegrityReport` | Orphan counts/keys, null keys, and validity |
|
|
457
|
+
| `deduplicate_by_business_key` | Deduplicate by key and report removed rows |
|
|
458
|
+
| `DeduplicationReport` | Removed rows, duplicate-key counts, and tie-breaker details |
|
|
285
459
|
| `DataCleaner.drift_report()` | Drift findings from the last `transform` |
|
|
286
460
|
| `DataCleaner.fitted_contract()` | The contract used by the fitted cleaner |
|
|
461
|
+
| `DataCleaner.decision_log()` | Tidy DataFrame of cleaning decisions |
|
|
462
|
+
| `DataCleaner.to_manifest()` | JSON-serializable artifact manifest |
|
|
463
|
+
| `DataCleaner.artifact_manifest()` | Manifest stored in a loaded artifact |
|
|
464
|
+
| `DataCleaner.fingerprint()` | Stable schema + statistics fingerprint |
|
|
465
|
+
| `fingerprint_dataframe` | Fingerprint any DataFrame for lineage |
|
|
466
|
+
| `compare_fingerprints` | Compare two fingerprints |
|
|
467
|
+
| `assert_schema_stable` | Raise when a frame's fingerprint changed |
|
|
287
468
|
| `profile_dataframe` | Column-level missing/type/cardinality profile |
|
|
288
469
|
| `dataset_summary` | Row/column/missing/duplicate summary |
|
|
289
470
|
| `drop_duplicate_rows` | Explicit duplicate removal helper |
|
|
@@ -305,6 +486,10 @@ predictions = pipeline.predict(X_test)
|
|
|
305
486
|
represented by the generated feature NaNs plus an optional missing indicator.
|
|
306
487
|
- IQR capping is skipped automatically when the IQR is zero, so constant or
|
|
307
488
|
near-constant numeric columns are not accidentally destroyed.
|
|
489
|
+
- Persist `cleaner.to_manifest(X_train, include_fingerprint=True)` alongside the
|
|
490
|
+
model so you can audit exactly which cleaning rules produced an artifact.
|
|
491
|
+
- `decision_log()` explains every learned or configured rule; ship it to your
|
|
492
|
+
experiment tracker when a model's preprocessing changes.
|
|
308
493
|
|
|
309
494
|
## Project layout
|
|
310
495
|
|
|
@@ -314,13 +499,18 @@ predictions = pipeline.predict(X_test)
|
|
|
314
499
|
│ ├── __init__.py # public API re-exports
|
|
315
500
|
│ ├── _version.py # version metadata
|
|
316
501
|
│ ├── _roles.py # semantic roles and dtype helpers
|
|
502
|
+
│ ├── artifacts.py # fingerprints and runtime metadata
|
|
317
503
|
│ ├── contract.py # ColumnSpec / DataContract / DriftReport
|
|
318
504
|
│ ├── config.py # CleanerConfig
|
|
319
505
|
│ ├── cleaner.py # DataCleaner
|
|
506
|
+
│ ├── decisions.py # CleaningDecision and decision log
|
|
507
|
+
│ ├── integration.py # joins, referential integrity, dedup
|
|
320
508
|
│ ├── profiling.py # profiling and validation helpers
|
|
509
|
+
│ ├── validation.py # expectations and ValidationReport
|
|
321
510
|
│ └── py.typed # PEP 561 marker
|
|
322
511
|
├── Universal_Data_Cleaner.ipynb # end-to-end reference workflow
|
|
323
|
-
├── tests/test_data_cleaner.py # unit and integration tests
|
|
512
|
+
├── tests/test_data_cleaner.py # core unit and integration tests
|
|
513
|
+
├── tests/test_validation.py # expectations and integration tests
|
|
324
514
|
├── pyproject.toml # package metadata and dev tooling
|
|
325
515
|
├── uv.lock # reproducible dependency lock
|
|
326
516
|
├── .pre-commit-config.yaml # local lint/format/type hooks
|
|
@@ -208,6 +208,165 @@ restored = DataContract.from_json(payload)
|
|
|
208
208
|
# restored = DataContract.from_yaml(contract.to_yaml())
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
## Expectations, joins, and referential integrity
|
|
212
|
+
|
|
213
|
+
### Expectations and validation reports
|
|
214
|
+
|
|
215
|
+
Run lightweight expectations against any batch and get a shareable report:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from data_cleaner import (
|
|
219
|
+
between,
|
|
220
|
+
has_dtype,
|
|
221
|
+
in_set,
|
|
222
|
+
matches_pattern,
|
|
223
|
+
not_null,
|
|
224
|
+
row_count_between,
|
|
225
|
+
unique,
|
|
226
|
+
validate,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
report = validate(
|
|
230
|
+
orders_new,
|
|
231
|
+
[
|
|
232
|
+
not_null("order_id"),
|
|
233
|
+
unique("order_id"),
|
|
234
|
+
in_set("status", ["new", "paid", "shipped"]),
|
|
235
|
+
between("amount", 0, 100_000),
|
|
236
|
+
matches_pattern("email", r"[^@]+@[^@]+\.[^@]+"),
|
|
237
|
+
has_dtype("created_at", "datetime"),
|
|
238
|
+
row_count_between(min_rows=1),
|
|
239
|
+
],
|
|
240
|
+
)
|
|
241
|
+
print(report.to_markdown())
|
|
242
|
+
report.raise_if_failed()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`report.success` is true when no error-severity expectation failed.
|
|
246
|
+
`report.failures` includes warnings too, so use `severity="warning"` for soft
|
|
247
|
+
checks. `has_dtype` accepts broad kinds (`numeric`, `categorical`, `boolean`,
|
|
248
|
+
`datetime`) or an exact pandas dtype such as `float64`.
|
|
249
|
+
|
|
250
|
+
Contracts can validate a batch directly and return the same report type:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from data_cleaner import DataContract
|
|
254
|
+
|
|
255
|
+
contract = DataContract.from_frame(orders_train)
|
|
256
|
+
result = contract.validate(orders_new)
|
|
257
|
+
result.failures.to_markdown()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`Contract` is a shorthand alias for `DataContract`.
|
|
261
|
+
|
|
262
|
+
### Join diagnostics
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from data_cleaner import diagnose_join
|
|
266
|
+
|
|
267
|
+
report = diagnose_join(orders, customers, on="customer_id", how="left")
|
|
268
|
+
|
|
269
|
+
report.safe # False on cardinality mismatch, left/right row multiplication, or null keys
|
|
270
|
+
report.cardinality # e.g. "1:m"
|
|
271
|
+
report.row_multiplier # output rows / left rows
|
|
272
|
+
report.unmatched_left
|
|
273
|
+
report.unmatched_right
|
|
274
|
+
report.warnings
|
|
275
|
+
report.raise_if_unsafe()
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`diagnose_join` defaults to `how="left"` and `validate="1:1"`. It reports
|
|
279
|
+
duplicate keys, null keys, unmatched rows, and the actual output row count
|
|
280
|
+
without materializing the full joined frame.
|
|
281
|
+
|
|
282
|
+
### Referential integrity
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from data_cleaner import check_referential_integrity
|
|
286
|
+
|
|
287
|
+
report = check_referential_integrity(
|
|
288
|
+
orders, customers, child_key="customer_id", parent_key="id"
|
|
289
|
+
)
|
|
290
|
+
report.orphan_rows
|
|
291
|
+
report.orphan_keys
|
|
292
|
+
report.raise_if_invalid()
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Null child keys are reported but allowed by default (`allow_null_keys=True`).
|
|
296
|
+
Duplicate parent keys make the relationship invalid because the parent key is
|
|
297
|
+
not a unique reference target.
|
|
298
|
+
|
|
299
|
+
### Business-key deduplication
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from data_cleaner import deduplicate_by_business_key
|
|
303
|
+
|
|
304
|
+
deduped, report = deduplicate_by_business_key(
|
|
305
|
+
orders, key="order_id", keep="last", tie_breaker="updated_at"
|
|
306
|
+
)
|
|
307
|
+
report.removed_rows
|
|
308
|
+
report.removed # the actual rows that were dropped
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The returned frame preserves the input row order and index. `tie_breaker`
|
|
312
|
+
prefers non-null values, and null business keys are reported explicitly.
|
|
313
|
+
The input frame is never mutated.
|
|
314
|
+
|
|
315
|
+
## Audit trail, manifests, and fingerprints
|
|
316
|
+
|
|
317
|
+
Every learned or configured decision is recorded during `fit()` and exposed as
|
|
318
|
+
a tidy DataFrame:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
log = cleaner.decision_log()
|
|
322
|
+
# columns: column, action, reason, source, before, after
|
|
323
|
+
log[log["action"] == "cap_outliers"]
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`source` says whether a rule was `learned` from the training data, supplied via
|
|
327
|
+
`config`, declared in a `contract`, or `inferred` by the library. Imputation
|
|
328
|
+
values, outlier bounds, datetime expansion, missing indicators, dropped
|
|
329
|
+
constants, and identifier handling are all explained instead of hidden.
|
|
330
|
+
|
|
331
|
+
Build a JSON-serializable manifest for logs or a model registry:
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
import json
|
|
335
|
+
|
|
336
|
+
manifest = cleaner.to_manifest(X_train, include_fingerprint=True)
|
|
337
|
+
# library_version, artifact_version, environment, config, contract,
|
|
338
|
+
# input_schema, output_columns, fitted, decisions, fingerprint
|
|
339
|
+
|
|
340
|
+
json.dumps(manifest) # safe to ship to a registry or a log line
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`save()` embeds the manifest and the training fingerprint in the artifact, and
|
|
344
|
+
`load()` restores them:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
cleaner.save("cleaner.joblib")
|
|
348
|
+
restored = DataCleaner.load("cleaner.joblib")
|
|
349
|
+
restored.artifact_manifest()
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Fingerprints are stable SHA-256 hashes of the schema plus summary statistics.
|
|
353
|
+
They never hash raw values, so they are safe to log:
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
from data_cleaner import (
|
|
357
|
+
assert_schema_stable,
|
|
358
|
+
compare_fingerprints,
|
|
359
|
+
fingerprint_dataframe,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
expected = fingerprint_dataframe(X_train)
|
|
363
|
+
same = compare_fingerprints(expected, fingerprint_dataframe(X_new))
|
|
364
|
+
assert_schema_stable(X_new, expected) # raises ValueError on schema/stat drift
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Set `CleanerConfig(fingerprint_on_fit=True)` to compute the training fingerprint
|
|
368
|
+
once during `fit()`, so `to_manifest(include_fingerprint=True)` needs no data.
|
|
369
|
+
|
|
211
370
|
## scikit-learn Pipeline
|
|
212
371
|
|
|
213
372
|
```python
|
|
@@ -246,11 +405,33 @@ predictions = pipeline.predict(X_test)
|
|
|
246
405
|
| --- | --- |
|
|
247
406
|
| `CleanerConfig` | Validated cleaning configuration |
|
|
248
407
|
| `DataCleaner` | Fit/transform cleaner with save/load and drift reporting |
|
|
408
|
+
| `CleaningDecision` | One auditable learned or configured decision |
|
|
249
409
|
| `ColumnSpec` | Declarative expectations and semantics for one column |
|
|
250
410
|
| `DataContract` | A set of column specs, with inference and (de)serialization |
|
|
411
|
+
| `Contract` | Shorthand alias for `DataContract` |
|
|
412
|
+
| `DataContract.validate()` | Check a batch and return a `ValidationReport` |
|
|
251
413
|
| `DriftReport` | Findings from a contract check, with a Markdown summary |
|
|
414
|
+
| `Expectation` | One reusable expectation produced by a factory |
|
|
415
|
+
| `validate` | Evaluate expectations against a frame |
|
|
416
|
+
| `not_null`, `unique`, `in_set`, `between` | Expectation factories |
|
|
417
|
+
| `matches_pattern`, `has_dtype` | Expectation factories |
|
|
418
|
+
| `row_count_between` | Frame-level row-count expectation |
|
|
419
|
+
| `ValidationReport` | Pass/fail results, failures table, Markdown, and raise helper |
|
|
420
|
+
| `diagnose_join` | Diagnose key duplication, cardinality, and row multiplication |
|
|
421
|
+
| `JoinDiagnosis` | Join report with multiplier, unmatched counts, and warnings |
|
|
422
|
+
| `check_referential_integrity` | Find orphan child keys and non-unique parent keys |
|
|
423
|
+
| `ReferentialIntegrityReport` | Orphan counts/keys, null keys, and validity |
|
|
424
|
+
| `deduplicate_by_business_key` | Deduplicate by key and report removed rows |
|
|
425
|
+
| `DeduplicationReport` | Removed rows, duplicate-key counts, and tie-breaker details |
|
|
252
426
|
| `DataCleaner.drift_report()` | Drift findings from the last `transform` |
|
|
253
427
|
| `DataCleaner.fitted_contract()` | The contract used by the fitted cleaner |
|
|
428
|
+
| `DataCleaner.decision_log()` | Tidy DataFrame of cleaning decisions |
|
|
429
|
+
| `DataCleaner.to_manifest()` | JSON-serializable artifact manifest |
|
|
430
|
+
| `DataCleaner.artifact_manifest()` | Manifest stored in a loaded artifact |
|
|
431
|
+
| `DataCleaner.fingerprint()` | Stable schema + statistics fingerprint |
|
|
432
|
+
| `fingerprint_dataframe` | Fingerprint any DataFrame for lineage |
|
|
433
|
+
| `compare_fingerprints` | Compare two fingerprints |
|
|
434
|
+
| `assert_schema_stable` | Raise when a frame's fingerprint changed |
|
|
254
435
|
| `profile_dataframe` | Column-level missing/type/cardinality profile |
|
|
255
436
|
| `dataset_summary` | Row/column/missing/duplicate summary |
|
|
256
437
|
| `drop_duplicate_rows` | Explicit duplicate removal helper |
|
|
@@ -272,6 +453,10 @@ predictions = pipeline.predict(X_test)
|
|
|
272
453
|
represented by the generated feature NaNs plus an optional missing indicator.
|
|
273
454
|
- IQR capping is skipped automatically when the IQR is zero, so constant or
|
|
274
455
|
near-constant numeric columns are not accidentally destroyed.
|
|
456
|
+
- Persist `cleaner.to_manifest(X_train, include_fingerprint=True)` alongside the
|
|
457
|
+
model so you can audit exactly which cleaning rules produced an artifact.
|
|
458
|
+
- `decision_log()` explains every learned or configured rule; ship it to your
|
|
459
|
+
experiment tracker when a model's preprocessing changes.
|
|
275
460
|
|
|
276
461
|
## Project layout
|
|
277
462
|
|
|
@@ -281,13 +466,18 @@ predictions = pipeline.predict(X_test)
|
|
|
281
466
|
│ ├── __init__.py # public API re-exports
|
|
282
467
|
│ ├── _version.py # version metadata
|
|
283
468
|
│ ├── _roles.py # semantic roles and dtype helpers
|
|
469
|
+
│ ├── artifacts.py # fingerprints and runtime metadata
|
|
284
470
|
│ ├── contract.py # ColumnSpec / DataContract / DriftReport
|
|
285
471
|
│ ├── config.py # CleanerConfig
|
|
286
472
|
│ ├── cleaner.py # DataCleaner
|
|
473
|
+
│ ├── decisions.py # CleaningDecision and decision log
|
|
474
|
+
│ ├── integration.py # joins, referential integrity, dedup
|
|
287
475
|
│ ├── profiling.py # profiling and validation helpers
|
|
476
|
+
│ ├── validation.py # expectations and ValidationReport
|
|
288
477
|
│ └── py.typed # PEP 561 marker
|
|
289
478
|
├── Universal_Data_Cleaner.ipynb # end-to-end reference workflow
|
|
290
|
-
├── tests/test_data_cleaner.py # unit and integration tests
|
|
479
|
+
├── tests/test_data_cleaner.py # core unit and integration tests
|
|
480
|
+
├── tests/test_validation.py # expectations and integration tests
|
|
291
481
|
├── pyproject.toml # package metadata and dev tooling
|
|
292
482
|
├── uv.lock # reproducible dependency lock
|
|
293
483
|
├── .pre-commit-config.yaml # local lint/format/type hooks
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "universal-data-cleaner"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.0"
|
|
8
8
|
description = "Production-ready, scikit-learn-compatible data cleaning transformer for pandas."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Universal Data Cleaner — production-oriented tabular cleaning toolkit.
|
|
2
|
+
|
|
3
|
+
The package is intentionally dependency-light:
|
|
4
|
+
|
|
5
|
+
- pandas + numpy for data handling
|
|
6
|
+
- scikit-learn base classes for Pipeline / clone compatibility
|
|
7
|
+
- joblib for artifact persistence
|
|
8
|
+
|
|
9
|
+
The public API is re-exported here so ``import data_cleaner`` keeps working
|
|
10
|
+
exactly as it did when the library shipped as a single module.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from ._version import __version__
|
|
16
|
+
from .artifacts import (
|
|
17
|
+
assert_schema_stable,
|
|
18
|
+
compare_fingerprints,
|
|
19
|
+
fingerprint_dataframe,
|
|
20
|
+
)
|
|
21
|
+
from .cleaner import DataCleaner
|
|
22
|
+
from .config import CleanerConfig
|
|
23
|
+
from .contract import ColumnSpec, Contract, DataContract, DriftReport
|
|
24
|
+
from .decisions import CleaningDecision
|
|
25
|
+
from .integration import (
|
|
26
|
+
DeduplicationReport,
|
|
27
|
+
JoinDiagnosis,
|
|
28
|
+
ReferentialIntegrityReport,
|
|
29
|
+
check_referential_integrity,
|
|
30
|
+
deduplicate_by_business_key,
|
|
31
|
+
diagnose_join,
|
|
32
|
+
)
|
|
33
|
+
from .profiling import (
|
|
34
|
+
assert_frame_clean,
|
|
35
|
+
dataset_summary,
|
|
36
|
+
drop_duplicate_rows,
|
|
37
|
+
profile_dataframe,
|
|
38
|
+
)
|
|
39
|
+
from .validation import (
|
|
40
|
+
Expectation,
|
|
41
|
+
ValidationReport,
|
|
42
|
+
ValidationResult,
|
|
43
|
+
between,
|
|
44
|
+
has_dtype,
|
|
45
|
+
in_set,
|
|
46
|
+
matches_pattern,
|
|
47
|
+
not_null,
|
|
48
|
+
row_count_between,
|
|
49
|
+
unique,
|
|
50
|
+
validate,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"CleanerConfig",
|
|
55
|
+
"CleaningDecision",
|
|
56
|
+
"ColumnSpec",
|
|
57
|
+
"Contract",
|
|
58
|
+
"DataCleaner",
|
|
59
|
+
"DataContract",
|
|
60
|
+
"DeduplicationReport",
|
|
61
|
+
"DriftReport",
|
|
62
|
+
"Expectation",
|
|
63
|
+
"JoinDiagnosis",
|
|
64
|
+
"ReferentialIntegrityReport",
|
|
65
|
+
"ValidationReport",
|
|
66
|
+
"ValidationResult",
|
|
67
|
+
"__version__",
|
|
68
|
+
"assert_frame_clean",
|
|
69
|
+
"assert_schema_stable",
|
|
70
|
+
"between",
|
|
71
|
+
"check_referential_integrity",
|
|
72
|
+
"compare_fingerprints",
|
|
73
|
+
"dataset_summary",
|
|
74
|
+
"deduplicate_by_business_key",
|
|
75
|
+
"diagnose_join",
|
|
76
|
+
"drop_duplicate_rows",
|
|
77
|
+
"fingerprint_dataframe",
|
|
78
|
+
"has_dtype",
|
|
79
|
+
"in_set",
|
|
80
|
+
"matches_pattern",
|
|
81
|
+
"not_null",
|
|
82
|
+
"profile_dataframe",
|
|
83
|
+
"row_count_between",
|
|
84
|
+
"unique",
|
|
85
|
+
"validate",
|
|
86
|
+
]
|