AutoCarver 7.2.1__tar.gz → 7.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.
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/binary_carver.py +3 -3
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/continuous_carver.py +4 -4
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/multiclass_carver.py +11 -6
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/utils/base_carver.py +11 -10
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/utils/pretty_print.py +3 -3
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/binary/binary_combination_evaluators.py +11 -4
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/binary/binary_target_rates.py +13 -4
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/continuous/continuous_combination_evaluators.py +19 -11
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/continuous/continuous_target_rates.py +19 -6
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/utils/combination_evaluator.py +61 -32
- autocarver-7.2.2/AutoCarver/combinations/utils/target_rate.py +52 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/discretizer.py +2 -2
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/qualitatives/categorical_discretizer.py +4 -4
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/qualitatives/chained_discretizer.py +4 -8
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/qualitatives/ordinal_discretizer.py +4 -9
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/qualitatives/qualitative_discretizer.py +3 -7
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/quantitatives/continuous_discretizer.py +1 -3
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/quantitatives/quantitative_discretizer.py +4 -6
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/utils/base_discretizer.py +57 -13
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/utils/type_discretizers.py +4 -4
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/utils/base_selector.py +7 -28
- {autocarver-7.2.1 → autocarver-7.2.2}/PKG-INFO +1 -1
- {autocarver-7.2.1 → autocarver-7.2.2}/pyproject.toml +2 -8
- autocarver-7.2.1/AutoCarver/combinations/utils/target_rate.py +0 -30
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/carvers/utils/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/binary/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/continuous/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/utils/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/utils/combinations.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/utils/testing.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/config.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/qualitatives/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/quantitatives/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/utils/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/discretizers/utils/multiprocessing.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/features.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/qualitatives/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/qualitatives/categorical_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/qualitatives/ordinal_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/qualitatives/qualitative_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/quantitatives/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/quantitatives/datetime_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/quantitatives/numerical_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/quantitatives/quantitative_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/utils/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/utils/base_feature.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/utils/grouped_list.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/features/utils/serialization.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/classification_selector.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/filters/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/filters/base_filters.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/filters/qualitative_filters.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/filters/quantitative_filters.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/measures/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/measures/base_measures.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/measures/qualitative_measures.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/measures/quantitative_measures.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/regression_selector.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/utils/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/selectors/utils/pretty_print.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/utils/__init__.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/utils/attributes.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/utils/dependencies.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/utils/extend_docstring.py +0 -0
- {autocarver-7.2.1 → autocarver-7.2.2}/README.md +0 -0
|
@@ -65,7 +65,7 @@ class BinaryCarver(BaseCarver):
|
|
|
65
65
|
config=config,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
def
|
|
68
|
+
def _prepare_samples(self, samples: Samples) -> Samples:
|
|
69
69
|
"""Validates format and content of X and y."""
|
|
70
70
|
if samples.train.y is None:
|
|
71
71
|
raise ValueError(f"[{self.__name__}] y must be provided")
|
|
@@ -73,9 +73,9 @@ class BinaryCarver(BaseCarver):
|
|
|
73
73
|
if not ((0 in y_values) and (1 in y_values)) or len(y_values) != 2:
|
|
74
74
|
raise ValueError(f"[{self.__name__}] y must be a binary Series of 0 and 1 (int or float, not object)")
|
|
75
75
|
|
|
76
|
-
return super().
|
|
76
|
+
return super()._prepare_samples(samples)
|
|
77
77
|
|
|
78
|
-
def _aggregator(self, X: pd.DataFrame, y: pd.Series) -> dict[str, pd.DataFrame | None]:
|
|
78
|
+
def _aggregator(self, X: pd.DataFrame, y: pd.Series) -> dict[str, pd.Series | pd.DataFrame | None]:
|
|
79
79
|
"""Computes crosstabs for specified features and ensures that the crosstab is ordered
|
|
80
80
|
according to the known labels"""
|
|
81
81
|
# checking for empty datasets (dev)
|
|
@@ -63,7 +63,7 @@ class ContinuousCarver(BaseCarver):
|
|
|
63
63
|
config=config,
|
|
64
64
|
)
|
|
65
65
|
|
|
66
|
-
def
|
|
66
|
+
def _prepare_samples(self, samples: Samples) -> Samples:
|
|
67
67
|
"""Validates format and content of X and y."""
|
|
68
68
|
if samples.train.y is None:
|
|
69
69
|
raise ValueError(f"[{self.__name__}] y must be provided")
|
|
@@ -74,9 +74,9 @@ class ContinuousCarver(BaseCarver):
|
|
|
74
74
|
if len(y_values) <= 2:
|
|
75
75
|
raise ValueError(f"[{self.__name__}] provided y is binary, consider using BinaryCarver instead.")
|
|
76
76
|
|
|
77
|
-
return super().
|
|
77
|
+
return super()._prepare_samples(samples)
|
|
78
78
|
|
|
79
|
-
def _aggregator(self, X: pd.DataFrame, y: pd.Series) -> dict[str, pd.DataFrame | None]:
|
|
79
|
+
def _aggregator(self, X: pd.DataFrame, y: pd.Series) -> dict[str, pd.Series | pd.DataFrame | None]:
|
|
80
80
|
"""Computes y values for modalities of specified features and ensures the ordering
|
|
81
81
|
according to the known labels"""
|
|
82
82
|
# checking for empty datasets
|
|
@@ -98,4 +98,4 @@ def get_target_values_by_modality(X: pd.DataFrame, y: pd.Series, feature: BaseFe
|
|
|
98
98
|
|
|
99
99
|
# reindexing to ensure the right order (labels may be None pre-fit; pandas
|
|
100
100
|
# treats None as "no reindex" so the original ordering is kept)
|
|
101
|
-
return yval.reindex(feature.labels, fill_value=[]) # type: ignore
|
|
101
|
+
return yval.reindex(feature.labels, fill_value=[]) # type: ignore
|
|
@@ -53,7 +53,7 @@ class MulticlassCarver(BinaryCarver):
|
|
|
53
53
|
if self.config.copy:
|
|
54
54
|
print("WARNING: can't set copy=True for MulticlassCarver (no inplace DataFrame.assign).")
|
|
55
55
|
|
|
56
|
-
def
|
|
56
|
+
def _prepare_samples(self, samples: Samples) -> Samples:
|
|
57
57
|
"""Validates format and content of X and y."""
|
|
58
58
|
# converting target to str (y is required by Carver.fit)
|
|
59
59
|
if samples.train.y is None:
|
|
@@ -93,11 +93,11 @@ class MulticlassCarver(BinaryCarver):
|
|
|
93
93
|
samples = Samples(train=Sample(X, y), dev=Sample(X_dev, y_dev))
|
|
94
94
|
|
|
95
95
|
# preparing datasets and checking for wrong values
|
|
96
|
-
samples = self.
|
|
96
|
+
samples = self._prepare_samples(samples)
|
|
97
97
|
|
|
98
|
-
# getting distinct y classes (
|
|
99
|
-
|
|
100
|
-
y_classes = sorted(samples.train.y.unique().tolist())[1:] #
|
|
98
|
+
# getting distinct y classes (_prepare_samples raises if y is missing)
|
|
99
|
+
# removing one of the classes
|
|
100
|
+
y_classes = sorted(samples.train.y.unique().tolist())[1:] # type: ignore
|
|
101
101
|
|
|
102
102
|
# adding versionned features
|
|
103
103
|
self.features.add_feature_versions(y_classes)
|
|
@@ -125,7 +125,12 @@ class MulticlassCarver(BinaryCarver):
|
|
|
125
125
|
config=replace(self.config, copy=True),
|
|
126
126
|
)
|
|
127
127
|
|
|
128
|
-
binary_carver.fit_transform(
|
|
128
|
+
binary_carver.fit_transform(
|
|
129
|
+
samples.train.X,
|
|
130
|
+
train_y_class,
|
|
131
|
+
X_dev=samples.dev.X if samples.dev.has_X else None,
|
|
132
|
+
y_dev=dev_y_class,
|
|
133
|
+
)
|
|
129
134
|
|
|
130
135
|
# filtering out dropped features whilst keeping other version tags
|
|
131
136
|
kept_features = binary_carver.features.versions
|
|
@@ -41,13 +41,13 @@ class Samples:
|
|
|
41
41
|
dev (Sample): The development sample, containing features (X) and target (y).
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
train: Sample = field(default_factory=
|
|
45
|
-
dev: Sample = field(default_factory=
|
|
44
|
+
train: Sample = field(default_factory=Sample)
|
|
45
|
+
dev: Sample = field(default_factory=Sample)
|
|
46
46
|
|
|
47
47
|
def fillna(self, features: Features) -> None:
|
|
48
48
|
"""fills up nans in X and X_dev"""
|
|
49
49
|
self.train.X = features.fillna(self.train.X)
|
|
50
|
-
if self.dev.
|
|
50
|
+
if self.dev.has_X:
|
|
51
51
|
self.dev.X = features.fillna(self.dev.X)
|
|
52
52
|
|
|
53
53
|
|
|
@@ -175,14 +175,14 @@ class BaseCarver(BaseDiscretizer, ABC):
|
|
|
175
175
|
content["combination_evaluator"] = self.combination_evaluator.to_json()
|
|
176
176
|
return content
|
|
177
177
|
|
|
178
|
-
def
|
|
178
|
+
def _prepare_samples(self, samples: Samples) -> Samples:
|
|
179
179
|
"""Validates format and content of X and y."""
|
|
180
180
|
if samples.train.y is None:
|
|
181
181
|
raise ValueError(f"[{self.__name__}] y must be provided, got {samples.train.y}")
|
|
182
182
|
|
|
183
183
|
# Checking for binary target and copying X
|
|
184
|
-
samples.train = super().
|
|
185
|
-
samples.dev = super().
|
|
184
|
+
samples.train = super()._prepare_sample(samples.train)
|
|
185
|
+
samples.dev = super()._prepare_sample(samples.dev)
|
|
186
186
|
|
|
187
187
|
# discretizing features at half min_freq so the carver has a finer
|
|
188
188
|
# granularity to combine when forming optimal groups
|
|
@@ -196,7 +196,7 @@ class BaseCarver(BaseDiscretizer, ABC):
|
|
|
196
196
|
|
|
197
197
|
return samples
|
|
198
198
|
|
|
199
|
-
def fit( #
|
|
199
|
+
def fit( # type: ignore
|
|
200
200
|
self,
|
|
201
201
|
X: pd.DataFrame,
|
|
202
202
|
y: pd.Series,
|
|
@@ -235,7 +235,7 @@ class BaseCarver(BaseDiscretizer, ABC):
|
|
|
235
235
|
samples = Samples(Sample(X, y), Sample(X_dev, y_dev))
|
|
236
236
|
|
|
237
237
|
# preparing datasets and checking for wrong values
|
|
238
|
-
samples = self.
|
|
238
|
+
samples = self._prepare_samples(samples)
|
|
239
239
|
|
|
240
240
|
# logging if requested
|
|
241
241
|
super()._log_if_verbose("---------\n------")
|
|
@@ -329,10 +329,11 @@ class BaseCarver(BaseDiscretizer, ABC):
|
|
|
329
329
|
|
|
330
330
|
# printing carved distribution, for found, suitable combination
|
|
331
331
|
if best_combination is not None:
|
|
332
|
+
dev_sample = self.combination_evaluator.samples.dev
|
|
332
333
|
self._print_xagg(
|
|
333
334
|
feature,
|
|
334
335
|
xagg=self.combination_evaluator.samples.train.xagg,
|
|
335
|
-
xagg_dev=
|
|
336
|
+
xagg_dev=dev_sample.xagg if dev_sample.has_xagg else None,
|
|
336
337
|
message="Carved distribution",
|
|
337
338
|
)
|
|
338
339
|
|
|
@@ -482,7 +483,7 @@ def discretize(
|
|
|
482
483
|
samples.train.X = discretizer.fit_transform(**samples.train)
|
|
483
484
|
|
|
484
485
|
# applying discretizer on X_dev if provided
|
|
485
|
-
if samples.dev.
|
|
486
|
+
if samples.dev.has_X:
|
|
486
487
|
samples.dev.X = discretizer.transform(**samples.dev)
|
|
487
488
|
|
|
488
489
|
return samples
|
|
@@ -44,14 +44,14 @@ def prettier_xagg(
|
|
|
44
44
|
|
|
45
45
|
# adding custom caption/title
|
|
46
46
|
if caption is not None:
|
|
47
|
-
nicer_xagg = nicer_xagg.set_caption(caption)
|
|
47
|
+
nicer_xagg = nicer_xagg.set_caption(caption) # type: ignore
|
|
48
48
|
|
|
49
49
|
# hiding index for dev
|
|
50
50
|
if hide_index:
|
|
51
|
-
nicer_xagg.hide(axis="index")
|
|
51
|
+
nicer_xagg.hide(axis="index") # type: ignore
|
|
52
52
|
|
|
53
53
|
# converting to html
|
|
54
|
-
nicer_xagg = nicer_xagg._repr_html_() #
|
|
54
|
+
nicer_xagg = nicer_xagg._repr_html_() # type: ignore
|
|
55
55
|
|
|
56
56
|
return nicer_xagg
|
|
57
57
|
|
|
@@ -14,15 +14,19 @@ from AutoCarver.combinations.utils.combination_evaluator import (
|
|
|
14
14
|
CombinationEvaluator,
|
|
15
15
|
)
|
|
16
16
|
from AutoCarver.combinations.utils.combinations import combination_formatter
|
|
17
|
+
from AutoCarver.combinations.utils.target_rate import TargetRate
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class BinaryCombinationEvaluator(CombinationEvaluator, ABC):
|
|
20
|
+
class BinaryCombinationEvaluator(CombinationEvaluator[pd.DataFrame], ABC):
|
|
20
21
|
"""Binary combination evaluator class."""
|
|
21
22
|
|
|
22
23
|
is_y_binary = True
|
|
23
24
|
_target_rate_classes: list[type[BinaryTargetRate]] = [TargetMean, OddsRatio, Woe]
|
|
25
|
+
# narrow inherited attribute: binary evaluators always carry a BinaryTargetRate
|
|
26
|
+
# (enforced by _init_target_rate).
|
|
27
|
+
target_rate: BinaryTargetRate
|
|
24
28
|
|
|
25
|
-
def _init_target_rate(self, target_rate:
|
|
29
|
+
def _init_target_rate(self, target_rate: TargetRate[pd.DataFrame] | None) -> BinaryTargetRate:
|
|
26
30
|
"""Initializes target rate."""
|
|
27
31
|
if target_rate is None:
|
|
28
32
|
return TargetMean()
|
|
@@ -31,8 +35,11 @@ class BinaryCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
31
35
|
return target_rate
|
|
32
36
|
|
|
33
37
|
def _association_measure(
|
|
34
|
-
self,
|
|
35
|
-
|
|
38
|
+
self,
|
|
39
|
+
xagg: AggregatedSample | pd.Series | pd.DataFrame,
|
|
40
|
+
n_obs: int | None = None,
|
|
41
|
+
tol: float = 1e-10,
|
|
42
|
+
) -> dict[str, float | None]:
|
|
36
43
|
"""Computes measures of association between feature and target by crosstab.
|
|
37
44
|
|
|
38
45
|
Used for the raw (one-shot) distribution. The hot per-combination loop
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""set of target rates for binary classification"""
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
|
+
from typing import overload
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import pandas as pd
|
|
@@ -8,12 +9,16 @@ import pandas as pd
|
|
|
8
9
|
from AutoCarver.combinations.utils import TargetRate
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class BinaryTargetRate(TargetRate, ABC):
|
|
12
|
+
class BinaryTargetRate(TargetRate[pd.DataFrame], ABC):
|
|
12
13
|
"""Binary target rate class."""
|
|
13
14
|
|
|
14
15
|
__name__ = "binary_target_rate"
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
@overload
|
|
18
|
+
def compute(self, xagg: pd.Series | pd.DataFrame) -> pd.DataFrame: ...
|
|
19
|
+
@overload
|
|
20
|
+
def compute(self, xagg: None) -> None: ...
|
|
21
|
+
def compute(self, xagg: pd.Series | pd.DataFrame | None) -> pd.DataFrame | None:
|
|
17
22
|
"""Computes the target rate.
|
|
18
23
|
|
|
19
24
|
Parameters
|
|
@@ -31,8 +36,12 @@ class BinaryTargetRate(TargetRate, ABC):
|
|
|
31
36
|
# frequency per modality
|
|
32
37
|
frequency = xagg.sum(axis=1) / xagg.sum().sum()
|
|
33
38
|
|
|
34
|
-
# computing target rate
|
|
35
|
-
|
|
39
|
+
# computing target rate. `_compute` expects pd.DataFrame (Generic
|
|
40
|
+
# XAgg=DataFrame); compute()'s wide signature is for LSP matching,
|
|
41
|
+
# callers always pass a crosstab here.
|
|
42
|
+
return pd.DataFrame(
|
|
43
|
+
{self.__name__: self._compute(xagg), "frequency": frequency} # type: ignore
|
|
44
|
+
)
|
|
36
45
|
return None
|
|
37
46
|
|
|
38
47
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
from collections.abc import Iterable, Iterator
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
import numpy as np
|
|
7
8
|
import pandas as pd
|
|
@@ -11,10 +12,11 @@ from tqdm import tqdm
|
|
|
11
12
|
from AutoCarver.combinations.continuous.continuous_target_rates import ContinuousTargetRate, TargetMean, TargetMedian
|
|
12
13
|
from AutoCarver.combinations.utils.combination_evaluator import AggregatedSample, CombinationEvaluator
|
|
13
14
|
from AutoCarver.combinations.utils.combinations import combination_formatter
|
|
15
|
+
from AutoCarver.combinations.utils.target_rate import TargetRate
|
|
14
16
|
from AutoCarver.combinations.utils.testing import Keys, is_viable, test_viability
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
19
|
+
class ContinuousCombinationEvaluator(CombinationEvaluator[pd.Series], ABC):
|
|
18
20
|
"""Continuous combination evaluator class."""
|
|
19
21
|
|
|
20
22
|
is_y_continuous = True
|
|
@@ -23,7 +25,7 @@ class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
23
25
|
# carvers always carry a ContinuousTargetRate (enforced by _init_target_rate).
|
|
24
26
|
target_rate: ContinuousTargetRate
|
|
25
27
|
|
|
26
|
-
def _init_target_rate(self, target_rate:
|
|
28
|
+
def _init_target_rate(self, target_rate: TargetRate[pd.Series] | None) -> ContinuousTargetRate:
|
|
27
29
|
"""Initializes target rate."""
|
|
28
30
|
if target_rate is None:
|
|
29
31
|
return TargetMean()
|
|
@@ -32,7 +34,10 @@ class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
32
34
|
return target_rate
|
|
33
35
|
|
|
34
36
|
def _association_measure(
|
|
35
|
-
self,
|
|
37
|
+
self,
|
|
38
|
+
xagg: AggregatedSample | pd.Series | pd.DataFrame,
|
|
39
|
+
n_obs: int | None = None,
|
|
40
|
+
tol: float = 1e-10,
|
|
36
41
|
) -> dict[str, float | None]:
|
|
37
42
|
"""Computes measures of association between feature and quantitative target.
|
|
38
43
|
|
|
@@ -138,8 +143,8 @@ class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
138
143
|
:meth:`_association_measure`).
|
|
139
144
|
"""
|
|
140
145
|
raw_xagg = self.samples.train.xagg
|
|
141
|
-
# Pre-rank y once for the whole feature
|
|
142
|
-
R_per_mod, n_per_mod, N, tie_corr = _modality_rank_stats(raw_xagg)
|
|
146
|
+
# Pre-rank y once for the whole feature.
|
|
147
|
+
R_per_mod, n_per_mod, N, tie_corr = _modality_rank_stats(raw_xagg) # type: ignore
|
|
143
148
|
|
|
144
149
|
# Map modality label -> position in R_per_mod / n_per_mod
|
|
145
150
|
mod_to_pos: dict = {m: i for i, m in enumerate(raw_xagg.index)}
|
|
@@ -148,14 +153,17 @@ class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
148
153
|
# Cache per-modality (n, sum_y) for the viability fast path.
|
|
149
154
|
# Resets each time _compute_associations runs so the nan-pass refreshes
|
|
150
155
|
# the cache after _apply_best_combination changes samples.train.xagg.
|
|
151
|
-
sum_y_per_mod = _modality_sum_y(raw_xagg)
|
|
152
|
-
|
|
156
|
+
sum_y_per_mod = _modality_sum_y(raw_xagg) # type: ignore
|
|
157
|
+
# Why: heterogeneous-value dict; annotate `Any` so downstream readers (line 203-204
|
|
158
|
+
# and _get_dev_modality_stats) can narrow to the per-key concrete type without ty
|
|
159
|
+
# unioning across all value types.
|
|
160
|
+
self._train_modality_stats: dict[str, Any] = {
|
|
153
161
|
"n_per_mod": n_per_mod.astype(float),
|
|
154
162
|
"sum_y_per_mod": sum_y_per_mod,
|
|
155
163
|
"mod_to_pos": mod_to_pos,
|
|
156
164
|
"n_mod": n_mod,
|
|
157
165
|
}
|
|
158
|
-
self._dev_modality_stats: dict | None = None # lazy; aligned to train's mod_to_pos
|
|
166
|
+
self._dev_modality_stats: dict[str, Any] | None = None # lazy; aligned to train's mod_to_pos
|
|
159
167
|
self._dev_modality_stats_id: int | None = None
|
|
160
168
|
|
|
161
169
|
batch: list[dict] = []
|
|
@@ -193,9 +201,9 @@ class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
193
201
|
computation (the unit tests rely on this; production flows reassign
|
|
194
202
|
dev only via ``samples.set`` at the start of ``get_best_combination``).
|
|
195
203
|
"""
|
|
196
|
-
|
|
197
|
-
if dev_xagg is None:
|
|
204
|
+
if not self.samples.dev.has_xagg:
|
|
198
205
|
return None
|
|
206
|
+
dev_xagg = self.samples.dev.xagg
|
|
199
207
|
if self._dev_modality_stats is not None and self._dev_modality_stats_id == id(dev_xagg):
|
|
200
208
|
return self._dev_modality_stats
|
|
201
209
|
train_stats = self._train_modality_stats
|
|
@@ -261,7 +269,7 @@ class ContinuousCombinationEvaluator(CombinationEvaluator, ABC):
|
|
|
261
269
|
"""Fast-path viability on dev; falls back to legacy when the active
|
|
262
270
|
target rate's ``compute_from_stats`` returns ``None``.
|
|
263
271
|
"""
|
|
264
|
-
if not test_results[Keys.VIABLE.value] or self.samples.dev.
|
|
272
|
+
if not test_results[Keys.VIABLE.value] or not self.samples.dev.has_xagg:
|
|
265
273
|
return {**test_results, "dev": {Keys.VIABLE.value: None}}
|
|
266
274
|
|
|
267
275
|
dev_stats = self._get_dev_modality_stats()
|
{autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/continuous/continuous_target_rates.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""set of target rates for binary classification"""
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
|
+
from typing import overload
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import pandas as pd
|
|
@@ -8,12 +9,16 @@ import pandas as pd
|
|
|
8
9
|
from AutoCarver.combinations.utils import TargetRate
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
class ContinuousTargetRate(TargetRate, ABC):
|
|
12
|
+
class ContinuousTargetRate(TargetRate[pd.Series], ABC):
|
|
12
13
|
"""Continuous target rate class."""
|
|
13
14
|
|
|
14
15
|
__name__ = "continuous_target_rate"
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
@overload
|
|
18
|
+
def compute(self, xagg: pd.Series | pd.DataFrame) -> pd.DataFrame: ...
|
|
19
|
+
@overload
|
|
20
|
+
def compute(self, xagg: None) -> None: ...
|
|
21
|
+
def compute(self, xagg: pd.Series | pd.DataFrame | None) -> pd.DataFrame | None:
|
|
17
22
|
"""Computes the target rate.
|
|
18
23
|
|
|
19
24
|
Parameters
|
|
@@ -31,8 +36,12 @@ class ContinuousTargetRate(TargetRate, ABC):
|
|
|
31
36
|
# frequency per modality
|
|
32
37
|
frequency = xagg.apply(len) / xagg.apply(len).sum()
|
|
33
38
|
|
|
34
|
-
# computing target rate
|
|
35
|
-
|
|
39
|
+
# computing target rate. `_compute` expects pd.Series (Generic
|
|
40
|
+
# XAgg=Series); compute()'s wide signature is for LSP matching,
|
|
41
|
+
# callers always pass a Series-of-y-lists here.
|
|
42
|
+
return pd.DataFrame(
|
|
43
|
+
{self.__name__: self._compute(xagg), "frequency": frequency} # type: ignore
|
|
44
|
+
)
|
|
36
45
|
return None
|
|
37
46
|
|
|
38
47
|
def compute_from_stats(self, *, stats: dict, index_to_groupby: dict) -> pd.DataFrame | None:
|
|
@@ -59,7 +68,10 @@ class TargetMean(ContinuousTargetRate):
|
|
|
59
68
|
|
|
60
69
|
def _compute(self, xagg: pd.Series | pd.DataFrame) -> pd.Series:
|
|
61
70
|
"""Computes the mean target rate."""
|
|
62
|
-
|
|
71
|
+
# pandas stubs widen `Series.apply(func)` to `Series | DataFrame`
|
|
72
|
+
# because they can't tell np.mean returns a scalar; the runtime
|
|
73
|
+
# result is always a Series.
|
|
74
|
+
return xagg.apply(np.mean) # type: ignore
|
|
63
75
|
|
|
64
76
|
def compute_from_stats(self, *, stats: dict, index_to_groupby: dict) -> pd.DataFrame | None:
|
|
65
77
|
"""Closed-form ``(target_mean, frequency)`` per group from per-modality stats.
|
|
@@ -130,7 +142,8 @@ class TargetMedian(ContinuousTargetRate):
|
|
|
130
142
|
|
|
131
143
|
def _compute(self, xagg: pd.Series | pd.DataFrame) -> pd.Series:
|
|
132
144
|
"""Computes the mean target rate."""
|
|
133
|
-
|
|
145
|
+
# see TargetMean._compute: same pandas-stub widening on apply().
|
|
146
|
+
return xagg.apply(np.median) # type: ignore
|
|
134
147
|
|
|
135
148
|
|
|
136
149
|
# class TargetVariance(ContinuousTargetRate):
|
{autocarver-7.2.1 → autocarver-7.2.2}/AutoCarver/combinations/utils/combination_evaluator.py
RENAMED
|
@@ -6,6 +6,7 @@ import math
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from collections.abc import Iterable, Iterator
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Generic
|
|
9
10
|
|
|
10
11
|
import pandas as pd
|
|
11
12
|
from tqdm import tqdm
|
|
@@ -17,31 +18,46 @@ from AutoCarver.combinations.utils.combinations import (
|
|
|
17
18
|
order_apply_combination,
|
|
18
19
|
xagg_apply_combination,
|
|
19
20
|
)
|
|
20
|
-
from AutoCarver.combinations.utils.target_rate import TargetRate
|
|
21
|
+
from AutoCarver.combinations.utils.target_rate import TargetRate, XAgg
|
|
21
22
|
from AutoCarver.combinations.utils.testing import Keys, is_viable, test_viability
|
|
22
23
|
from AutoCarver.features import BaseFeature, GroupedList
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
@dataclass
|
|
26
26
|
class AggregatedSample:
|
|
27
|
-
"""Sample class to store aggregated samples
|
|
28
|
-
|
|
29
|
-
Attributes
|
|
30
|
-
----------
|
|
31
|
-
xagg : pd.DataFrame
|
|
32
|
-
Aggregated sample
|
|
33
|
-
raw : pd.DataFrame
|
|
34
|
-
Raw aggregated sample
|
|
35
|
-
"""
|
|
27
|
+
"""Sample class to store aggregated samples.
|
|
36
28
|
|
|
37
|
-
xagg
|
|
38
|
-
|
|
29
|
+
The public ``xagg`` is typed as mandatory ``pd.Series | pd.DataFrame``. The
|
|
30
|
+
constructor accepts ``None`` so that placeholders (default factories, optional
|
|
31
|
+
dev samples) are expressible, but reading ``.xagg`` on an unset sample raises.
|
|
32
|
+
Use :attr:`has_xagg` to check presence without triggering.
|
|
33
|
+
"""
|
|
39
34
|
|
|
40
|
-
def
|
|
41
|
-
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
xagg: pd.Series | pd.DataFrame | None = None,
|
|
38
|
+
_raw: pd.Series | pd.DataFrame | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._xagg: pd.Series | pd.DataFrame | None = xagg
|
|
41
|
+
self._raw: pd.Series | pd.DataFrame | None = _raw
|
|
42
42
|
# setting xtab_dev to xtab if not provided
|
|
43
|
-
if self._raw is None and self.
|
|
44
|
-
self._raw = self.
|
|
43
|
+
if self._raw is None and self._xagg is not None:
|
|
44
|
+
self._raw = self._xagg.copy()
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def xagg(self) -> pd.Series | pd.DataFrame:
|
|
48
|
+
"""Returns the aggregated sample, or raises if not set."""
|
|
49
|
+
if self._xagg is None:
|
|
50
|
+
raise RuntimeError("[AggregatedSample] xagg is not set")
|
|
51
|
+
return self._xagg
|
|
52
|
+
|
|
53
|
+
@xagg.setter
|
|
54
|
+
def xagg(self, value: pd.Series | pd.DataFrame) -> None:
|
|
55
|
+
self._xagg = value
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def has_xagg(self) -> bool:
|
|
59
|
+
"""Whether xagg is set."""
|
|
60
|
+
return self._xagg is not None
|
|
45
61
|
|
|
46
62
|
@property
|
|
47
63
|
def raw(self) -> pd.Series | pd.DataFrame | None:
|
|
@@ -88,8 +104,8 @@ class AggregatedSample:
|
|
|
88
104
|
class AggregatedSamples:
|
|
89
105
|
"""stores train and dev samples"""
|
|
90
106
|
|
|
91
|
-
train: AggregatedSample = field(default_factory=
|
|
92
|
-
dev: AggregatedSample = field(default_factory=
|
|
107
|
+
train: AggregatedSample = field(default_factory=AggregatedSample)
|
|
108
|
+
dev: AggregatedSample = field(default_factory=AggregatedSample)
|
|
93
109
|
|
|
94
110
|
def set(self, train: pd.Series | pd.DataFrame | None, dev: pd.Series | pd.DataFrame | None = None) -> None:
|
|
95
111
|
"""Sets the train and dev samples"""
|
|
@@ -130,7 +146,7 @@ class AggregatedSamples:
|
|
|
130
146
|
self.dev.raw = xagg_apply_combination(self.dev.raw, feature)
|
|
131
147
|
|
|
132
148
|
|
|
133
|
-
class CombinationEvaluator(ABC):
|
|
149
|
+
class CombinationEvaluator(ABC, Generic[XAgg]):
|
|
134
150
|
"""CombinationEvaluator class to evaluate
|
|
135
151
|
the best combination of modalities for a feature."""
|
|
136
152
|
|
|
@@ -144,7 +160,7 @@ class CombinationEvaluator(ABC):
|
|
|
144
160
|
self,
|
|
145
161
|
*,
|
|
146
162
|
verbose: bool = False,
|
|
147
|
-
target_rate: TargetRate | None = None,
|
|
163
|
+
target_rate: TargetRate[XAgg] | None = None,
|
|
148
164
|
) -> None:
|
|
149
165
|
"""
|
|
150
166
|
Parameters
|
|
@@ -162,10 +178,10 @@ class CombinationEvaluator(ABC):
|
|
|
162
178
|
self._feature: BaseFeature | None = None
|
|
163
179
|
self.samples: AggregatedSamples = AggregatedSamples()
|
|
164
180
|
self._statistics_cache = None
|
|
165
|
-
self.target_rate: TargetRate = self._init_target_rate(target_rate)
|
|
181
|
+
self.target_rate: TargetRate[XAgg] = self._init_target_rate(target_rate)
|
|
166
182
|
|
|
167
183
|
@abstractmethod
|
|
168
|
-
def _init_target_rate(self, target_rate: TargetRate | None) -> TargetRate:
|
|
184
|
+
def _init_target_rate(self, target_rate: TargetRate[XAgg] | None) -> TargetRate[XAgg]:
|
|
169
185
|
"""Initializes target rate."""
|
|
170
186
|
if target_rate is None:
|
|
171
187
|
raise NotImplementedError("Subclasses must implement _init_target_rate to provide a default target rate")
|
|
@@ -209,7 +225,7 @@ class CombinationEvaluator(ABC):
|
|
|
209
225
|
combinations. Output is in arrival order; the caller is expected to
|
|
210
226
|
sort by the configured metric.
|
|
211
227
|
"""
|
|
212
|
-
n_obs = self.samples.train.xagg.apply(sum).sum()
|
|
228
|
+
n_obs: int = self.samples.train.xagg.apply(sum).sum() # type: ignore
|
|
213
229
|
for grouped_xagg in tqdm(grouped_xaggs, desc="Computing associations", disable=not self.verbose):
|
|
214
230
|
measure = self._association_measure(grouped_xagg["xagg"], n_obs=n_obs)
|
|
215
231
|
yield {
|
|
@@ -218,7 +234,7 @@ class CombinationEvaluator(ABC):
|
|
|
218
234
|
**measure,
|
|
219
235
|
}
|
|
220
236
|
|
|
221
|
-
def _get_best_association(self, combinations: Iterable[list[str]]) -> dict | None:
|
|
237
|
+
def _get_best_association(self, combinations: Iterable[list[list[str]]]) -> dict | None:
|
|
222
238
|
"""Streams grouping → scoring → viability in one pass.
|
|
223
239
|
|
|
224
240
|
- ``combinations`` is consumed lazily (generator-friendly).
|
|
@@ -275,8 +291,13 @@ class CombinationEvaluator(ABC):
|
|
|
275
291
|
# applying best_combination to raw xagg and xagg_dev
|
|
276
292
|
self.samples.apply_combination(self.feature)
|
|
277
293
|
|
|
278
|
-
# udpating statistics
|
|
279
|
-
|
|
294
|
+
# udpating statistics — `apply_combination` always populates
|
|
295
|
+
# `samples.train.raw`; narrow Optional so the overloaded
|
|
296
|
+
# compute() returns DataFrame (not DataFrame | None).
|
|
297
|
+
raw = self.samples.train.raw
|
|
298
|
+
if raw is None:
|
|
299
|
+
raise RuntimeError(f"[{self.__name__}] samples.train.raw is not populated after apply_combination")
|
|
300
|
+
self.feature.statistics = self.target_rate.compute(raw)
|
|
280
301
|
|
|
281
302
|
def _get_best_combination_non_nan(self) -> dict | None:
|
|
282
303
|
"""Computes associations of the tab for each combination of non-nans
|
|
@@ -384,7 +405,7 @@ class CombinationEvaluator(ABC):
|
|
|
384
405
|
"""testing the viability of the combination on xagg_dev"""
|
|
385
406
|
|
|
386
407
|
# case 0: not viable on train or no test sample -> not testing for robustness
|
|
387
|
-
if not test_results[Keys.VIABLE.value] or self.samples.dev.
|
|
408
|
+
if not test_results[Keys.VIABLE.value] or not self.samples.dev.has_xagg:
|
|
388
409
|
return {**test_results, "dev": {Keys.VIABLE.value: None}}
|
|
389
410
|
|
|
390
411
|
# case 1: test sample provided and viable on train -> testing robustness
|
|
@@ -440,14 +461,22 @@ class CombinationEvaluator(ABC):
|
|
|
440
461
|
return viable_combination
|
|
441
462
|
|
|
442
463
|
@abstractmethod
|
|
443
|
-
def _grouper(self, xagg: AggregatedSample, groupby: dict[str, str]) ->
|
|
464
|
+
def _grouper(self, xagg: AggregatedSample, groupby: dict[str, str]) -> XAgg:
|
|
444
465
|
"""Helper to group XAGG's values by groupby (carver specific)"""
|
|
445
466
|
|
|
446
467
|
@abstractmethod
|
|
447
468
|
def _association_measure(
|
|
448
|
-
self,
|
|
449
|
-
|
|
450
|
-
|
|
469
|
+
self,
|
|
470
|
+
xagg: AggregatedSample | pd.Series | pd.DataFrame,
|
|
471
|
+
n_obs: int | None = None,
|
|
472
|
+
tol: float = 1e-10,
|
|
473
|
+
) -> dict[str, float | None]:
|
|
474
|
+
"""Helper to measure association between X and y (carver specific).
|
|
475
|
+
|
|
476
|
+
Return value type is widened to ``float | None`` so the continuous
|
|
477
|
+
Kruskal–Wallis path (which returns ``None`` when scipy raises) can
|
|
478
|
+
share the base signature with the binary chi² path.
|
|
479
|
+
"""
|
|
451
480
|
|
|
452
481
|
def _historize_remaining_combinations(self, associations: list[dict], n_combination: int) -> None:
|
|
453
482
|
"""historizes the remaining combinations that have not been tested"""
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""defines a target rate"""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Generic, TypeVar, overload
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
# parametric type: target rates and evaluators are split by the *kind* of
|
|
9
|
+
# aggregated input — DataFrame crosstabs for binary, Series-of-lists for
|
|
10
|
+
# continuous. A value-constrained TypeVar prevents accidental third instantiations.
|
|
11
|
+
XAgg = TypeVar("XAgg", pd.Series, pd.DataFrame)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TargetRate(ABC, Generic[XAgg]):
|
|
15
|
+
"""Target rate class.
|
|
16
|
+
|
|
17
|
+
Generic over ``XAgg`` for the inner :meth:`_compute` worker so that
|
|
18
|
+
binary (``DataFrame`` crosstabs) and continuous (``Series`` of y-lists)
|
|
19
|
+
subclasses don't violate LSP by narrowing the worker's parameter type.
|
|
20
|
+
The outer :meth:`compute` keeps a wide ``Series | DataFrame | None``
|
|
21
|
+
signature because call sites in :class:`CombinationEvaluator` and
|
|
22
|
+
:class:`BaseCarver` carry that union directly from
|
|
23
|
+
:class:`AggregatedSample.raw` / pretty-printer plumbing.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
__name__ = "target_rate"
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def _compute(self, xagg: XAgg) -> pd.Series:
|
|
30
|
+
"""Computes the target rate."""
|
|
31
|
+
|
|
32
|
+
# `compute` is overloaded so that callers passing a non-None ``xagg`` get a
|
|
33
|
+
# non-Optional ``pd.DataFrame`` back — required by `_test_viability_*` and
|
|
34
|
+
# the `BaseFeature.statistics` setter, which don't accept ``None``.
|
|
35
|
+
@overload
|
|
36
|
+
def compute(self, xagg: pd.Series | pd.DataFrame) -> pd.DataFrame: ...
|
|
37
|
+
@overload
|
|
38
|
+
def compute(self, xagg: None) -> None: ...
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def compute(self, xagg: pd.Series | pd.DataFrame | None) -> pd.DataFrame | None:
|
|
41
|
+
"""Computes the target rate.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
xagg : pd.Series | pd.DataFrame | None
|
|
46
|
+
A crosstab (binary) or Series-of-y-lists (continuous).
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
pd.DataFrame | None
|
|
51
|
+
Target rate frame, or ``None`` if ``xagg`` was ``None``.
|
|
52
|
+
"""
|
|
@@ -39,9 +39,9 @@ class Discretizer(BaseDiscretizer):
|
|
|
39
39
|
super().__init__(features, min_freq=min_freq, config=config)
|
|
40
40
|
|
|
41
41
|
@extend_docstring(BaseDiscretizer.fit)
|
|
42
|
-
def fit(self, X: pd.DataFrame, y: pd.Series) -> Self:
|
|
42
|
+
def fit(self, X: pd.DataFrame, y: pd.Series) -> Self:
|
|
43
43
|
# Checking for binary target and copying X
|
|
44
|
-
sample = self.
|
|
44
|
+
sample = self._prepare_sample(Sample(X, y))
|
|
45
45
|
|
|
46
46
|
# fitting quantitative features if any
|
|
47
47
|
self._fit_quantitatives(**sample)
|