upgini 1.2.156.dev5__tar.gz → 1.2.157.dev2__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 (84) hide show
  1. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/PKG-INFO +1 -1
  2. upgini-1.2.157.dev2/src/upgini/__about__.py +1 -0
  3. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/binary.py +63 -124
  4. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/date.py +20 -8
  5. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/feature.py +35 -19
  6. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/groupby.py +13 -4
  7. upgini-1.2.157.dev2/src/upgini/autofe/operand.py +112 -0
  8. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/operator.py +26 -10
  9. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/base.py +3 -1
  10. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/cross.py +5 -3
  11. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/unary.py +22 -11
  12. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/vector.py +10 -6
  13. upgini-1.2.156.dev5/src/upgini/__about__.py +0 -1
  14. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/.gitignore +0 -0
  15. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/LICENSE +0 -0
  16. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/README.md +0 -0
  17. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/pyproject.toml +0 -0
  18. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/__init__.py +0 -0
  19. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/ads.py +0 -0
  20. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/ads_management/__init__.py +0 -0
  21. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/ads_management/ads_manager.py +0 -0
  22. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/__init__.py +0 -0
  23. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/all_operators.py +0 -0
  24. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/__init__.py +0 -0
  25. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/delta.py +0 -0
  26. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/lag.py +0 -0
  27. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/roll.py +0 -0
  28. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/trend.py +0 -0
  29. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/volatility.py +0 -0
  30. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/utils.py +0 -0
  31. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/data_source/__init__.py +0 -0
  32. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/data_source/data_source_publisher.py +0 -0
  33. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/dataset.py +0 -0
  34. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/errors.py +0 -0
  35. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/features_enricher.py +0 -0
  36. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/http.py +0 -0
  37. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/mdc/__init__.py +0 -0
  38. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/mdc/context.py +0 -0
  39. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/metadata.py +0 -0
  40. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/metrics.py +0 -0
  41. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/normalizer/__init__.py +0 -0
  42. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/normalizer/normalize_utils.py +0 -0
  43. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/__init__.py +0 -0
  44. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/exceptions.py +0 -0
  45. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/strings.properties +0 -0
  46. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/strings_widget.properties +0 -0
  47. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/__init__.py +0 -0
  48. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/base.py +0 -0
  49. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/random_under_sampler.py +0 -0
  50. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/utils.py +0 -0
  51. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/search_task.py +0 -0
  52. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/spinner.py +0 -0
  53. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/Roboto-Regular.ttf +0 -0
  54. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/__init__.py +0 -0
  55. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/base_search_key_detector.py +0 -0
  56. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/blocked_time_series.py +0 -0
  57. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/config.py +0 -0
  58. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/country_utils.py +0 -0
  59. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/custom_loss_utils.py +0 -0
  60. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/cv_utils.py +0 -0
  61. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/datetime_utils.py +0 -0
  62. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/deduplicate_utils.py +0 -0
  63. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/display_utils.py +0 -0
  64. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/email_utils.py +0 -0
  65. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/fallback_progress_bar.py +0 -0
  66. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/feature_info.py +0 -0
  67. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/features_validator.py +0 -0
  68. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/format.py +0 -0
  69. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/hash_utils.py +0 -0
  70. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/ip_utils.py +0 -0
  71. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/mstats.py +0 -0
  72. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/one_hot_encoder.py +0 -0
  73. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/phone_utils.py +0 -0
  74. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/postal_code_utils.py +0 -0
  75. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/progress_bar.py +0 -0
  76. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/psi.py +0 -0
  77. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/sample_utils.py +0 -0
  78. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/sklearn_ext.py +0 -0
  79. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/sort.py +0 -0
  80. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/target_utils.py +0 -0
  81. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/track_info.py +0 -0
  82. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/ts_utils.py +0 -0
  83. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/warning_counter.py +0 -0
  84. {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/version_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: upgini
3
- Version: 1.2.156.dev5
3
+ Version: 1.2.157.dev2
4
4
  Summary: Intelligent data search & enrichment for Machine Learning
5
5
  Project-URL: Bug Reports, https://github.com/upgini/upgini/issues
6
6
  Project-URL: Homepage, https://upgini.com/
@@ -0,0 +1 @@
1
+ __version__ = "1.2.157.dev2"
@@ -6,6 +6,7 @@ import numpy as np
6
6
  import pandas as pd
7
7
  from jarowinkler import jarowinkler_similarity
8
8
 
9
+ from upgini.autofe.operand import OperandKind, OperandValue
9
10
  from upgini.autofe.operator import PandasOperator, VectorizableMixin
10
11
 
11
12
 
@@ -15,7 +16,9 @@ class Min(PandasOperator):
15
16
  is_symmetrical: bool = True
16
17
  has_symmetry_importance: bool = True
17
18
 
18
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
19
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
20
+ left = left.as_series()
21
+ right = right.as_series()
19
22
  return np.minimum(left, right)
20
23
 
21
24
 
@@ -25,7 +28,9 @@ class Max(PandasOperator):
25
28
  is_symmetrical: bool = True
26
29
  has_symmetry_importance: bool = True
27
30
 
28
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
31
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
32
+ left = left.as_series()
33
+ right = right.as_series()
29
34
  return np.maximum(left, right)
30
35
 
31
36
 
@@ -37,7 +42,9 @@ class Add(PandasOperator, VectorizableMixin):
37
42
  has_symmetry_importance: bool = True
38
43
  is_vectorizable: bool = True
39
44
 
40
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
45
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
46
+ left = left.as_series()
47
+ right = right.as_series()
41
48
  return left + right
42
49
 
43
50
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -56,7 +63,9 @@ class Subtract(PandasOperator, VectorizableMixin):
56
63
  has_symmetry_importance: bool = True
57
64
  is_vectorizable: bool = True
58
65
 
59
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
66
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
67
+ left = left.as_series()
68
+ right = right.as_series()
60
69
  return left - right
61
70
 
62
71
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -75,7 +84,9 @@ class Multiply(PandasOperator, VectorizableMixin):
75
84
  has_symmetry_importance: bool = True
76
85
  is_vectorizable: bool = True
77
86
 
78
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
87
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
88
+ left = left.as_series()
89
+ right = right.as_series()
79
90
  return left * right
80
91
 
81
92
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -94,7 +105,9 @@ class Divide(PandasOperator, VectorizableMixin):
94
105
  is_vectorizable: bool = True
95
106
  output_type: Optional[str] = "float"
96
107
 
97
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
108
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
109
+ left = left.as_series()
110
+ right = right.as_series()
98
111
  return left / right.replace(0, np.nan)
99
112
 
100
113
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -111,7 +124,9 @@ class Combine(PandasOperator):
111
124
  has_symmetry_importance: bool = True
112
125
  output_type: Optional[str] = "object"
113
126
 
114
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
127
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
128
+ left = left.as_series()
129
+ right = right.as_series()
115
130
  temp = left.astype(str) + "_" + right.astype(str)
116
131
  temp[left.isna() | right.isna()] = np.nan
117
132
  return pd.Series(temp, index=left.index)
@@ -126,7 +141,9 @@ class CombineThenFreq(PandasOperator):
126
141
  is_distribution_dependent: bool = True
127
142
  input_type: Optional[str] = "discrete"
128
143
 
129
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
144
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
145
+ left = left.as_series()
146
+ right = right.as_series()
130
147
  temp = left.astype(str) + "_" + right.astype(str)
131
148
  temp[left.isna() | right.isna()] = np.nan
132
149
  value_counts = temp.value_counts(normalize=True)
@@ -140,120 +157,40 @@ class Distance(PandasOperator):
140
157
  is_symmetrical: bool = True
141
158
  has_symmetry_importance: bool = True
142
159
 
143
- def _to_float_vector(self, value) -> Optional[np.ndarray]:
144
- if value is None or (isinstance(value, float) and np.isnan(value)):
145
- return None
146
- return np.atleast_1d(np.asarray(value, dtype=np.float64))
147
-
148
- def _stack_vectors(self, arr: np.ndarray, indices: np.ndarray) -> Optional[np.ndarray]:
149
- try:
150
- stacked = np.vstack(arr[indices])
151
- except (ValueError, TypeError):
152
- return None
153
- if stacked.ndim != 2 or stacked.shape[1] == 0:
154
- return None
155
- if stacked.dtype != np.float64:
156
- stacked = stacked.astype(np.float64, copy=False)
157
- return stacked
158
-
159
- def _cosine_distance_stacked(self, left_mat: np.ndarray, right_mat: np.ndarray) -> np.ndarray:
160
- if left_mat.shape != right_mat.shape:
161
- raise ValueError("Left and right matrices must have the same shape")
162
- dots = np.sum(left_mat * right_mat, axis=1)
163
- denom = np.sqrt(np.sum(left_mat * left_mat, axis=1)) * np.sqrt(np.sum(right_mat * right_mat, axis=1))
164
- results = np.full(len(left_mat), np.nan, dtype=np.float64)
165
- valid = denom > 0
166
- results[valid] = 1 - dots[valid] / denom[valid]
167
- return results
168
-
169
- def _try_fast_distance(
170
- self, left_arr: np.ndarray, right_arr: np.ndarray, row_present: np.ndarray, n: int
171
- ) -> Optional[np.ndarray]:
172
- valid_idx = np.flatnonzero(row_present)
173
- if len(valid_idx) == 0:
174
- return None
175
-
176
- left_mat = self._stack_vectors(left_arr, valid_idx)
177
- right_mat = self._stack_vectors(right_arr, valid_idx)
178
- if left_mat is None or right_mat is None:
179
- return None
180
-
181
- stacked_results = self._cosine_distance_stacked(left_mat, right_mat)
182
- if row_present.all():
183
- return stacked_results
184
-
185
- results = np.full(n, np.nan, dtype=np.float64)
186
- results[valid_idx] = stacked_results
187
- return results
188
-
189
- def _parse_vectors(self, left_arr: np.ndarray, right_arr: np.ndarray) -> tuple[list, list, np.ndarray]:
190
- n = len(left_arr)
191
- left_vecs: list[Optional[np.ndarray]] = [None] * n
192
- right_vecs: list[Optional[np.ndarray]] = [None] * n
193
- row_valid = np.zeros(n, dtype=bool)
194
- for i, (left_value, right_value) in enumerate(zip(left_arr, right_arr)):
195
- left_vec = self._to_float_vector(left_value)
196
- right_vec = self._to_float_vector(right_value)
197
- left_vecs[i] = left_vec
198
- right_vecs[i] = right_vec
199
- if left_vec is not None and right_vec is not None and left_vec.size > 0 and right_vec.size > 0:
200
- row_valid[i] = True
201
- return left_vecs, right_vecs, row_valid
202
-
203
- def _masked_cosine_distance(
204
- self, left_mat: np.ndarray, right_mat: np.ndarray, row_valid: np.ndarray
205
- ) -> np.ndarray:
206
- results = self._cosine_distance_stacked(left_mat, right_mat)
207
- if len(results) == len(row_valid):
208
- results[~row_valid] = np.nan
209
- return results
210
-
211
- def _calculate_rowwise(
212
- self, results: np.ndarray, indices: np.ndarray, left_vecs: list, right_vecs: list
213
- ) -> None:
214
- for index in indices:
215
- left_vec = left_vecs[index]
216
- right_vec = right_vecs[index]
217
- dot = np.sum(left_vec * right_vec)
218
- denom = np.sqrt(np.sum(left_vec * left_vec)) * np.sqrt(np.sum(right_vec * right_vec))
219
- if denom != 0:
220
- results[index] = 1 - dot / denom
221
-
222
- def _calculate_fallback_distance(
223
- self, left_arr: np.ndarray, right_arr: np.ndarray, n: int, index: pd.Index
224
- ) -> pd.Series:
225
- results = np.full(n, np.nan, dtype=np.float64)
226
- left_vecs, right_vecs, row_valid = self._parse_vectors(left_arr, right_arr)
227
- if not row_valid.any():
228
- return pd.Series(results, index=index, dtype=np.float64)
229
-
230
- valid_idx = np.flatnonzero(row_valid)
231
- dim = left_vecs[valid_idx[0]].size
232
- if not all(left_vecs[i].size == dim and right_vecs[i].size == dim for i in valid_idx):
233
- self._calculate_rowwise(results, valid_idx, left_vecs, right_vecs)
234
- return pd.Series(results, index=index, dtype=np.float64)
235
-
236
- left_mat = np.zeros((n, dim), dtype=np.float64)
237
- right_mat = np.zeros((n, dim), dtype=np.float64)
238
- left_mat[valid_idx] = np.stack([left_vecs[i] for i in valid_idx])
239
- right_mat[valid_idx] = np.stack([right_vecs[i] for i in valid_idx])
240
-
241
- return pd.Series(self._masked_cosine_distance(left_mat, right_mat, row_valid), index=index, dtype=np.float64)
242
-
243
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
244
- n = len(left)
245
- if n == 0:
246
- return pd.Series(np.full(n, np.nan), index=left.index, dtype=np.float64)
247
-
248
- row_present = left.notna().to_numpy() & right.notna().to_numpy()
249
- left_arr = left.to_numpy()
250
- right_arr = right.to_numpy()
251
-
252
- fast_results = self._try_fast_distance(left_arr, right_arr, row_present, n)
253
- if fast_results is not None:
254
- return pd.Series(fast_results, index=left.index, dtype=np.float64)
255
-
256
- return self._calculate_fallback_distance(left_arr, right_arr, n, left.index)
160
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
161
+ if left.kind == OperandKind.MATRIX and right.kind == OperandKind.MATRIX:
162
+ return self._calculate_binary_matrices(left, right)
163
+ return self._calculate_binary_series(left.as_series(), right.as_series())
164
+
165
+ def _calculate_binary_matrices(self, left: OperandValue, right: OperandValue) -> pd.Series:
166
+ left_mat = left.as_matrix()
167
+ right_mat = right.as_matrix()
168
+ dot = np.sum(left_mat * right_mat, axis=1)
169
+ left_norm = np.sqrt(np.sum(left_mat * left_mat, axis=1))
170
+ right_norm = np.sqrt(np.sum(right_mat * right_mat, axis=1))
171
+ denom = left_norm * right_norm
172
+ with np.errstate(invalid="ignore", divide="ignore"):
173
+ result = 1 - dot / denom
174
+ result[denom == 0] = np.nan
175
+ return pd.Series(result, index=left.index, dtype=np.float64)
176
+
177
+ def _calculate_binary_series(self, left: pd.Series, right: pd.Series) -> pd.Series:
178
+ return pd.Series(
179
+ 1 - self.__dot(left, right) / (self.__norm(left) * self.__norm(right)), index=left.index
180
+ ).astype(np.float64)
181
+
182
+ # row-wise dot product, handling None values
183
+ def __dot(self, left: pd.Series, right: pd.Series) -> pd.Series:
184
+ left = left.apply(lambda x: np.array(x).astype(np.float64))
185
+ right = right.apply(lambda x: np.array(x).astype(np.float64))
186
+ res = (left.dropna() * right.dropna()).apply(np.sum)
187
+ res = res.reindex(left.index.union(right.index))
188
+ return res
189
+
190
+ # Calculate the norm of a vector, handling None values
191
+ def __norm(self, vector: pd.Series) -> pd.Series:
192
+ vector = vector.fillna(np.nan)
193
+ return np.sqrt(self.__dot(vector, vector))
257
194
 
258
195
 
259
196
  # Left for backward compatibility
@@ -264,12 +201,14 @@ class Sim(Distance):
264
201
  is_symmetrical: bool = True
265
202
  has_symmetry_importance: bool = True
266
203
 
267
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
204
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
268
205
  return 1 - super().calculate_binary(left, right)
269
206
 
270
207
 
271
208
  class StringSim(PandasOperator, abc.ABC):
272
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
209
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
210
+ left = left.as_series()
211
+ right = right.as_series()
273
212
  sims = []
274
213
  for i in left.index:
275
214
  left_i = self._prepare_value(left.get(i))
@@ -7,6 +7,7 @@ import pandas as pd
7
7
  from pandas.core.arrays.timedeltas import TimedeltaArray
8
8
  from pydantic import BaseModel, __version__ as pydantic_version
9
9
 
10
+ from upgini.autofe.operand import OperandValue
10
11
  from upgini.autofe.operator import PandasOperator, ParametrizedOperator
11
12
  from upgini.autofe.utils import bin_index, bin_index_many, bin_index_vectorized, pydantic_validator
12
13
 
@@ -64,7 +65,9 @@ class DateDiff(PandasOperator, DateDiffMixin):
64
65
  )
