universal-data-cleaner 2.0.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.0.0/LICENSE +21 -0
- universal_data_cleaner-2.0.0/PKG-INFO +259 -0
- universal_data_cleaner-2.0.0/README.md +226 -0
- universal_data_cleaner-2.0.0/data_cleaner.py +754 -0
- universal_data_cleaner-2.0.0/pyproject.toml +66 -0
- universal_data_cleaner-2.0.0/setup.cfg +4 -0
- universal_data_cleaner-2.0.0/tests/test_data_cleaner.py +195 -0
- universal_data_cleaner-2.0.0/universal_data_cleaner.egg-info/PKG-INFO +259 -0
- universal_data_cleaner-2.0.0/universal_data_cleaner.egg-info/SOURCES.txt +10 -0
- universal_data_cleaner-2.0.0/universal_data_cleaner.egg-info/dependency_links.txt +1 -0
- universal_data_cleaner-2.0.0/universal_data_cleaner.egg-info/requires.txt +4 -0
- universal_data_cleaner-2.0.0/universal_data_cleaner.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Haseeb Khan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: universal-data-cleaner
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Production-ready, scikit-learn-compatible data cleaning transformer for pandas.
|
|
5
|
+
Author: Haseeb Khan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/devlprkhan/universal-data-cleaner
|
|
8
|
+
Project-URL: Repository, https://github.com/devlprkhan/universal-data-cleaner
|
|
9
|
+
Project-URL: Issues, https://github.com/devlprkhan/universal-data-cleaner/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/devlprkhan/universal-data-cleaner/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: data-cleaning,data-quality,data-preprocessing,feature-engineering,machine-learning,pandas,scikit-learn,tabular-data
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: pandas>=2.2
|
|
29
|
+
Requires-Dist: numpy>=1.26
|
|
30
|
+
Requires-Dist: scikit-learn>=1.4
|
|
31
|
+
Requires-Dist: joblib>=1.3
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Universal Data Cleaner
|
|
35
|
+
|
|
36
|
+
[](https://github.com/devlprkhan/universal-data-cleaner/actions/workflows/ci.yml)
|
|
37
|
+
[](https://pypi.org/project/universal-data-cleaner/)
|
|
38
|
+
[](https://www.python.org/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[](https://docs.astral.sh/uv/)
|
|
41
|
+
|
|
42
|
+
A small, production-oriented tabular cleaning transformer for pandas + scikit-learn.
|
|
43
|
+
|
|
44
|
+
It is designed for the part of an ML pipeline that is easy to get wrong: cleaning
|
|
45
|
+
training data and inference data with **exactly the same learned rules**.
|
|
46
|
+
|
|
47
|
+
## Why this exists
|
|
48
|
+
|
|
49
|
+
A common notebook pattern is:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
df["Age"] = df["Age"].fillna(df["Age"].median())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That leaks information when it is run before the train/test split, and it silently
|
|
56
|
+
breaks at inference time because the median is recomputed on the new batch.
|
|
57
|
+
|
|
58
|
+
`DataCleaner` fits on training data, stores the learned values, and reuses them in
|
|
59
|
+
`transform()` and in saved artifacts.
|
|
60
|
+
|
|
61
|
+
## What it handles
|
|
62
|
+
|
|
63
|
+
- numeric coercion and median / mean / constant imputation
|
|
64
|
+
- optional IQR outlier capping (learned from training data only)
|
|
65
|
+
- categorical mode / constant imputation
|
|
66
|
+
- optional unseen-category policy: keep or replace
|
|
67
|
+
- boolean coercion and imputation
|
|
68
|
+
- datetime parsing plus calendar feature extraction
|
|
69
|
+
- optional missing-value indicator columns
|
|
70
|
+
- optional constant-column dropping
|
|
71
|
+
- stable output schema and column order
|
|
72
|
+
- joblib save / load with artifact versioning
|
|
73
|
+
- scikit-learn `Pipeline`, `clone`, and `get_feature_names_out` compatibility
|
|
74
|
+
|
|
75
|
+
The cleaner never mutates the input DataFrame and never drops inference rows.
|
|
76
|
+
|
|
77
|
+
## Install from PyPI
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install universal-data-cleaner
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The distribution name is `universal-data-cleaner`; the import name is
|
|
84
|
+
`data_cleaner`.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from data_cleaner import CleanerConfig, DataCleaner
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Development setup with uv
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/devlprkhan/universal-data-cleaner.git
|
|
94
|
+
cd universal-data-cleaner
|
|
95
|
+
uv sync
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Run the test suite:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv run pytest -q
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Open the reference notebook:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv run jupyter lab Universal_Data_Cleaner.ipynb
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The notebook is also verified headlessly in CI-style environments with
|
|
111
|
+
`nbclient`; both tools are in the `dev` dependency group.
|
|
112
|
+
|
|
113
|
+
### Install directly from GitHub
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv add "git+https://github.com/devlprkhan/universal-data-cleaner.git"
|
|
117
|
+
# or
|
|
118
|
+
pip install "git+https://github.com/devlprkhan/universal-data-cleaner.git"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Quick start
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import pandas as pd
|
|
125
|
+
from data_cleaner import CleanerConfig, DataCleaner
|
|
126
|
+
|
|
127
|
+
train = pd.DataFrame(
|
|
128
|
+
{
|
|
129
|
+
"age": [34, 41, None, 29],
|
|
130
|
+
"salary": [70_000, 82_000, 91_000, 55_000],
|
|
131
|
+
"dept": ["IT", "HR", "IT", None],
|
|
132
|
+
"joined": ["2022-01-03", "2021-11-20", "2023-04-01", "bad-date"],
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
config = CleanerConfig(
|
|
137
|
+
datetime_columns=("joined",),
|
|
138
|
+
add_missing_indicators=True,
|
|
139
|
+
drop_constant_columns=True,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
cleaner = DataCleaner(config).fit(train)
|
|
143
|
+
train_clean = cleaner.transform(train)
|
|
144
|
+
print(cleaner.cleaning_summary())
|
|
145
|
+
print(cleaner.outlier_summary())
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For a column that should be numeric even though it arrived as dirty strings:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
config = CleanerConfig(
|
|
152
|
+
numeric_columns=("salary",), # force numeric parsing
|
|
153
|
+
datetime_columns=("joined",), # parse dates instead of treating them as categories
|
|
154
|
+
unseen_category="keep", # or "fill" to replace unseen labels
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Leak-safe workflow
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from sklearn.model_selection import train_test_split
|
|
162
|
+
|
|
163
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
164
|
+
X, y, test_size=0.2, random_state=42
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
cleaner = DataCleaner(config).fit(X_train)
|
|
168
|
+
|
|
169
|
+
X_train_clean = cleaner.transform(X_train)
|
|
170
|
+
X_test_clean = cleaner.transform(X_test) # uses train statistics only
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Save the fitted cleaner with the model artifact:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
cleaner.save("artifacts/data_cleaner.joblib")
|
|
177
|
+
restored = DataCleaner.load("artifacts/data_cleaner.joblib")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## scikit-learn Pipeline
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from sklearn.compose import ColumnTransformer
|
|
184
|
+
from sklearn.linear_model import Ridge
|
|
185
|
+
from sklearn.pipeline import Pipeline
|
|
186
|
+
from sklearn.preprocessing import OneHotEncoder, StandardScaler
|
|
187
|
+
|
|
188
|
+
numeric = ["age", "salary", "joined__year"]
|
|
189
|
+
categorical = ["dept"]
|
|
190
|
+
boolean = []
|
|
191
|
+
|
|
192
|
+
preprocess = ColumnTransformer(
|
|
193
|
+
[
|
|
194
|
+
("num", StandardScaler(), numeric),
|
|
195
|
+
("cat", OneHotEncoder(handle_unknown="ignore"), categorical),
|
|
196
|
+
("bool", "passthrough", boolean),
|
|
197
|
+
],
|
|
198
|
+
remainder="drop",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
pipeline = Pipeline(
|
|
202
|
+
[
|
|
203
|
+
("cleaner", DataCleaner(config)),
|
|
204
|
+
("features", preprocess),
|
|
205
|
+
("model", Ridge(alpha=1.0)),
|
|
206
|
+
]
|
|
207
|
+
)
|
|
208
|
+
pipeline.fit(X_train, y_train)
|
|
209
|
+
predictions = pipeline.predict(X_test)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Public API
|
|
213
|
+
|
|
214
|
+
| Name | Purpose |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `CleanerConfig` | Validated cleaning configuration |
|
|
217
|
+
| `DataCleaner` | Fit/transform cleaner with save/load |
|
|
218
|
+
| `profile_dataframe` | Column-level missing/type/cardinality profile |
|
|
219
|
+
| `dataset_summary` | Row/column/missing/duplicate summary |
|
|
220
|
+
| `drop_duplicate_rows` | Explicit duplicate removal helper |
|
|
221
|
+
| `assert_frame_clean` | Guard against missing/infinite output values |
|
|
222
|
+
|
|
223
|
+
## Important production notes
|
|
224
|
+
|
|
225
|
+
- Fit the cleaner **after** splitting and **only** on training data.
|
|
226
|
+
- Do not pass IDs, free-text notes, or the target column to the cleaner.
|
|
227
|
+
- Keep `DataSource`-style constant columns out of the model, or let
|
|
228
|
+
`drop_constant_columns=True` remove them.
|
|
229
|
+
- `unseen_category="keep"` is the default because one-hot encoders with
|
|
230
|
+
`handle_unknown="ignore"` can handle an unseen label safely.
|
|
231
|
+
- Datetime columns are replaced by calendar features. Their missing values are
|
|
232
|
+
represented by the generated feature NaNs plus an optional missing indicator.
|
|
233
|
+
- IQR capping is skipped automatically when the IQR is zero, so constant or
|
|
234
|
+
near-constant numeric columns are not accidentally destroyed.
|
|
235
|
+
|
|
236
|
+
## Project layout
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
.
|
|
240
|
+
├── data_cleaner.py # reusable library module
|
|
241
|
+
├── Universal_Data_Cleaner.ipynb # end-to-end reference workflow
|
|
242
|
+
├── tests/test_data_cleaner.py # unit and integration tests
|
|
243
|
+
├── pyproject.toml # package metadata and dev tooling
|
|
244
|
+
├── uv.lock # reproducible dependency lock
|
|
245
|
+
├── README.md
|
|
246
|
+
├── CHANGELOG.md
|
|
247
|
+
├── CONTRIBUTING.md
|
|
248
|
+
├── SECURITY.md
|
|
249
|
+
└── LICENSE
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the local
|
|
255
|
+
setup, test command, and pull-request expectations.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Universal Data Cleaner
|
|
2
|
+
|
|
3
|
+
[](https://github.com/devlprkhan/universal-data-cleaner/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/universal-data-cleaner/)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://docs.astral.sh/uv/)
|
|
8
|
+
|
|
9
|
+
A small, production-oriented tabular cleaning transformer for pandas + scikit-learn.
|
|
10
|
+
|
|
11
|
+
It is designed for the part of an ML pipeline that is easy to get wrong: cleaning
|
|
12
|
+
training data and inference data with **exactly the same learned rules**.
|
|
13
|
+
|
|
14
|
+
## Why this exists
|
|
15
|
+
|
|
16
|
+
A common notebook pattern is:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
df["Age"] = df["Age"].fillna(df["Age"].median())
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That leaks information when it is run before the train/test split, and it silently
|
|
23
|
+
breaks at inference time because the median is recomputed on the new batch.
|
|
24
|
+
|
|
25
|
+
`DataCleaner` fits on training data, stores the learned values, and reuses them in
|
|
26
|
+
`transform()` and in saved artifacts.
|
|
27
|
+
|
|
28
|
+
## What it handles
|
|
29
|
+
|
|
30
|
+
- numeric coercion and median / mean / constant imputation
|
|
31
|
+
- optional IQR outlier capping (learned from training data only)
|
|
32
|
+
- categorical mode / constant imputation
|
|
33
|
+
- optional unseen-category policy: keep or replace
|
|
34
|
+
- boolean coercion and imputation
|
|
35
|
+
- datetime parsing plus calendar feature extraction
|
|
36
|
+
- optional missing-value indicator columns
|
|
37
|
+
- optional constant-column dropping
|
|
38
|
+
- stable output schema and column order
|
|
39
|
+
- joblib save / load with artifact versioning
|
|
40
|
+
- scikit-learn `Pipeline`, `clone`, and `get_feature_names_out` compatibility
|
|
41
|
+
|
|
42
|
+
The cleaner never mutates the input DataFrame and never drops inference rows.
|
|
43
|
+
|
|
44
|
+
## Install from PyPI
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install universal-data-cleaner
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The distribution name is `universal-data-cleaner`; the import name is
|
|
51
|
+
`data_cleaner`.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from data_cleaner import CleanerConfig, DataCleaner
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development setup with uv
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/devlprkhan/universal-data-cleaner.git
|
|
61
|
+
cd universal-data-cleaner
|
|
62
|
+
uv sync
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Run the test suite:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv run pytest -q
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Open the reference notebook:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uv run jupyter lab Universal_Data_Cleaner.ipynb
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The notebook is also verified headlessly in CI-style environments with
|
|
78
|
+
`nbclient`; both tools are in the `dev` dependency group.
|
|
79
|
+
|
|
80
|
+
### Install directly from GitHub
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv add "git+https://github.com/devlprkhan/universal-data-cleaner.git"
|
|
84
|
+
# or
|
|
85
|
+
pip install "git+https://github.com/devlprkhan/universal-data-cleaner.git"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick start
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import pandas as pd
|
|
92
|
+
from data_cleaner import CleanerConfig, DataCleaner
|
|
93
|
+
|
|
94
|
+
train = pd.DataFrame(
|
|
95
|
+
{
|
|
96
|
+
"age": [34, 41, None, 29],
|
|
97
|
+
"salary": [70_000, 82_000, 91_000, 55_000],
|
|
98
|
+
"dept": ["IT", "HR", "IT", None],
|
|
99
|
+
"joined": ["2022-01-03", "2021-11-20", "2023-04-01", "bad-date"],
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
config = CleanerConfig(
|
|
104
|
+
datetime_columns=("joined",),
|
|
105
|
+
add_missing_indicators=True,
|
|
106
|
+
drop_constant_columns=True,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
cleaner = DataCleaner(config).fit(train)
|
|
110
|
+
train_clean = cleaner.transform(train)
|
|
111
|
+
print(cleaner.cleaning_summary())
|
|
112
|
+
print(cleaner.outlier_summary())
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For a column that should be numeric even though it arrived as dirty strings:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
config = CleanerConfig(
|
|
119
|
+
numeric_columns=("salary",), # force numeric parsing
|
|
120
|
+
datetime_columns=("joined",), # parse dates instead of treating them as categories
|
|
121
|
+
unseen_category="keep", # or "fill" to replace unseen labels
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Leak-safe workflow
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from sklearn.model_selection import train_test_split
|
|
129
|
+
|
|
130
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
131
|
+
X, y, test_size=0.2, random_state=42
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
cleaner = DataCleaner(config).fit(X_train)
|
|
135
|
+
|
|
136
|
+
X_train_clean = cleaner.transform(X_train)
|
|
137
|
+
X_test_clean = cleaner.transform(X_test) # uses train statistics only
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Save the fitted cleaner with the model artifact:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
cleaner.save("artifacts/data_cleaner.joblib")
|
|
144
|
+
restored = DataCleaner.load("artifacts/data_cleaner.joblib")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## scikit-learn Pipeline
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from sklearn.compose import ColumnTransformer
|
|
151
|
+
from sklearn.linear_model import Ridge
|
|
152
|
+
from sklearn.pipeline import Pipeline
|
|
153
|
+
from sklearn.preprocessing import OneHotEncoder, StandardScaler
|
|
154
|
+
|
|
155
|
+
numeric = ["age", "salary", "joined__year"]
|
|
156
|
+
categorical = ["dept"]
|
|
157
|
+
boolean = []
|
|
158
|
+
|
|
159
|
+
preprocess = ColumnTransformer(
|
|
160
|
+
[
|
|
161
|
+
("num", StandardScaler(), numeric),
|
|
162
|
+
("cat", OneHotEncoder(handle_unknown="ignore"), categorical),
|
|
163
|
+
("bool", "passthrough", boolean),
|
|
164
|
+
],
|
|
165
|
+
remainder="drop",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
pipeline = Pipeline(
|
|
169
|
+
[
|
|
170
|
+
("cleaner", DataCleaner(config)),
|
|
171
|
+
("features", preprocess),
|
|
172
|
+
("model", Ridge(alpha=1.0)),
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
pipeline.fit(X_train, y_train)
|
|
176
|
+
predictions = pipeline.predict(X_test)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Public API
|
|
180
|
+
|
|
181
|
+
| Name | Purpose |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| `CleanerConfig` | Validated cleaning configuration |
|
|
184
|
+
| `DataCleaner` | Fit/transform cleaner with save/load |
|
|
185
|
+
| `profile_dataframe` | Column-level missing/type/cardinality profile |
|
|
186
|
+
| `dataset_summary` | Row/column/missing/duplicate summary |
|
|
187
|
+
| `drop_duplicate_rows` | Explicit duplicate removal helper |
|
|
188
|
+
| `assert_frame_clean` | Guard against missing/infinite output values |
|
|
189
|
+
|
|
190
|
+
## Important production notes
|
|
191
|
+
|
|
192
|
+
- Fit the cleaner **after** splitting and **only** on training data.
|
|
193
|
+
- Do not pass IDs, free-text notes, or the target column to the cleaner.
|
|
194
|
+
- Keep `DataSource`-style constant columns out of the model, or let
|
|
195
|
+
`drop_constant_columns=True` remove them.
|
|
196
|
+
- `unseen_category="keep"` is the default because one-hot encoders with
|
|
197
|
+
`handle_unknown="ignore"` can handle an unseen label safely.
|
|
198
|
+
- Datetime columns are replaced by calendar features. Their missing values are
|
|
199
|
+
represented by the generated feature NaNs plus an optional missing indicator.
|
|
200
|
+
- IQR capping is skipped automatically when the IQR is zero, so constant or
|
|
201
|
+
near-constant numeric columns are not accidentally destroyed.
|
|
202
|
+
|
|
203
|
+
## Project layout
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
.
|
|
207
|
+
├── data_cleaner.py # reusable library module
|
|
208
|
+
├── Universal_Data_Cleaner.ipynb # end-to-end reference workflow
|
|
209
|
+
├── tests/test_data_cleaner.py # unit and integration tests
|
|
210
|
+
├── pyproject.toml # package metadata and dev tooling
|
|
211
|
+
├── uv.lock # reproducible dependency lock
|
|
212
|
+
├── README.md
|
|
213
|
+
├── CHANGELOG.md
|
|
214
|
+
├── CONTRIBUTING.md
|
|
215
|
+
├── SECURITY.md
|
|
216
|
+
└── LICENSE
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the local
|
|
222
|
+
setup, test command, and pull-request expectations.
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
Released under the [MIT License](LICENSE).
|