scikit-extensions 0.2.2__tar.gz → 0.2.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scikit-extensions
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A collection of Scikit-learn-compatible transformers for feature engineering, preprocessing, and pipeline utilities.
5
5
  Keywords: scikit-learn,transformers,preprocessing,pipeline
6
6
  Author: sh99
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scikit-extensions"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "A collection of Scikit-learn-compatible transformers for feature engineering, preprocessing, and pipeline utilities."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scikit-extensions"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "A collection of Scikit-learn-compatible transformers for feature engineering, preprocessing, and pipeline utilities."
5
5
  authors = [{ name = "sh99", email = "shriramh99@gmail.com" }]
6
6
  readme = "README.md"
@@ -0,0 +1,13 @@
1
+ from .multilabels import MultiLabelBinarizerTransformer
2
+ from .multicolinerity import MultiCollinearityReducer
3
+ from .linear_model import OLSRegressor
4
+ from .feature_engineering import RareCategoryEncoder, TargetEncoder, TimeFeatureExtractor
5
+
6
+ __all__ = [
7
+ "MultiLabelBinarizerTransformer",
8
+ "MultiCollinearityReducer",
9
+ "OLSRegressor",
10
+ "RareCategoryEncoder",
11
+ "TargetEncoder",
12
+ "TimeFeatureExtractor",
13
+ ]
@@ -0,0 +1,201 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from sklearn.base import BaseEstimator, TransformerMixin
4
+
5
+
6
+ class RareCategoryEncoder(BaseEstimator, TransformerMixin):
7
+ """Replace infrequent categorical values with a fallback label."""
8
+
9
+ def __init__(self, min_frequency=0.05, replacement="other", columns=None):
10
+ self.min_frequency = min_frequency
11
+ self.replacement = replacement
12
+ self.columns = columns
13
+
14
+ def fit(self, X, y=None):
15
+ X = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
16
+
17
+ if self.columns is None:
18
+ self.columns_ = [
19
+ col for col in X.columns
20
+ if not pd.api.types.is_numeric_dtype(X[col])
21
+ and not pd.api.types.is_bool_dtype(X[col])
22
+ ]
23
+ else:
24
+ self.columns_ = list(self.columns)
25
+ missing = [col for col in self.columns_ if col not in X.columns]
26
+ if missing:
27
+ raise ValueError(f"Column '{missing[0]}' is not present in X.")
28
+
29
+ self.rare_categories_ = {}
30
+ for col in self.columns_:
31
+ values = X[col].astype("object").fillna("__missing__")
32
+ counts = values.value_counts(normalize=True)
33
+ threshold = self.min_frequency
34
+ rare = counts[counts < threshold].index
35
+ self.rare_categories_[col] = set(rare)
36
+ return self
37
+
38
+ def transform(self, X):
39
+ if not hasattr(self, "rare_categories_"):
40
+ raise ValueError("Encoder has not been fitted yet.")
41
+
42
+ X = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
43
+ transformed = X.copy()
44
+
45
+ for col in self.columns_:
46
+ if col not in transformed.columns:
47
+ raise ValueError(f"Column '{col}' is not present in X.")
48
+ values = transformed[col].astype("object").fillna("__missing__")
49
+ transformed[col] = values.map(
50
+ lambda value: self.replacement if value in self.rare_categories_[col] else value
51
+ )
52
+ transformed[col] = transformed[col].replace("__missing__", self.replacement)
53
+ return transformed
54
+
55
+ def get_feature_names_out(self, input_features=None):
56
+ if input_features is None:
57
+ input_features = list(self.columns_)
58
+ return np.asarray(input_features, dtype=object)
59
+
60
+
61
+ class TargetEncoder(BaseEstimator, TransformerMixin):
62
+ """Encode categorical columns using the mean target value."""
63
+
64
+ def __init__(self, columns=None, handle_unknown="mean"):
65
+ self.columns = columns
66
+ self.handle_unknown = handle_unknown
67
+
68
+ def fit(self, X, y):
69
+ X = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
70
+ y = pd.Series(np.asarray(y)).reset_index(drop=True)
71
+
72
+ if self.columns is None:
73
+ self.columns_ = [
74
+ col for col in X.columns
75
+ if not pd.api.types.is_numeric_dtype(X[col])
76
+ and not pd.api.types.is_bool_dtype(X[col])
77
+ ]
78
+ else:
79
+ self.columns_ = list(self.columns)
80
+ missing = [col for col in self.columns_ if col not in X.columns]
81
+ if missing:
82
+ raise ValueError(f"Column '{missing[0]}' is not present in X.")
83
+
84
+ self.global_mean_ = float(y.mean()) if len(y) else 0.0
85
+ self.mapping_ = {}
86
+
87
+ for col in self.columns_:
88
+ mapping = pd.Series(y.values, index=X[col].astype("object").fillna("__missing__")).groupby(level=0).mean()
89
+ self.mapping_[col] = mapping.to_dict()
90
+ return self
91
+
92
+ def transform(self, X):
93
+ if not hasattr(self, "mapping_"):
94
+ raise ValueError("Encoder has not been fitted yet.")
95
+
96
+ X = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
97
+ transformed = X.copy()
98
+
99
+ for col in self.columns_:
100
+ if col not in transformed.columns:
101
+ raise ValueError(f"Column '{col}' is not present in X.")
102
+ values = transformed[col].astype("object").fillna("__missing__")
103
+ transformed[col] = values.map(self.mapping_[col]).fillna(self.global_mean_)
104
+ return transformed
105
+
106
+ def get_feature_names_out(self, input_features=None):
107
+ if input_features is None:
108
+ input_features = list(self.columns_)
109
+ return np.asarray(input_features, dtype=object)
110
+
111
+
112
+ class TimeFeatureExtractor(BaseEstimator, TransformerMixin):
113
+ """Extract calendar features from datetime columns."""
114
+
115
+ VALID_FEATURES = {
116
+ "month",
117
+ "day_of_month",
118
+ "day_of_week",
119
+ "hour",
120
+ "minute",
121
+ "is_weekend",
122
+ "quarter",
123
+ "year",
124
+ }
125
+
126
+ def __init__(self, features=None, columns=None):
127
+ if features is None:
128
+ features = ["month", "day_of_week", "hour", "is_weekend"]
129
+ self.features = list(features)
130
+ self.columns = columns
131
+
132
+ def fit(self, X, y=None):
133
+ X = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
134
+
135
+ invalid = [feature for feature in self.features if feature not in self.VALID_FEATURES]
136
+ if invalid:
137
+ raise ValueError(f"Unsupported time features: {invalid}")
138
+
139
+ if self.columns is None:
140
+ candidate_columns = []
141
+ for col in X.columns:
142
+ try:
143
+ pd.to_datetime(X[col])
144
+ candidate_columns.append(col)
145
+ except (TypeError, ValueError):
146
+ pass
147
+ self.columns_ = candidate_columns
148
+ else:
149
+ self.columns_ = list(self.columns)
150
+ missing = [col for col in self.columns_ if col not in X.columns]
151
+ if missing:
152
+ raise ValueError(f"Column '{missing[0]}' is not present in X.")
153
+
154
+ return self
155
+
156
+ def transform(self, X):
157
+ if not hasattr(self, "columns_"):
158
+ raise ValueError("Transformer has not been fitted yet.")
159
+
160
+ X = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
161
+ output = pd.DataFrame(index=X.index)
162
+
163
+ for col in self.columns_:
164
+ if col not in X.columns:
165
+ raise ValueError(f"Column '{col}' is not present in X.")
166
+ series = pd.to_datetime(X[col], errors="coerce")
167
+
168
+ for feature in self.features:
169
+ if feature == "month":
170
+ values = series.dt.month
171
+ elif feature == "day_of_month":
172
+ values = series.dt.day
173
+ elif feature == "day_of_week":
174
+ values = series.dt.dayofweek
175
+ elif feature == "hour":
176
+ values = series.dt.hour
177
+ elif feature == "minute":
178
+ values = series.dt.minute
179
+ elif feature == "is_weekend":
180
+ values = series.dt.dayofweek.isin([5, 6])
181
+ elif feature == "quarter":
182
+ values = series.dt.quarter
183
+ elif feature == "year":
184
+ values = series.dt.year
185
+ else:
186
+ continue
187
+
188
+ name = feature if len(self.columns_) == 1 else f"{col}__{feature}"
189
+ output[name] = values
190
+
191
+ return output
192
+
193
+ def get_feature_names_out(self, input_features=None):
194
+ if not hasattr(self, "columns_"):
195
+ raise ValueError("Transformer has not been fitted yet.")
196
+
197
+ names = []
198
+ for col in self.columns_:
199
+ for feature in self.features:
200
+ names.append(feature if len(self.columns_) == 1 else f"{col}__{feature}")
201
+ return np.asarray(names, dtype=object)
@@ -1,5 +0,0 @@
1
- from .multilabels import MultiLabelBinarizerTransformer
2
- from .multicolinerity import MultiCollinearityReducer
3
- from .linear_model import OLSRegressor
4
-
5
- __all__ = ["MultiLabelBinarizerTransformer", "MultiCollinearityReducer", "OLSRegressor"]