upgini 1.2.156.dev5__tar.gz → 1.2.157.dev1__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.dev1}/PKG-INFO +1 -1
  2. upgini-1.2.157.dev1/src/upgini/__about__.py +1 -0
  3. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/binary.py +48 -124
  4. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/date.py +20 -8
  5. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/feature.py +38 -19
  6. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/groupby.py +13 -4
  7. upgini-1.2.157.dev1/src/upgini/autofe/operand.py +73 -0
  8. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/operator.py +20 -7
  9. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/base.py +3 -1
  10. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/cross.py +5 -3
  11. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/unary.py +21 -10
  12. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/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.dev1}/.gitignore +0 -0
  15. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/LICENSE +0 -0
  16. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/README.md +0 -0
  17. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/pyproject.toml +0 -0
  18. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/__init__.py +0 -0
  19. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/ads.py +0 -0
  20. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/ads_management/__init__.py +0 -0
  21. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/ads_management/ads_manager.py +0 -0
  22. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/__init__.py +0 -0
  23. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/all_operators.py +0 -0
  24. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/__init__.py +0 -0
  25. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/delta.py +0 -0
  26. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/lag.py +0 -0
  27. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/roll.py +0 -0
  28. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/trend.py +0 -0
  29. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/timeseries/volatility.py +0 -0
  30. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/autofe/utils.py +0 -0
  31. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/data_source/__init__.py +0 -0
  32. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/data_source/data_source_publisher.py +0 -0
  33. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/dataset.py +0 -0
  34. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/errors.py +0 -0
  35. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/features_enricher.py +0 -0
  36. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/http.py +0 -0
  37. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/mdc/__init__.py +0 -0
  38. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/mdc/context.py +0 -0
  39. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/metadata.py +0 -0
  40. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/metrics.py +0 -0
  41. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/normalizer/__init__.py +0 -0
  42. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/normalizer/normalize_utils.py +0 -0
  43. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/resource_bundle/__init__.py +0 -0
  44. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/resource_bundle/exceptions.py +0 -0
  45. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/resource_bundle/strings.properties +0 -0
  46. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/resource_bundle/strings_widget.properties +0 -0
  47. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/sampler/__init__.py +0 -0
  48. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/sampler/base.py +0 -0
  49. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/sampler/random_under_sampler.py +0 -0
  50. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/sampler/utils.py +0 -0
  51. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/search_task.py +0 -0
  52. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/spinner.py +0 -0
  53. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/Roboto-Regular.ttf +0 -0
  54. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/__init__.py +0 -0
  55. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/base_search_key_detector.py +0 -0
  56. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/blocked_time_series.py +0 -0
  57. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/config.py +0 -0
  58. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/country_utils.py +0 -0
  59. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/custom_loss_utils.py +0 -0
  60. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/cv_utils.py +0 -0
  61. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/datetime_utils.py +0 -0
  62. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/deduplicate_utils.py +0 -0
  63. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/display_utils.py +0 -0
  64. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/email_utils.py +0 -0
  65. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/fallback_progress_bar.py +0 -0
  66. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/feature_info.py +0 -0
  67. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/features_validator.py +0 -0
  68. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/format.py +0 -0
  69. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/hash_utils.py +0 -0
  70. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/ip_utils.py +0 -0
  71. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/mstats.py +0 -0
  72. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/one_hot_encoder.py +0 -0
  73. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/phone_utils.py +0 -0
  74. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/postal_code_utils.py +0 -0
  75. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/progress_bar.py +0 -0
  76. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/psi.py +0 -0
  77. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/sample_utils.py +0 -0
  78. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/sklearn_ext.py +0 -0
  79. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/sort.py +0 -0
  80. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/target_utils.py +0 -0
  81. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/track_info.py +0 -0
  82. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/ts_utils.py +0 -0
  83. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/src/upgini/utils/warning_counter.py +0 -0
  84. {upgini-1.2.156.dev5 → upgini-1.2.157.dev1}/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.dev1
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.dev1"
@@ -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 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,25 @@ 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
+ left = left.as_series()
162
+ right = right.as_series()
163
+ return pd.Series(
164
+ 1 - self.__dot(left, right) / (self.__norm(left) * self.__norm(right)), index=left.index
165
+ ).astype(np.float64)
166
+
167
+ # row-wise dot product, handling None values
168
+ def __dot(self, left: pd.Series, right: pd.Series) -> pd.Series:
169
+ left = left.apply(lambda x: np.array(x).astype(np.float64))
170
+ right = right.apply(lambda x: np.array(x).astype(np.float64))
171
+ res = (left.dropna() * right.dropna()).apply(np.sum)
172
+ res = res.reindex(left.index.union(right.index))
173
+ return res
174
+
175
+ # Calculate the norm of a vector, handling None values
176
+ def __norm(self, vector: pd.Series) -> pd.Series:
177
+ vector = vector.fillna(np.nan)
178
+ return np.sqrt(self.__dot(vector, vector))
257
179
 
