edaprep 0.2.0__tar.gz → 0.2.2__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.
- {edaprep-0.2.0/src/edaprep.egg-info → edaprep-0.2.2}/PKG-INFO +29 -4
- {edaprep-0.2.0 → edaprep-0.2.2}/README.md +28 -3
- edaprep-0.2.2/src/edaprep/_version.py +1 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/backends/base.py +2 -6
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/backends/pandas_backend.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/base.py +1 -2
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/pipeline.py +3 -9
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/analyzer.py +2 -6
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/categorical.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/correlation.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/numerical.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/target.py +3 -9
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/decisions.py +3 -9
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/planner.py +3 -9
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/rules.py +179 -46
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/__init__.py +2 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/casting.py +3 -9
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/datetime_features.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/encoding.py +120 -33
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/missing.py +3 -9
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/outliers.py +4 -8
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/selection.py +5 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/text.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/transformations.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/column_types.py +2 -5
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/profiler.py +6 -19
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/statistics.py +4 -11
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/reporting/html.py +1 -4
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/reporting/report.py +6 -16
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/visualization/plots.py +5 -9
- {edaprep-0.2.0 → edaprep-0.2.2/src/edaprep.egg-info}/PKG-INFO +29 -4
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_leakage.py +20 -17
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_pipeline.py +194 -16
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_statistics.py +1 -3
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_transformers.py +60 -45
- edaprep-0.2.0/src/edaprep/_version.py +0 -1
- {edaprep-0.2.0 → edaprep-0.2.2}/LICENSE +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/pyproject.toml +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/setup.cfg +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/backends/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/config.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/context.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/journal.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/outliers.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/exceptions.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/duplicates.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/scaling.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/quality.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/py.typed +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/reporting/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/types.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/visualization/__init__.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/SOURCES.txt +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/dependency_links.txt +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/requires.txt +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/top_level.txt +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_column_types.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_eda.py +0 -0
- {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_profiler.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: edaprep
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Transparent, leakage-safe EDA and ML preprocessing with an explainable planner.
|
|
5
5
|
Author-email: bijay <bijaybeezoe@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -125,6 +125,8 @@ pip install "edaprep[all]"
|
|
|
125
125
|
|
|
126
126
|
Python 3.9–3.13, tested on Linux, macOS and Windows.
|
|
127
127
|
|
|
128
|
+
Would rather try it than install it? There is a [runnable notebook on Kaggle](https://www.kaggle.com/code/bijaybeezoe/every-preprocessing-decision-with-its-reason) that needs no setup — it measures two preprocessing leaks on Telco Churn, one worth `+0.000001` and one scoring a perfect `1.00000` AUC on pure noise, then shows what this library does about them.
|
|
129
|
+
|
|
128
130
|
---
|
|
129
131
|
|
|
130
132
|
## What it does
|
|
@@ -268,12 +270,13 @@ looked promising was slower than pandas.
|
|
|
268
270
|
|
|
269
271
|
| | |
|
|
270
272
|
|---|---|
|
|
271
|
-
| [
|
|
273
|
+
| [Design rationale](docs/design-rationale.md) | the workflow tabular notebooks converge on, where it reliably goes wrong, and how each design decision follows |
|
|
272
274
|
| [Architecture](docs/architecture.md) | package design, the planner, execution model |
|
|
273
275
|
| [User guide](docs/guide.md) | installation to production, with the train/test workflow |
|
|
274
276
|
| [Performance](docs/performance.md) | benchmarks, method, and what optimisation actually changed |
|
|
275
277
|
| [Extending](docs/extending.md) | custom transformers, rules and backends |
|
|
276
278
|
| [Example](examples/end_to_end.py) | raw dataset to ML-ready, end to end |
|
|
279
|
+
| [Runnable notebook](https://www.kaggle.com/code/bijaybeezoe/every-preprocessing-decision-with-its-reason) | Kaggle, no install: the `select_dtypes` failure and two leaks, measured on Telco Churn |
|
|
277
280
|
|
|
278
281
|
---
|
|
279
282
|
|
|
@@ -296,15 +299,37 @@ CI actually enforces, and points at issues scoped so that each one names the fil
|
|
|
296
299
|
change and the test to write.
|
|
297
300
|
|
|
298
301
|
Issues labelled [`good first issue`](https://github.com/bijay-odyssey/edaprep/labels/good%20first%20issue)
|
|
299
|
-
are a deliberate starting set. Comment to claim
|
|
302
|
+
are a deliberate starting set. **Comment on one to claim it before you start** — two
|
|
303
|
+
people once fixed the same issue eight hours apart, and one of them had to be turned
|
|
304
|
+
away.
|
|
300
305
|
|
|
301
306
|
```bash
|
|
302
307
|
pip install -e ".[dev]"
|
|
303
|
-
pytest #
|
|
308
|
+
pytest # 367 tests, ~15s
|
|
304
309
|
ruff check src/ tests/ benchmarks/ examples/
|
|
305
310
|
python benchmarks/bench.py
|
|
306
311
|
```
|
|
307
312
|
|
|
313
|
+
## Contributors
|
|
314
|
+
|
|
315
|
+
This library argues that a decision is worth little without the reasoning behind it,
|
|
316
|
+
so it would be odd to credit only the diffs. Measurement, review and design that
|
|
317
|
+
changed what shipped are listed here alongside the commits.
|
|
318
|
+
|
|
319
|
+
| | |
|
|
320
|
+
|---|---|
|
|
321
|
+
| [@qiaobochi040726-source](https://github.com/qiaobochi040726-source) | Removed `Config.n_jobs`, a setting the library accepted and never read ([#11](https://github.com/bijay-odyssey/edaprep/pull/11)) |
|
|
322
|
+
| [@zbs-ops](https://github.com/zbs-ops) | Benchmarked parallelising the per-column profiling loop, which is what decided [#9](https://github.com/bijay-odyssey/edaprep/issues/9) — no commit, and the reason the parameter went |
|
|
323
|
+
| [@Jeferson681](https://github.com/Jeferson681) | Made the missing-indicator and high-missing rules see gaps introduced at the cast step ([#16](https://github.com/bijay-odyssey/edaprep/pull/16)) |
|
|
324
|
+
| [@luziyi123448-gif](https://github.com/luziyi123448-gif) | Independent fix for the same issue whose design was the better one; that design shipped in [#18](https://github.com/bijay-odyssey/edaprep/issues/18) ([#17](https://github.com/bijay-odyssey/edaprep/pull/17)) |
|
|
325
|
+
| [@LeonxLJX](https://github.com/LeonxLJX) | Formatted the tree and made `ruff format` a real CI gate rather than an advisory one ([#25](https://github.com/bijay-odyssey/edaprep/pull/25)) |
|
|
326
|
+
| [@TrueFurina](https://github.com/TrueFurina) | First to run the Codecov upload path, which is how we learned it fails silently from a fork with no token — CI green, nothing reported; coverage moved to the job summary instead ([#21](https://github.com/bijay-odyssey/edaprep/pull/21)) |
|
|
327
|
+
|
|
328
|
+
[GitHub's contributors graph](https://github.com/bijay-odyssey/edaprep/graphs/contributors)
|
|
329
|
+
counts commits on `main`, so it cannot show a benchmark that settled an argument or a
|
|
330
|
+
review that caught a bug. This table can. If you contributed something that changed the
|
|
331
|
+
library and you are not on it, that is an oversight worth an issue — please open one.
|
|
332
|
+
|
|
308
333
|
## Licence
|
|
309
334
|
|
|
310
335
|
MIT.
|
|
@@ -79,6 +79,8 @@ pip install "edaprep[all]"
|
|
|
79
79
|
|
|
80
80
|
Python 3.9–3.13, tested on Linux, macOS and Windows.
|
|
81
81
|
|
|
82
|
+
Would rather try it than install it? There is a [runnable notebook on Kaggle](https://www.kaggle.com/code/bijaybeezoe/every-preprocessing-decision-with-its-reason) that needs no setup — it measures two preprocessing leaks on Telco Churn, one worth `+0.000001` and one scoring a perfect `1.00000` AUC on pure noise, then shows what this library does about them.
|
|
83
|
+
|
|
82
84
|
---
|
|
83
85
|
|
|
84
86
|
## What it does
|
|
@@ -222,12 +224,13 @@ looked promising was slower than pandas.
|
|
|
222
224
|
|
|
223
225
|
| | |
|
|
224
226
|
|---|---|
|
|
225
|
-
| [
|
|
227
|
+
| [Design rationale](docs/design-rationale.md) | the workflow tabular notebooks converge on, where it reliably goes wrong, and how each design decision follows |
|
|
226
228
|
| [Architecture](docs/architecture.md) | package design, the planner, execution model |
|
|
227
229
|
| [User guide](docs/guide.md) | installation to production, with the train/test workflow |
|
|
228
230
|
| [Performance](docs/performance.md) | benchmarks, method, and what optimisation actually changed |
|
|
229
231
|
| [Extending](docs/extending.md) | custom transformers, rules and backends |
|
|
230
232
|
| [Example](examples/end_to_end.py) | raw dataset to ML-ready, end to end |
|
|
233
|
+
| [Runnable notebook](https://www.kaggle.com/code/bijaybeezoe/every-preprocessing-decision-with-its-reason) | Kaggle, no install: the `select_dtypes` failure and two leaks, measured on Telco Churn |
|
|
231
234
|
|
|
232
235
|
---
|
|
233
236
|
|
|
@@ -250,15 +253,37 @@ CI actually enforces, and points at issues scoped so that each one names the fil
|
|
|
250
253
|
change and the test to write.
|
|
251
254
|
|
|
252
255
|
Issues labelled [`good first issue`](https://github.com/bijay-odyssey/edaprep/labels/good%20first%20issue)
|
|
253
|
-
are a deliberate starting set. Comment to claim
|
|
256
|
+
are a deliberate starting set. **Comment on one to claim it before you start** — two
|
|
257
|
+
people once fixed the same issue eight hours apart, and one of them had to be turned
|
|
258
|
+
away.
|
|
254
259
|
|
|
255
260
|
```bash
|
|
256
261
|
pip install -e ".[dev]"
|
|
257
|
-
pytest #
|
|
262
|
+
pytest # 367 tests, ~15s
|
|
258
263
|
ruff check src/ tests/ benchmarks/ examples/
|
|
259
264
|
python benchmarks/bench.py
|
|
260
265
|
```
|
|
261
266
|
|
|
267
|
+
## Contributors
|
|
268
|
+
|
|
269
|
+
This library argues that a decision is worth little without the reasoning behind it,
|
|
270
|
+
so it would be odd to credit only the diffs. Measurement, review and design that
|
|
271
|
+
changed what shipped are listed here alongside the commits.
|
|
272
|
+
|
|
273
|
+
| | |
|
|
274
|
+
|---|---|
|
|
275
|
+
| [@qiaobochi040726-source](https://github.com/qiaobochi040726-source) | Removed `Config.n_jobs`, a setting the library accepted and never read ([#11](https://github.com/bijay-odyssey/edaprep/pull/11)) |
|
|
276
|
+
| [@zbs-ops](https://github.com/zbs-ops) | Benchmarked parallelising the per-column profiling loop, which is what decided [#9](https://github.com/bijay-odyssey/edaprep/issues/9) — no commit, and the reason the parameter went |
|
|
277
|
+
| [@Jeferson681](https://github.com/Jeferson681) | Made the missing-indicator and high-missing rules see gaps introduced at the cast step ([#16](https://github.com/bijay-odyssey/edaprep/pull/16)) |
|
|
278
|
+
| [@luziyi123448-gif](https://github.com/luziyi123448-gif) | Independent fix for the same issue whose design was the better one; that design shipped in [#18](https://github.com/bijay-odyssey/edaprep/issues/18) ([#17](https://github.com/bijay-odyssey/edaprep/pull/17)) |
|
|
279
|
+
| [@LeonxLJX](https://github.com/LeonxLJX) | Formatted the tree and made `ruff format` a real CI gate rather than an advisory one ([#25](https://github.com/bijay-odyssey/edaprep/pull/25)) |
|
|
280
|
+
| [@TrueFurina](https://github.com/TrueFurina) | First to run the Codecov upload path, which is how we learned it fails silently from a fork with no token — CI green, nothing reported; coverage moved to the job summary instead ([#21](https://github.com/bijay-odyssey/edaprep/pull/21)) |
|
|
281
|
+
|
|
282
|
+
[GitHub's contributors graph](https://github.com/bijay-odyssey/edaprep/graphs/contributors)
|
|
283
|
+
counts commits on `main`, so it cannot show a benchmark that settled an argument or a
|
|
284
|
+
review that caught a bug. This table can. If you contributed something that changed the
|
|
285
|
+
library and you are not on it, that is an oversight worth an issue — please open one.
|
|
286
|
+
|
|
262
287
|
## Licence
|
|
263
288
|
|
|
264
289
|
MIT.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.2"
|
|
@@ -118,15 +118,11 @@ class Backend(ABC):
|
|
|
118
118
|
"""Quantile matrix of shape ``(len(levels), len(columns))``."""
|
|
119
119
|
|
|
120
120
|
@abstractmethod
|
|
121
|
-
def group_mean(
|
|
122
|
-
self, frame: Any, value_column: str, group_column: str
|
|
123
|
-
) -> Dict[Any, float]:
|
|
121
|
+
def group_mean(self, frame: Any, value_column: str, group_column: str) -> Dict[Any, float]:
|
|
124
122
|
"""Mean of ``value_column`` per level of ``group_column``."""
|
|
125
123
|
|
|
126
124
|
@abstractmethod
|
|
127
|
-
def duplicated_rows(
|
|
128
|
-
self, frame: Any, subset: Optional[Sequence[str]] = None
|
|
129
|
-
) -> np.ndarray:
|
|
125
|
+
def duplicated_rows(self, frame: Any, subset: Optional[Sequence[str]] = None) -> np.ndarray:
|
|
130
126
|
"""Boolean array: True where the row repeats an earlier one."""
|
|
131
127
|
|
|
132
128
|
# -- construction --------------------------------------------------------------------
|
|
@@ -109,9 +109,7 @@ class PandasBackend(Backend):
|
|
|
109
109
|
return frame.duplicated(subset=list(subset) if subset else None).to_numpy()
|
|
110
110
|
except TypeError:
|
|
111
111
|
return (
|
|
112
|
-
frame.astype(str)
|
|
113
|
-
.duplicated(subset=list(subset) if subset else None)
|
|
114
|
-
.to_numpy()
|
|
112
|
+
frame.astype(str).duplicated(subset=list(subset) if subset else None).to_numpy()
|
|
115
113
|
)
|
|
116
114
|
|
|
117
115
|
# -- construction --------------------------------------------------------------------
|
|
@@ -227,8 +227,7 @@ class Transformer(ABC):
|
|
|
227
227
|
) -> FitContext:
|
|
228
228
|
if not isinstance(X, pd.DataFrame):
|
|
229
229
|
raise TypeError(
|
|
230
|
-
f"{type(self).__name__}.fit expects a pandas DataFrame, got "
|
|
231
|
-
f"{type(X).__name__}."
|
|
230
|
+
f"{type(self).__name__}.fit expects a pandas DataFrame, got {type(X).__name__}."
|
|
232
231
|
)
|
|
233
232
|
if self.uses_target and y is None:
|
|
234
233
|
raise LeakageError.target_required(type(self).__name__)
|
|
@@ -226,9 +226,7 @@ class Pipeline(Transformer):
|
|
|
226
226
|
self._context_ = context
|
|
227
227
|
return super().fit_transform(X, y, context)
|
|
228
228
|
|
|
229
|
-
def transform(
|
|
230
|
-
self, X: pd.DataFrame, context: Optional[FitContext] = None
|
|
231
|
-
) -> pd.DataFrame:
|
|
229
|
+
def transform(self, X: pd.DataFrame, context: Optional[FitContext] = None) -> pd.DataFrame:
|
|
232
230
|
check_is_fitted(self)
|
|
233
231
|
context = context or getattr(self, "_context_", None) or FitContext(config=self.config)
|
|
234
232
|
# Only the most recent transform is described in the report; otherwise calling
|
|
@@ -437,9 +435,7 @@ class AutoPipeline(Pipeline):
|
|
|
437
435
|
self.report_ = self.report()
|
|
438
436
|
return out
|
|
439
437
|
|
|
440
|
-
def transform(
|
|
441
|
-
self, X: pd.DataFrame, context: Optional[FitContext] = None
|
|
442
|
-
) -> pd.DataFrame:
|
|
438
|
+
def transform(self, X: pd.DataFrame, context: Optional[FitContext] = None) -> pd.DataFrame:
|
|
443
439
|
out = super().transform(X, context)
|
|
444
440
|
self.report_ = self.report()
|
|
445
441
|
return out
|
|
@@ -497,9 +493,7 @@ class AutoPipeline(Pipeline):
|
|
|
497
493
|
"rule": d.rule,
|
|
498
494
|
"source": d.source,
|
|
499
495
|
}
|
|
500
|
-
for d in sorted(
|
|
501
|
-
self.plan_.decisions, key=lambda d: (d.column, d.stage.order)
|
|
502
|
-
)
|
|
496
|
+
for d in sorted(self.plan_.decisions, key=lambda d: (d.column, d.stage.order))
|
|
503
497
|
]
|
|
504
498
|
)
|
|
505
499
|
|
|
@@ -137,9 +137,7 @@ class EDAReport:
|
|
|
137
137
|
if self.issues:
|
|
138
138
|
parts.append("<h2>Findings</h2>")
|
|
139
139
|
for issue in sorted(self.issues, key=lambda i: -i.severity.rank):
|
|
140
|
-
parts.append(
|
|
141
|
-
f"<div class='w {_e(issue.severity)}'>{_e(issue.message)}</div>"
|
|
142
|
-
)
|
|
140
|
+
parts.append(f"<div class='w {_e(issue.severity)}'>{_e(issue.message)}</div>")
|
|
143
141
|
parts.append("</main>")
|
|
144
142
|
html = "\n".join(parts)
|
|
145
143
|
if path:
|
|
@@ -262,9 +260,7 @@ class EDA:
|
|
|
262
260
|
def profile_(self) -> DatasetProfile:
|
|
263
261
|
"""The profile, computed once and reused."""
|
|
264
262
|
if self._profile is None:
|
|
265
|
-
self._profile = profile_dataset(
|
|
266
|
-
self.data, target=self.target, config=self.config
|
|
267
|
-
)
|
|
263
|
+
self._profile = profile_dataset(self.data, target=self.target, config=self.config)
|
|
268
264
|
return self._profile
|
|
269
265
|
|
|
270
266
|
def analyze(
|
|
@@ -72,9 +72,7 @@ def categorical_summary(
|
|
|
72
72
|
def _note(cp, high_cardinality: int, n_rare: int) -> str:
|
|
73
73
|
notes = []
|
|
74
74
|
if cp.n_unique > high_cardinality:
|
|
75
|
-
notes.append(
|
|
76
|
-
f"high cardinality: one-hot would add {cp.n_unique} columns"
|
|
77
|
-
)
|
|
75
|
+
notes.append(f"high cardinality: one-hot would add {cp.n_unique} columns")
|
|
78
76
|
if cp.is_near_constant:
|
|
79
77
|
notes.append(f"near-constant ({cp.modal_frequency:.1%} one value)")
|
|
80
78
|
if n_rare:
|
|
@@ -133,9 +133,7 @@ def variance_inflation(
|
|
|
133
133
|
|
|
134
134
|
# copy=True for the same reason as in preprocessing/selection.py: pandas 2.3+
|
|
135
135
|
# returns a read-only view, and `fill_diagonal` writes in place.
|
|
136
|
-
corr = frame.corr(method="pearson", numeric_only=True).to_numpy(
|
|
137
|
-
dtype=np.float64, copy=True
|
|
138
|
-
)
|
|
136
|
+
corr = frame.corr(method="pearson", numeric_only=True).to_numpy(dtype=np.float64, copy=True)
|
|
139
137
|
corr = np.nan_to_num(corr, nan=0.0)
|
|
140
138
|
np.fill_diagonal(corr, 1.0)
|
|
141
139
|
|
|
@@ -78,9 +78,7 @@ def numerical_summary(
|
|
|
78
78
|
return frame
|
|
79
79
|
# Most-skewed first: those are the columns a reader most needs to look at.
|
|
80
80
|
frame["_sort"] = frame["skew"].abs().fillna(-1)
|
|
81
|
-
frame = frame.sort_values("_sort", ascending=False, ignore_index=True).drop(
|
|
82
|
-
columns="_sort"
|
|
83
|
-
)
|
|
81
|
+
frame = frame.sort_values("_sort", ascending=False, ignore_index=True).drop(columns="_sort")
|
|
84
82
|
numeric_cols = frame.select_dtypes(include="number").columns
|
|
85
83
|
frame[numeric_cols] = frame[numeric_cols].round(4)
|
|
86
84
|
return frame
|
|
@@ -30,9 +30,7 @@ from ..types import NUMERIC_LIKE, SemanticType
|
|
|
30
30
|
__all__ = ["target_summary", "target_relationships", "benjamini_hochberg"]
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def target_summary(
|
|
34
|
-
data: pd.DataFrame, profile: DatasetProfile, target: str
|
|
35
|
-
) -> Dict[str, Any]:
|
|
33
|
+
def target_summary(data: pd.DataFrame, profile: DatasetProfile, target: str) -> Dict[str, Any]:
|
|
36
34
|
"""Distribution of the target, plus the imbalance measurement."""
|
|
37
35
|
series = data[target]
|
|
38
36
|
out: Dict[str, Any] = {
|
|
@@ -121,9 +119,7 @@ def target_relationships(
|
|
|
121
119
|
"column": name,
|
|
122
120
|
"semantic": str(cp.semantic),
|
|
123
121
|
"association": (
|
|
124
|
-
round(cp.target_association, 4)
|
|
125
|
-
if cp.target_association is not None
|
|
126
|
-
else None
|
|
122
|
+
round(cp.target_association, 4) if cp.target_association is not None else None
|
|
127
123
|
),
|
|
128
124
|
"measure": cp.target_association_kind,
|
|
129
125
|
}
|
|
@@ -145,9 +141,7 @@ def target_relationships(
|
|
|
145
141
|
frame["p_value"] = frame["p_value"].round(6)
|
|
146
142
|
|
|
147
143
|
frame["_sort"] = frame["association"].fillna(-1)
|
|
148
|
-
return frame.sort_values("_sort", ascending=False, ignore_index=True).drop(
|
|
149
|
-
columns="_sort"
|
|
150
|
-
)
|
|
144
|
+
return frame.sort_values("_sort", ascending=False, ignore_index=True).drop(columns="_sort")
|
|
151
145
|
|
|
152
146
|
|
|
153
147
|
def _test(
|
|
@@ -169,9 +169,7 @@ class Plan:
|
|
|
169
169
|
@property
|
|
170
170
|
def decisions(self) -> List[Decision]:
|
|
171
171
|
"""Every decision, including the ones that resolved to doing nothing."""
|
|
172
|
-
return [d for step in self.steps for d in step.decisions] + list(
|
|
173
|
-
self.noop_decisions
|
|
174
|
-
)
|
|
172
|
+
return [d for step in self.steps for d in step.decisions] + list(self.noop_decisions)
|
|
175
173
|
|
|
176
174
|
def for_column(self, column: str) -> List[Decision]:
|
|
177
175
|
"""Every decision affecting ``column``, in stage order."""
|
|
@@ -199,9 +197,7 @@ class Plan:
|
|
|
199
197
|
|
|
200
198
|
@property
|
|
201
199
|
def uses_target(self) -> bool:
|
|
202
|
-
return any(
|
|
203
|
-
d.action in ("encode_target",) for d in self.decisions
|
|
204
|
-
)
|
|
200
|
+
return any(d.action in ("encode_target",) for d in self.decisions)
|
|
205
201
|
|
|
206
202
|
# -- editing --------------------------------------------------------------------
|
|
207
203
|
|
|
@@ -252,9 +248,7 @@ class Plan:
|
|
|
252
248
|
def from_dict(cls, data: Dict[str, Any]) -> "Plan":
|
|
253
249
|
return cls(
|
|
254
250
|
steps=tuple(PlannedStep.from_dict(s) for s in data.get("steps", ())),
|
|
255
|
-
noop_decisions=tuple(
|
|
256
|
-
Decision.from_dict(d) for d in data.get("noop_decisions", ())
|
|
257
|
-
),
|
|
251
|
+
noop_decisions=tuple(Decision.from_dict(d) for d in data.get("noop_decisions", ())),
|
|
258
252
|
target=data.get("target"),
|
|
259
253
|
model_family=data.get("model_family"),
|
|
260
254
|
dropped_columns=dict(data.get("dropped_columns", {})),
|
|
@@ -92,9 +92,7 @@ class Planner:
|
|
|
92
92
|
# DROP_COLUMNS first: a dropped column takes no further part in planning, which
|
|
93
93
|
# keeps the plan free of steps that operate on columns that will not exist.
|
|
94
94
|
for name in feature_columns:
|
|
95
|
-
decision = self.rules.decide(
|
|
96
|
-
Stage.DROP_COLUMNS, profile.columns[name], context
|
|
97
|
-
)
|
|
95
|
+
decision = self.rules.decide(Stage.DROP_COLUMNS, profile.columns[name], context)
|
|
98
96
|
if decision is not None and decision.action == "drop":
|
|
99
97
|
dropped[name] = decision.rationale
|
|
100
98
|
decisions_by_stage.setdefault(Stage.DROP_COLUMNS, []).append(decision)
|
|
@@ -259,9 +257,7 @@ class Planner:
|
|
|
259
257
|
columns = ()
|
|
260
258
|
|
|
261
259
|
if stage is Stage.OUTLIERS:
|
|
262
|
-
params["per_column_method"] = {
|
|
263
|
-
d.column: d.params.get("method") for d in decisions
|
|
264
|
-
}
|
|
260
|
+
params["per_column_method"] = {d.column: d.params.get("method") for d in decisions}
|
|
265
261
|
params["per_column_strategy"] = {
|
|
266
262
|
d.column: d.params.get("strategy") for d in decisions
|
|
267
263
|
}
|
|
@@ -277,9 +273,7 @@ class Planner:
|
|
|
277
273
|
params["threshold"] = decisions[0].params.get("threshold")
|
|
278
274
|
elif stage is Stage.DATETIME:
|
|
279
275
|
explicit = {
|
|
280
|
-
d.column: d.params["features"]
|
|
281
|
-
for d in decisions
|
|
282
|
-
if d.params.get("features")
|
|
276
|
+
d.column: d.params["features"] for d in decisions if d.params.get("features")
|
|
283
277
|
}
|
|
284
278
|
if explicit:
|
|
285
279
|
params["per_column_features"] = explicit
|