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.
Files changed (64) hide show
  1. {edaprep-0.2.0/src/edaprep.egg-info → edaprep-0.2.2}/PKG-INFO +29 -4
  2. {edaprep-0.2.0 → edaprep-0.2.2}/README.md +28 -3
  3. edaprep-0.2.2/src/edaprep/_version.py +1 -0
  4. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/backends/base.py +2 -6
  5. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/backends/pandas_backend.py +1 -3
  6. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/base.py +1 -2
  7. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/pipeline.py +3 -9
  8. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/analyzer.py +2 -6
  9. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/categorical.py +1 -3
  10. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/correlation.py +1 -3
  11. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/numerical.py +1 -3
  12. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/target.py +3 -9
  13. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/decisions.py +3 -9
  14. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/planner.py +3 -9
  15. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/rules.py +179 -46
  16. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/__init__.py +2 -0
  17. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/casting.py +3 -9
  18. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/datetime_features.py +1 -3
  19. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/encoding.py +120 -33
  20. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/missing.py +3 -9
  21. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/outliers.py +4 -8
  22. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/selection.py +5 -3
  23. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/text.py +1 -3
  24. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/transformations.py +1 -3
  25. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/column_types.py +2 -5
  26. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/profiler.py +6 -19
  27. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/statistics.py +4 -11
  28. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/reporting/html.py +1 -4
  29. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/reporting/report.py +6 -16
  30. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/visualization/plots.py +5 -9
  31. {edaprep-0.2.0 → edaprep-0.2.2/src/edaprep.egg-info}/PKG-INFO +29 -4
  32. {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_leakage.py +20 -17
  33. {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_pipeline.py +194 -16
  34. {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_statistics.py +1 -3
  35. {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_transformers.py +60 -45
  36. edaprep-0.2.0/src/edaprep/_version.py +0 -1
  37. {edaprep-0.2.0 → edaprep-0.2.2}/LICENSE +0 -0
  38. {edaprep-0.2.0 → edaprep-0.2.2}/pyproject.toml +0 -0
  39. {edaprep-0.2.0 → edaprep-0.2.2}/setup.cfg +0 -0
  40. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/__init__.py +0 -0
  41. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/backends/__init__.py +0 -0
  42. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/config.py +0 -0
  43. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/__init__.py +0 -0
  44. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/context.py +0 -0
  45. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/core/journal.py +0 -0
  46. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/__init__.py +0 -0
  47. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/eda/outliers.py +0 -0
  48. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/exceptions.py +0 -0
  49. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/planning/__init__.py +0 -0
  50. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/duplicates.py +0 -0
  51. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/preprocessing/scaling.py +0 -0
  52. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/__init__.py +0 -0
  53. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/profiling/quality.py +0 -0
  54. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/py.typed +0 -0
  55. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/reporting/__init__.py +0 -0
  56. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/types.py +0 -0
  57. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep/visualization/__init__.py +0 -0
  58. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/SOURCES.txt +0 -0
  59. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/dependency_links.txt +0 -0
  60. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/requires.txt +0 -0
  61. {edaprep-0.2.0 → edaprep-0.2.2}/src/edaprep.egg-info/top_level.txt +0 -0
  62. {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_column_types.py +0 -0
  63. {edaprep-0.2.0 → edaprep-0.2.2}/tests/test_eda.py +0 -0
  64. {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.0
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
- | [Workflow mining](docs/design-rationale.md) | what 13 repositories revealed, and the 9 defects found |
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 one.
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 # 358 tests, ~15s
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
- | [Workflow mining](docs/design-rationale.md) | what 13 repositories revealed, and the 9 defects found |
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 one.
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 # 358 tests, ~15s
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