258
180
 
259
181
  # Left for backward compatibility
@@ -264,12 +186,14 @@ class Sim(Distance):
264
186
  is_symmetrical: bool = True
265
187
  has_symmetry_importance: bool = True
266
188
 
267
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
189
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
268
190
  return 1 - super().calculate_binary(left, right)
269
191
 
270
192
 
271
193
  class StringSim(PandasOperator, abc.ABC):
272
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
194
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
195
+ left = left.as_series()
196
+ right = right.as_series()
273
197
  sims = []
274
198
  for i in left.index:
275
199
  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)
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, wrap_operand
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,41 @@ 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
+ result = self.op.calculate(data=operands)
262
+ return wrap_operand(result)
263
+ left = self.children[0]._eval(ctx)
264
+ if len(self.children) < 2:
265
+ result = self.op.calculate(data=left)
266
+ return wrap_operand(result, source=left.source)
267
+ right = self.children[1]._eval(ctx)
268
+ result = self.op.calculate(left=left, right=right)
269
+ return wrap_operand(result, source=left.source)
270
+ raise NotImplementedError(f"Unrecognized operator {self.op.name}.")
271
+
272
+ @staticmethod
273
+ def _operand_to_output(operand: OperandValue) -> Union[pd.Series, pd.DataFrame]:
274
+ new_data = operand.as_series()
275
+ if (str(new_data.dtype) == "category") | (str(new_data.dtype) == "object"):
276
+ return new_data
277
+ return new_data.replace([-np.inf, np.inf], np.nan)
278
+
260
279
  @staticmethod
261
280
  def check_xor(left: Union[Column, "Feature"], right: Union[Column, "Feature"]) -> bool:
262
281
  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,73 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import 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: pd.DataFrame
54
+
55
+ @property
56
+ def index(self) -> pd.Index:
57
+ return self.data.index
58
+
59
+ @classmethod
60
+ def from_dataframe(cls, data: pd.DataFrame) -> "CalculationContext":
61
+ return cls(data=data)
62
+
63
+ def resolve_operand(self, name: str) -> OperandValue:
64
+ return OperandValue.from_series(self.data[name], source=name)
65
+
66
+
67
+ def wrap_operand(
68
+ result: Union[pd.Series, "OperandValue"],
69
+ source: Optional[str] = None,
70
+ ) -> "OperandValue":
71
+ if isinstance(result, OperandValue):
72
+ return result
73
+ 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
10
+
9
11
 
10
12
  class OperatorRegistry(type(BaseModel)):
11
13
  _registry = {}
@@ -132,21 +134,32 @@ MAIN_COLUMN = "main_column"
132
134
 
133
135
 
134
136
  class PandasOperator(Operator, abc.ABC):
135
- def calculate(self, **kwargs) -> pd.Series:
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
+
143
+ def calculate(self, **kwargs) -> Union[pd.Series, OperandValue]:
136
144
  if self.is_unary:
137
- return self.calculate_unary(kwargs["data"])
145
+ return self.calculate_unary(self._operand(kwargs["data"]))
138
146
  elif self.is_binary or self.is_grouping:
139
- return self.calculate_binary(kwargs["left"], kwargs["right"])
147
+ return self.calculate_binary(
148
+ self._operand(kwargs["left"]),
149
+ self._operand(kwargs["right"]),
150
+ )
140
151
  else:
141
- return self.calculate_vector(kwargs["data"])
152
+ return self.calculate_vector([self._operand(value) for value in kwargs["data"]])
142
153
 
143
- def calculate_unary(self, data: pd.Series) -> pd.Series:
154
+ def calculate_unary(self, data: OperandValue) -> Union[pd.Series, OperandValue]:
144
155
  pass
145
156
 
146
- def calculate_binary(self, left: pd.Series, right: pd.Series) -> pd.Series:
157
+ def calculate_binary(
158
+ self, left: OperandValue, right: OperandValue
159
+ ) -> Union[pd.Series, OperandValue]:
147
160
  pass
148
161
 
149
- def calculate_vector(self, data: List[pd.Series]) -> pd.Series:
162
+ def calculate_vector(self, data: List[OperandValue]) -> Union[pd.Series, OperandValue]:
150
163
  pass
151
164
 
152
165
  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)
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
@@ -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