scikit-extensions 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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scikit-extensions
3
- Version: 0.2.0
3
+ Version: 0.2.2
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.0"
3
+ version = "0.2.2"
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.0"
3
+ version = "0.2.2"
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"
@@ -39,12 +39,15 @@ class OLSRegressor(BaseEstimator, RegressorMixin):
39
39
  self : object
40
40
  Returns self.
41
41
  """
42
+ input_feature_names = X.columns.tolist() if isinstance(X, pd.DataFrame) else None
43
+
42
44
  # Check input data
43
45
  X, y = check_X_y(X, y, y_numeric=True)
44
46
 
45
47
  # Convert to DataFrame to preserve feature names
46
- if isinstance(X, pd.DataFrame):
47
- self.feature_names_ = X.columns.tolist()
48
+ if input_feature_names is not None:
49
+ self.feature_names_ = input_feature_names
50
+ X = pd.DataFrame(X, columns=self.feature_names_)
48
51
  else:
49
52
  self.feature_names_ = [f'x{i}' for i in range(X.shape[1])]
50
53
  X = pd.DataFrame(X, columns=self.feature_names_)
@@ -98,14 +101,15 @@ class OLSRegressor(BaseEstimator, RegressorMixin):
98
101
  """Return model coefficients (excluding intercept)."""
99
102
  if self.results_ is None:
100
103
  raise ValueError("Model not fitted. Call 'fit' first.")
101
- return self.results_.params[1:] if self.fit_intercept else self.results_.params
104
+ params = np.asarray(self.results_.params)
105
+ return params[1:] if self.fit_intercept else params
102
106
 
103
107
  @property
104
108
  def intercept_(self):
105
109
  """Return intercept value."""
106
110
  if self.results_ is None:
107
111
  raise ValueError("Model not fitted. Call 'fit' first.")
108
- return self.results_.params[0] if self.fit_intercept else 0.0
112
+ return np.asarray(self.results_.params)[0] if self.fit_intercept else 0.0
109
113
 
110
114
  def summary(self):
111
115
  """Return statsmodels summary object."""
@@ -31,8 +31,6 @@ class MultiCollinearityReducer(BaseEstimator, TransformerMixin):
31
31
  self.correlation_threshold = correlation_threshold
32
32
  self.strategy = strategy
33
33
  self.random_state = random_state
34
- self.selected_features_ = None
35
- self.feature_correlations_ = None
36
34
 
37
35
  def _select_by_variance(self, X, features):
38
36
  """Select feature with highest variance from group"""
@@ -74,6 +72,18 @@ class MultiCollinearityReducer(BaseEstimator, TransformerMixin):
74
72
  """
75
73
  # Convert to DataFrame if needed
76
74
  X_ = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
75
+ self.n_features_in_ = X_.shape[1]
76
+ if isinstance(X, pd.DataFrame):
77
+ self.input_columns_ = list(X.columns)
78
+ if all(isinstance(column, str) for column in X.columns):
79
+ self.feature_names_in_ = np.asarray(X.columns, dtype=object)
80
+ elif hasattr(self, "feature_names_in_"):
81
+ del self.feature_names_in_
82
+ else:
83
+ self.input_columns_ = [f"x{i}" for i in range(X_.shape[1])]
84
+ X_.columns = self.input_columns_
85
+ if hasattr(self, "feature_names_in_"):
86
+ del self.feature_names_in_
77
87
 
78
88
  # Calculate correlation matrix
79
89
  corr_matrix = X_.corr().abs()
@@ -129,12 +139,24 @@ class MultiCollinearityReducer(BaseEstimator, TransformerMixin):
129
139
  pandas.DataFrame
130
140
  Transformed data with selected features only
131
141
  """
132
- if self.selected_features_ is None:
142
+ if not hasattr(self, "selected_features_"):
133
143
  raise ValueError("Transformer has not been fitted yet.")
134
-
135
- X_ = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X)
144
+
145
+ X_ = X.copy() if isinstance(X, pd.DataFrame) else pd.DataFrame(X, columns=self.input_columns_)
146
+ if X_.shape[1] != self.n_features_in_:
147
+ raise ValueError("X has a different number of features than during fit.")
148
+ if isinstance(X, pd.DataFrame) and list(X.columns) != self.input_columns_:
149
+ raise ValueError("Input columns must match the columns seen during fit.")
136
150
  return X_[self.selected_features_]
137
151
 
138
152
  def get_feature_names_out(self, input_features=None):
139
153
  """Return feature names for output features."""
140
- return np.array(self.selected_features_)
154
+ if not hasattr(self, "selected_features_"):
155
+ raise ValueError("Transformer has not been fitted yet.")
156
+ if input_features is not None and hasattr(self, "feature_names_in_"):
157
+ if not np.array_equal(input_features, self.feature_names_in_):
158
+ raise ValueError("input_features must match feature_names_in_ when feature names are available.")
159
+ return np.asarray(self.selected_features_, dtype=object)
160
+
161
+
162
+ MulticollinearityRemover = MultiCollinearityReducer
@@ -35,8 +35,6 @@ class MultiLabelBinarizerTransformer(BaseEstimator, TransformerMixin):
35
35
  If True, the output will be a sparse matrix in Compressed Sparse Row (CSR) format. Otherwise, a dense NumPy array is returned.
36
36
  """
37
37
  self.sparse_output = sparse_output
38
- self.mlbs = {}
39
- self.feature_names_in_ = None
40
38
 
41
39
  def fit(self, X, y=None):
42
40
  """
@@ -57,10 +55,15 @@ class MultiLabelBinarizerTransformer(BaseEstimator, TransformerMixin):
57
55
  """