65
66
  return res
66
67
 
67
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
68
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
69
+ left = left.as_series()
70
+ right = right.as_series()
68
71
  if left.isna().all() or right.isna().all():
69
72
  return pd.Series([None] * len(left), index=left.index, dtype=np.float64)
70
73
 
@@ -95,7 +98,9 @@ class DateDiffType2(PandasOperator, DateDiffMixin):
95
98
  )
96
99
  return res
97
100
 
98
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
101
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
102
+ left = left.as_series()
103
+ right = right.as_series()
99
104
  left = self._convert_to_date(left, self.left_unit)
100
105
  right = self._convert_to_date(right, self.right_unit)
101
106
  future = right + (left.dt.year - right.dt.year).apply(
@@ -187,7 +192,9 @@ class DateListDiffLists(PandasOperator, DateDiffMixin, ParametrizedOperator):
187
192
  diffs = diffs[diffs > 0]
188
193
  return np.atleast_1d(np.asarray(diffs, dtype=np.float64)).tolist()
189
194
 
190
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
195
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
196
+ left = left.as_series()
197
+ right = right.as_series()
191
198
  if left.isna().all() or right.isna().all():
192
199
  return pd.Series([None] * len(left), index=left.index, dtype=object)
193
200
 
@@ -271,7 +278,8 @@ class DateListDiffAggWithinBounds(PandasOperator, ParametrizedOperator):
271
278
  return agg_res / orig_len
272
279
  return agg_res
273
280
 
274
- def calculate_unary(self, data: pd.Series) -> pd.Series:
281
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
282
+ data = data.as_series()
275
283
  results = np.empty(len(data), dtype=np.float64)
276
284
  results[:] = np.nan
277
285
  for i, diffs in enumerate(data.to_numpy()):
@@ -332,13 +340,15 @@ class DateListDiff(PandasOperator, DateDiffMixin, ParametrizedOperator):
332
340
  return pd.Series([None] * len(left), index=left.index, dtype=np.float64)
333
341
 
334
342
  right_mask = DateListDiffLists._non_empty_list_mask(right)
335
- diff_lists = self._lists_op().calculate_binary(left, right)
336
- result = self._agg_op().calculate_unary(diff_lists)
343
+ diff_lists = self._lists_op().calculate(left=left, right=right)
344
+ result = self._agg_op().calculate(data=diff_lists).as_series()
337
345
  if self.aggregation in _count_aggregations:
338
346
  result[~right_mask] = 0.0
339
347
  return result.astype(np.float64)
340
348
 
341
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
349
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
350
+ left = left.as_series()
351
+ right = right.as_series()
342
352
  return self._compose_list_diff(left, right)
343
353
 
344
354
 
@@ -404,7 +414,9 @@ class DatePercentileBase(PandasOperator, abc.ABC):
404
414
 
405
415
  date_unit: Optional[str] = None
406
416
 
407
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
417
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
418
+ left = left.as_series()
419
+ right = right.as_series()
408
420
  # Assuming that left is a date column, right is a feature column
409
421
  left = pd.to_datetime(left, unit=self.date_unit)
410
422
 
@@ -8,13 +8,14 @@ import pandas as pd
8
8
  from pandas._typing import DtypeObj
9
9
 
10
10
  from upgini.autofe.all_operators import find_op
11
+ from upgini.autofe.operand import CalculationContext, OperandValue
11
12
  from upgini.autofe.operator import Operator, PandasOperator
12
13
  from upgini.autofe.timeseries.base import TimeSeriesBase
13
14
  from upgini.autofe.utils import pydantic_dump_method, pydantic_parse_method
14
15
 
15
16
 
16
17
  class Column:
17
- def __init__(self, name: str, data: Optional[pd.Series] = None, calculate_all=False):
18
+ def __init__(self, name: str, data: Optional[OperandValue] = None, calculate_all=False):
18
19
  self.name = name
19
20
  self.data = data
20
21
  self.calculate_all = calculate_all
@@ -61,8 +62,12 @@ class Column:
61
62
  def infer_type(self, data: pd.DataFrame) -> DtypeObj:
62
63
  return data[self.name].dtype
63
64
 
64
- def calculate(self, data: pd.DataFrame) -> pd.Series:
65
- self.data = data[self.name]
65
+ def calculate(self, data: Union[pd.DataFrame, CalculationContext]) -> pd.Series:
66
+ ctx = data if isinstance(data, CalculationContext) else CalculationContext.from_dataframe(data)
67
+ return self._eval(ctx).as_series()
68
+
69
+ def _eval(self, ctx: CalculationContext) -> OperandValue:
70
+ self.data = ctx.resolve_operand(self.name)
66
71
  return self.data
67
72
 
68
73
  def to_formula(self, **kwargs) -> str:
@@ -236,27 +241,38 @@ class Feature:
236
241
  # either a symmetrical operator or group by
237
242
  return self.children[0].infer_type(data)
238
243
 
239
- def calculate(self, data: pd.DataFrame, is_root=False) -> Union[pd.Series, pd.DataFrame]:
240
- if isinstance(self.op, PandasOperator):
241
- if self.op.is_vector:
242
- ds = [child.calculate(data) for child in self.children]
243
- new_data = self.op.calculate(data=ds)
244
- else:
245
- d1 = self.children[0].calculate(data)
246
- d2 = None if len(self.children) < 2 else self.children[1].calculate(data)
247
- new_data = self.op.calculate(data=d1, left=d1, right=d2)
248
- else:
249
- raise NotImplementedError(f"Unrecognized operator {self.op.name}.")
250
-
251
- if (str(new_data.dtype) == "category") | (str(new_data.dtype) == "object"):
252
- pass
253
- else:
254
- new_data = new_data.replace([-np.inf, np.inf], np.nan)
244
+ def calculate(
245
+ self,
246
+ data: Union[pd.DataFrame, CalculationContext],
247
+ is_root: bool = False,
248
+ ) -> Union[pd.Series, pd.DataFrame]:
249
+ ctx = data if isinstance(data, CalculationContext) else CalculationContext.from_dataframe(data)
250
+ result = self._eval(ctx)
251
+ new_data = self._operand_to_output(result)
255
252
 
256
253
  if is_root:
257
254
  self.data = new_data
258
255
  return new_data
259
256
 
257
+ def _eval(self, ctx: CalculationContext) -> OperandValue:
258
+ if isinstance(self.op, PandasOperator):
259
+ if self.op.is_vector:
260
+ operands = [child._eval(ctx) for child in self.children]
261
+ return self.op.calculate(data=operands)
262
+ left = self.children[0]._eval(ctx)
263
+ if len(self.children) < 2:
264
+ return self.op.calculate(data=left)
265
+ right = self.children[1]._eval(ctx)
266
+ return self.op.calculate(left=left, right=right)
267
+ raise NotImplementedError(f"Unrecognized operator {self.op.name}.")
268
+
269
+ @staticmethod
270
+ def _operand_to_output(operand: OperandValue) -> Union[pd.Series, pd.DataFrame]:
271
+ new_data = operand.as_series()
272
+ if (str(new_data.dtype) == "category") | (str(new_data.dtype) == "object"):
273
+ return new_data
274
+ return new_data.replace([-np.inf, np.inf], np.nan)
275
+
260
276
  @staticmethod
261
277
  def check_xor(left: Union[Column, "Feature"], right: Union[Column, "Feature"]) -> bool:
262
278
  def _get_all_columns(feature: Union[Column, "Feature"]) -> List[str]:
@@ -2,6 +2,7 @@ from typing import Optional
2
2
 
3
3
  import pandas as pd
4
4
 
5
+ from upgini.autofe.operand import OperandValue
5
6
  from upgini.autofe.operator import PandasOperator, ParametrizedOperator, VectorizableMixin
6
7
 
7
8
 
@@ -27,7 +28,9 @@ class GroupByThenAgg(
27
28
  return None
28
29
  return cls(agg=agg)
29
30
 
30
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
31
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
32
+ left = left.as_series()
33
+ right = right.as_series()
31
34
  temp = left.groupby(right).agg(self.agg.lower())
32
35
  return self._loc(right, temp)
33
36
 
@@ -46,7 +49,9 @@ class GroupByThenRank(PandasOperator, VectorizableMixin):
46
49
  output_type: Optional[str] = "float"
47
50
  is_distribution_dependent: bool = True
48
51
 
49
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
52
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
53
+ left = left.as_series()
54
+ right = right.as_series()
50
55
  temp = pd.DataFrame(left[~right.isna()].groupby(right).rank(ascending=True, pct=True)).reset_index()
51
56
  return temp.merge(pd.DataFrame(right).reset_index(), how="right", on=["index"])[left.name]
52
57
 
@@ -66,7 +71,9 @@ class GroupByThenNUnique(PandasOperator, VectorizableMixin):
66
71
  is_distribution_dependent: bool = True
67
72
  input_type: Optional[str] = "discrete"
68
73
 
69
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
74
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
75
+ left = left.as_series()
76
+ right = right.as_series()
70
77
  nunique = left.groupby(right).nunique()
71
78
  return self._loc(right, nunique)
72
79
 
@@ -85,7 +92,9 @@ class GroupByThenFreq(PandasOperator):
85
92
  is_distribution_dependent: bool = True
86
93
  input_type: Optional[str] = "discrete"
87
94
 
88
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
95
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
96
+ left = left.as_series()
97
+ right = right.as_series()
89
98
  def _f(x):
90
99
  value_counts = x.value_counts(normalize=True)
91
100
  return self._loc(x, value_counts)
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from typing import Dict, Optional, Union
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+
11
+ class OperandKind(str, Enum):
12
+ SERIES = "series"
13
+ MATRIX = "matrix"
14
+ ARRAY = "array"
15
+
16
+
17
+ @dataclass
18
+ class OperandValue:
19
+ kind: OperandKind
20
+ data: Union[pd.Series, np.ndarray]
21
+ index: pd.Index
22
+ source: Optional[str] = None
23
+
24
+ @classmethod
25
+ def from_series(cls, series: pd.Series, source: Optional[str] = None) -> "OperandValue":
26
+ return cls(kind=OperandKind.SERIES, data=series, index=series.index, source=source)
27
+
28
+ @classmethod
29
+ def from_matrix(cls, matrix: np.ndarray, index: pd.Index, source: Optional[str] = None) -> "OperandValue":
30
+ return cls(kind=OperandKind.MATRIX, data=matrix, index=index, source=source)
31
+
32
+ @classmethod
33
+ def from_array(cls, array: np.ndarray, index: pd.Index, source: Optional[str] = None) -> "OperandValue":
34
+ return cls(kind=OperandKind.ARRAY, data=array, index=index, source=source)
35
+
36
+ def as_series(self) -> pd.Series:
37
+ if self.kind == OperandKind.SERIES:
38
+ return self.data # type: ignore[return-value]
39
+ if self.kind == OperandKind.ARRAY:
40
+ return pd.Series(self.data, index=self.index, dtype=np.float64)
41
+ if self.kind == OperandKind.MATRIX:
42
+ return pd.Series(list(self.as_matrix()), index=self.index)
43
+ raise TypeError(f"Cannot convert operand kind {self.kind.value} to series")
44
+
45
+ def as_matrix(self) -> np.ndarray:
46
+ if self.kind != OperandKind.MATRIX:
47
+ raise TypeError(f"Cannot convert operand kind {self.kind.value} to matrix")
48
+ return self.data # type: ignore[return-value]
49
+
50
+
51
+ @dataclass
52
+ class CalculationContext:
53
+ data: Optional[pd.DataFrame] = None
54
+ matrices: Dict[str, np.ndarray] = field(default_factory=dict)
55
+ row_index: Optional[pd.Index] = None
56
+
57
+ def __post_init__(self) -> None:
58
+ if self.data is None and not self.matrices:
59
+ raise ValueError("CalculationContext requires data and/or matrices")
60
+ if self.matrices:
61
+ lengths = {name: matrix.shape[0] for name, matrix in self.matrices.items()}
62
+ if len(set(lengths.values())) > 1:
63
+ raise ValueError(f"Matrix row counts mismatch: {lengths}")
64
+
65
+ @property
66
+ def index(self) -> pd.Index:
67
+ if self.data is not None:
68
+ return self.data.index
69
+ if self.row_index is not None:
70
+ return self.row_index
71
+ if self.matrices:
72
+ nrows = next(iter(self.matrices.values())).shape[0]
73
+ return pd.RangeIndex(nrows)
74
+ raise ValueError("CalculationContext has no index source")
75
+
76
+ @classmethod
77
+ def from_dataframe(cls, data: pd.DataFrame) -> "CalculationContext":
78
+ return cls(data=data)
79
+
80
+ @classmethod
81
+ def from_matrices(
82
+ cls,
83
+ matrices: Dict[str, np.ndarray],
84
+ index: Optional[pd.Index] = None,
85
+ ) -> "CalculationContext":
86
+ return cls(matrices=matrices, row_index=index)
87
+
88
+ def resolve_operand(self, name: str) -> OperandValue:
89
+ matrix = self.matrices.get(name)
90
+ if matrix is not None:
91
+ return OperandValue.from_matrix(matrix, index=self.index, source=name)
92
+ if self.data is not None and name in self.data:
93
+ return OperandValue.from_series(self.data[name], source=name)
94
+ raise KeyError(name)
95
+
96
+
97
+ def wrap_operand(
98
+ result: Union[pd.Series, np.ndarray, "OperandValue"],
99
+ source: Optional[str] = None,
100
+ index: Optional[pd.Index] = None,
101
+ ) -> "OperandValue":
102
+ if isinstance(result, OperandValue):
103
+ return result
104
+ if isinstance(result, np.ndarray):
105
+ if index is None:
106
+ raise TypeError("index is required when wrapping ndarray result")
107
+ if result.ndim == 1:
108
+ return OperandValue.from_array(result, index=index, source=source)
109
+ if result.ndim == 2:
110
+ return OperandValue.from_matrix(result, index=index, source=source)
111
+ raise TypeError(f"Cannot wrap ndarray with {result.ndim} dimensions")
112
+ return OperandValue.from_series(result, source=source)
@@ -6,6 +6,8 @@ import numpy as np
6
6
  import pandas as pd
7
7
  from pydantic import BaseModel, PrivateAttr
8
8
 
9
+ from upgini.autofe.operand import OperandValue, wrap_operand
10
+
9
11
 
10
12
  class OperatorRegistry(type(BaseModel)):
11
13
  _registry = {}
@@ -132,21 +134,35 @@ MAIN_COLUMN = "main_column"
132
134
 
133
135
 
134
136
  class PandasOperator(Operator, abc.ABC):
135
- def calculate(self, **kwargs) -> pd.Series:
136
- if self.is_unary:
137
- return self.calculate_unary(kwargs["data"])
138
- elif self.is_binary or self.is_grouping:
139
- return self.calculate_binary(kwargs["left"], kwargs["right"])
140
- else:
141
- return self.calculate_vector(kwargs["data"])
137
+ @staticmethod
138
+ def _operand(value: Union[pd.Series, OperandValue]) -> OperandValue:
139
+ if isinstance(value, OperandValue):
140
+ return value
141
+ return OperandValue.from_series(value)
142
142
 
143
- def calculate_unary(self, data: pd.Series) -> pd.Series:
143
+ def calculate(self, **kwargs) -> OperandValue:
144
+ if self.is_unary:
145
+ data = self._operand(kwargs["data"])
146
+ return wrap_operand(self.calculate_unary(data), source=data.source, index=data.index)
147
+ if self.is_binary or self.is_grouping:
148
+ left = self._operand(kwargs["left"])
149
+ right = self._operand(kwargs["right"])
150
+ return wrap_operand(self.calculate_binary(left, right), source=left.source, index=left.index)
151
+ operands = [self._operand(value) for value in kwargs["data"]]
152
+ index = operands[0].index if operands else None
153
+ return wrap_operand(self.calculate_vector(operands), index=index)
154
+
155
+ def calculate_unary(self, data: OperandValue) -> Union[pd.Series, np.ndarray, OperandValue]:
144
156
  pass
145
157
 
146
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
158
+ def calculate_binary(
159
+ self, left: OperandValue, right: OperandValue
160
+ ) -> Union[pd.Series, np.ndarray, OperandValue]:
147
161
  pass
148
162
 
149
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
163
+ def calculate_vector(
164
+ self, data: List[OperandValue]
165
+ ) -> Union[pd.Series, np.ndarray, OperandValue]:
150
166
  pass
151
167
 
152
168
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -2,6 +2,7 @@ import abc
2
2
  from typing import Dict, List, Optional, Tuple
3
3
 
4
4
  import pandas as pd
5
+ from upgini.autofe.operand import OperandValue
5
6
  from upgini.autofe.operator import PandasOperator
6
7
 
7
8
 
@@ -22,7 +23,8 @@ class TimeSeriesBase(PandasOperator, abc.ABC):
22
23
  )
23
24
  return res
24
25
 
25
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
26
+ def calculate_vector(self, data: List[OperandValue]) -> pd.Series:
27
+ data = [operand.as_series() for operand in data]
26
28
  # assuming first is date, last is value, rest is group columns
27
29
  date = pd.to_datetime(data[0], unit=self.date_unit, errors="coerce")
28
30
  ts = pd.concat([date] + data[1:], axis=1)
@@ -5,6 +5,7 @@ import numpy as np
5
5
  import pandas as pd
6
6
 
7
7
  from upgini.autofe.all_operators import find_op
8
+ from upgini.autofe.operand import OperandValue
8
9
  from upgini.autofe.operator import PandasOperator, ParametrizedOperator
9
10
  from upgini.autofe.timeseries.base import TimeSeriesBase
10
11
  from upgini.autofe.utils import pydantic_validator
@@ -117,14 +118,15 @@ class CrossSeriesInteraction(TimeSeriesBase, ParametrizedOperator):
117
118
  def _get_interaction_op_name(self) -> str:
118
119
  return self.interaction_op.alias or self.interaction_op.to_formula()
119
120
 
120
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
121
+ def calculate_vector(self, data: List[OperandValue]) -> pd.Series:
122
+ data = [operand.as_series() for operand in data]
121
123
  left_mask = self._get_mask(data, self.left_descriptor)
122
124
  left = self._extract_series(data, left_mask)
123
125
 
124
126
  right_mask = self._get_mask(data, self.right_descriptor)
125
127
  right = self._extract_series(data, right_mask)
126
128
 
127
- interaction: pd.Series = self.interaction_op.calculate_binary(left, right)
129
+ interaction: pd.Series = self.interaction_op.calculate(left=left, right=right).as_series()
128
130
  interaction = interaction.reindex(self._get_index(data))
129
131
  res = pd.Series(np.nan, index=data[-1].index, name=data[-1].name)
130
132
  res.loc[left_mask] = interaction[left_mask].values
@@ -137,7 +139,7 @@ class CrossSeriesInteraction(TimeSeriesBase, ParametrizedOperator):
137
139
 
138
140
  def _extract_series(self, data: List[pd.Series], mask: pd.Series) -> pd.Series:
139
141
  masked_data = [d[mask] for d in data]
140
- shifted = super().calculate_vector(masked_data)
142
+ shifted = super().calculate_vector([OperandValue.from_series(d) for d in masked_data])
141
143
  shifted.index = self._get_index(masked_data)
142
144
  return shifted
143
145
 
@@ -3,6 +3,7 @@ from typing import Dict, List, Optional
3
3
  import numpy as np
4
4
  import pandas as pd
5
5
 
6
+ from upgini.autofe.operand import OperandValue
6
7
  from upgini.autofe.operator import PandasOperator, ParametrizedOperator, VectorizableMixin
7
8
  from upgini.autofe.utils import bin_index, bin_index_vectorized, pydantic_validator
8
9
 
@@ -13,7 +14,8 @@ class Abs(PandasOperator, VectorizableMixin):
13
14
  is_vectorizable: bool = True
14
15
  group_index: int = 0
15
16
 
16
- def calculate_unary(self, data: pd.Series) -> pd.Series:
17
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
18
+ data = data.as_series()
17
19
  return data.astype(np.float64).abs()
18
20
 
19
21
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -27,7 +29,8 @@ class Log(PandasOperator, VectorizableMixin):
27
29
  output_type: Optional[str] = "float"
28
30
  group_index: int = 0
29
31
 
30
- def calculate_unary(self, data: pd.Series) -> pd.Series:
32
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
33
+ data = data.as_series()
31
34
  return self._round_value(np.log(np.abs(data.replace(0, np.nan))), 10)
32
35
 
33
36
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -41,7 +44,8 @@ class Sqrt(PandasOperator, VectorizableMixin):
41
44
  output_type: Optional[str] = "float"
42
45
  group_index: int = 0
43
46
 
44
- def calculate_unary(self, data: pd.Series) -> pd.Series:
47
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
48
+ data = data.as_series()
45
49
  return self._round_value(np.sqrt(np.abs(data)))
46
50
 
47
51
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -54,7 +58,8 @@ class Square(PandasOperator, VectorizableMixin):
54
58
  is_vectorizable: bool = True
55
59
  group_index: int = 0
56
60
 
57
- def calculate_unary(self, data: pd.Series) -> pd.Series:
61
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
62
+ data = data.as_series()
58
63
  return np.square(data)
59
64
 
60
65
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -68,7 +73,8 @@ class Sigmoid(PandasOperator, VectorizableMixin):
68
73
  output_type: Optional[str] = "float"
69
74
  group_index: int = 0
70
75
 
71
- def calculate_unary(self, data: pd.Series) -> pd.Series:
76
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
77
+ data = data.as_series()
72
78
  return self._round_value(1 / (1 + np.exp(-data)))
73
79
 
74
80
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -83,7 +89,8 @@ class Floor(PandasOperator, VectorizableMixin):
83
89
  input_type: Optional[str] = "continuous"
84
90
  group_index: int = 0
85
91
 
86
- def calculate_unary(self, data: pd.Series) -> pd.Series:
92
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
93
+ data = data.as_series()
87
94
  return np.floor(data)
88
95
 
89
96
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -97,7 +104,8 @@ class Residual(PandasOperator, VectorizableMixin):
97
104
  input_type: Optional[str] = "continuous"
98
105
  group_index: int = 0
99
106
 
100
- def calculate_unary(self, data: pd.Series) -> pd.Series:
107
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
108
+ data = data.as_series()
101
109
  return data - np.floor(data)
102
110
 
103
111
  def calculate_group(self, data: pd.DataFrame, **kwargs) -> pd.DataFrame:
@@ -111,7 +119,8 @@ class Freq(PandasOperator):
111
119
  is_distribution_dependent: bool = True
112
120
  input_type: Optional[str] = "discrete"
113
121
 
114
- def calculate_unary(self, data: pd.Series) -> pd.Series:
122
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
123
+ data = data.as_series()
115
124
  value_counts = data.value_counts(normalize=True)
116
125
  return self._loc(data, value_counts)
117
126
 
@@ -122,7 +131,8 @@ class Norm(PandasOperator):
122
131
  output_type: Optional[str] = "float"
123
132
  norm: Optional[float] = None
124
133
 
125
- def calculate_unary(self, data: pd.Series) -> pd.Series:
134
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
135
+ data = data.as_series()
126
136
  data_dropna = data.dropna()
127
137
  if data_dropna.empty:
128
138
  return data
@@ -152,7 +162,7 @@ class Embeddings(PandasOperator):
152
162
  name: str = "emb"
153
163
  is_unary: bool = True
154
164
  input_type: Optional[str] = "string"
155
- output_type: Optional[str] = "vector"
165
+ output_type: Optional[str] = "matrix"
156
166
 
157
167
 
158
168
  class Bin(PandasOperator):
@@ -162,7 +172,8 @@ class Bin(PandasOperator):
162
172
  bin_bounds: List[int] = []
163
173
  is_categorical: bool = True
164
174
 
165
- def calculate_unary(self, data: pd.Series) -> pd.Series:
175
+ def calculate_unary(self, data: OperandValue) -> pd.Series:
176
+ data = data.as_series()
166
177
  bounds_arr = np.asarray(self.bin_bounds, dtype=np.float64)
167
178
  values = pd.to_numeric(data, errors="coerce").to_numpy(dtype=np.float64, copy=False)
168
179
  result = bin_index_vectorized(values, bounds_arr)
@@ -2,6 +2,7 @@ from typing import List, Optional
2
2
 
3
3
  import pandas as pd
4
4
 
5
+ from upgini.autofe.operand import OperandValue
5
6
  from upgini.autofe.operator import OperatorRegistry, PandasOperator, ParametrizedOperator, VectorizableMixin
6
7
 
7
8
 
@@ -11,8 +12,9 @@ class Mean(PandasOperator, VectorizableMixin):
11
12
  is_vector: bool = True
12
13
  group_index: int = 0
13
14
 
14
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
15
- return pd.DataFrame(data).T.fillna(0).mean(axis=1)
15
+ def calculate_vector(self, data: List[OperandValue]) -> pd.Series:
16
+ series = [operand.as_series() for operand in data]
17
+ return pd.DataFrame(series).T.fillna(0).mean(axis=1)
16
18
 
17
19
 
18
20
  class Sum(PandasOperator, VectorizableMixin):
@@ -20,8 +22,9 @@ class Sum(PandasOperator, VectorizableMixin):
20
22
  is_vector: bool = True
21
23
  group_index: int = 0
22
24
 
23
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
24
- return pd.DataFrame(data).T.fillna(0).sum(axis=1)
25
+ def calculate_vector(self, data: List[OperandValue]) -> pd.Series:
26
+ series = [operand.as_series() for operand in data]
27
+ return pd.DataFrame(series).T.fillna(0).sum(axis=1)
25
28
 
26
29
 
27
30
  class Vectorize(PandasOperator, VectorizableMixin):
@@ -29,8 +32,9 @@ class Vectorize(PandasOperator, VectorizableMixin):
29
32
  is_vector: bool = True
30
33
  group_index: int = 0
31
34
 
32
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
33
- return pd.DataFrame(data).T.apply(lambda x: x.to_list(), axis=1)
35
+ def calculate_vector(self, data: List[OperandValue]) -> pd.Series:
36
+ series = [operand.as_series() for operand in data]
37
+ return pd.DataFrame(series).T.apply(lambda x: x.to_list(), axis=1)
34
38
 
35
39
 
36
40
  class OnnxModel(PandasOperator, ParametrizedOperator, metaclass=OperatorRegistry):
@@ -1 +0,0 @@
1
- __version__ = "1.2.156.dev5"
File without changes
File without changes
File without changes