58
56
  if not isinstance(X, pd.DataFrame):
59
57
  raise ValueError("Input must be a pandas DataFrame")
60
- self.feature_names_in_ = X.columns.tolist()
61
- self.mlbs = {
58
+ self.input_columns_ = list(X.columns)
59
+ self.n_features_in_ = X.shape[1]
60
+ if all(isinstance(column, str) for column in X.columns):
61
+ self.feature_names_in_ = np.asarray(X.columns, dtype=object)
62
+ elif hasattr(self, "feature_names_in_"):
63
+ del self.feature_names_in_
64
+ self.mlbs_ = {
62
65
  col: MultiLabelBinarizer(sparse_output=self.sparse_output).fit(X[col])
63
- for col in self.feature_names_in_
66
+ for col in self.input_columns_
64
67
  }
65
68
  return self
66
69
 
@@ -80,9 +83,13 @@ class MultiLabelBinarizerTransformer(BaseEstimator, TransformerMixin):
80
83
  """
81
84
  if not isinstance(X, pd.DataFrame):
82
85
  raise ValueError("Input must be a pandas DataFrame")
86
+ if not hasattr(self, "mlbs_"):
87
+ raise ValueError("Transformer has not been fitted yet.")
88
+ if list(X.columns) != self.input_columns_:
89
+ raise ValueError("Input columns must match the columns seen during fit.")
83
90
  transformed_parts = []
84
- for col in self.feature_names_in_:
85
- mlb = self.mlbs[col]
91
+ for col in self.input_columns_:
92
+ mlb = self.mlbs_[col]
86
93
  part = mlb.transform(X[col])
87
94
  if not self.sparse_output:
88
95
  part = pd.DataFrame(
@@ -91,7 +98,7 @@ class MultiLabelBinarizerTransformer(BaseEstimator, TransformerMixin):
91
98
  index=X.index
92
99
  )
93
100
  transformed_parts.append(part)
94
- return hstack(transformed_parts) if self.sparse_output else pd.concat(transformed_parts, axis=1)
101
+ return hstack(transformed_parts, format="csr") if self.sparse_output else pd.concat(transformed_parts, axis=1)
95
102
 
96
103
  def inverse_transform(self, X):
97
104
  """
@@ -107,24 +114,31 @@ class MultiLabelBinarizerTransformer(BaseEstimator, TransformerMixin):
107
114
  inverse_transformed : pandas DataFrame of shape (n_samples, n_features)
108
115
  A DataFrame where each column contains the original label sets for each sample.
109
116
  """
117
+ if not hasattr(self, "mlbs_"):
118
+ raise ValueError("Transformer has not been fitted yet.")
110
119
  if self.sparse_output and not issparse(X):
111
120
  raise ValueError("Expected sparse input for inverse_transform but got dense.")
112
- if not self.sparse_output and isinstance(X, np.ndarray):
113
- X = pd.DataFrame(X, columns=self.get_feature_names_out())
121
+ if not self.sparse_output:
122
+ X = np.asarray(X)
114
123
  result = {}
115
124
  if self.sparse_output:
116
125
  current_col = 0
117
- for col in self.feature_names_in_:
118
- mlb = self.mlbs[col]
126
+ for col in self.input_columns_:
127
+ mlb = self.mlbs_[col]
119
128
  n_classes = len(mlb.classes_)
120
129
  col_slice = X[:, current_col:current_col + n_classes]
121
- result[col] = mlb.inverse_transform(col_slice)
130
+ result[col] = [list(labels) for labels in mlb.inverse_transform(col_slice)]
122
131
  current_col += n_classes
123
132
  else:
124
- for col in self.feature_names_in_:
125
- mlb = self.mlbs[col]
126
- col_data = X[[f"{col}__{cls}" for cls in mlb.classes_]]
127
- result[col] = mlb.inverse_transform(col_data.values)
133
+ current_col = 0
134
+ for col in self.input_columns_:
135
+ mlb = self.mlbs_[col]
136
+ n_classes = len(mlb.classes_)
137
+ result[col] = [
138
+ list(labels)
139
+ for labels in mlb.inverse_transform(X[:, current_col:current_col + n_classes])
140
+ ]
141
+ current_col += n_classes
128
142
  return pd.DataFrame(result)
129
143
 
130
144
  def get_feature_names_out(self, input_features=None):
@@ -141,11 +155,20 @@ class MultiLabelBinarizerTransformer(BaseEstimator, TransformerMixin):
141
155
  feature_names_out : list of str
142
156
  The output feature names.
143
157
  """
158
+ if not hasattr(self, "mlbs_"):
159
+ raise ValueError("Transformer has not been fitted yet.")
144
160
  if input_features is None:
145
- input_features = self.feature_names_in_
161
+ input_features = getattr(
162
+ self,
163
+ "feature_names_in_",
164
+ np.asarray([f"x{i}" for i in range(self.n_features_in_)], dtype=object),
165
+ )
166
+ elif hasattr(self, "feature_names_in_") and not np.array_equal(
167
+ input_features, self.feature_names_in_
168
+ ):
169
+ raise ValueError("input_features must match feature_names_in_ when feature names are available.")
146
170
  out = []
147
- for col in input_features:
148
- mlb = self.mlbs.get(col)
149
- if mlb:
150
- out.extend([f"{col}__{cls}" for cls in mlb.classes_])
151
- return out
171
+ for col, fitted_col in zip(input_features, self.input_columns_):
172
+ mlb = self.mlbs_[fitted_col]
173
+ out.extend([f"{col}__{cls}" for cls in mlb.classes_])
174
+ return np.asarray(out, dtype=object)