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.
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/PKG-INFO +1 -1
- upgini-1.2.157.dev2/src/upgini/__about__.py +1 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/binary.py +63 -124
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/date.py +20 -8
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/feature.py +35 -19
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/groupby.py +13 -4
- upgini-1.2.157.dev2/src/upgini/autofe/operand.py +112 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/operator.py +26 -10
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/base.py +3 -1
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/cross.py +5 -3
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/unary.py +22 -11
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/vector.py +10 -6
- upgini-1.2.156.dev5/src/upgini/__about__.py +0 -1
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/.gitignore +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/LICENSE +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/README.md +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/pyproject.toml +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/ads.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/ads_management/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/ads_management/ads_manager.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/all_operators.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/delta.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/lag.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/roll.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/trend.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/timeseries/volatility.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/autofe/utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/data_source/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/data_source/data_source_publisher.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/dataset.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/errors.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/features_enricher.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/http.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/mdc/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/mdc/context.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/metadata.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/metrics.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/normalizer/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/normalizer/normalize_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/exceptions.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/strings.properties +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/strings_widget.properties +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/base.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/random_under_sampler.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/sampler/utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/search_task.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/spinner.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/Roboto-Regular.ttf +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/__init__.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/base_search_key_detector.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/blocked_time_series.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/config.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/country_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/custom_loss_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/cv_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/datetime_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/deduplicate_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/display_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/email_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/fallback_progress_bar.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/feature_info.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/features_validator.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/format.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/hash_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/ip_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/mstats.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/one_hot_encoder.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/phone_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/postal_code_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/progress_bar.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/psi.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/sample_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/sklearn_ext.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/sort.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/target_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/track_info.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/ts_utils.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/utils/warning_counter.py +0 -0
- {upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/version_validator.py +0 -0
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
144
|
-
if
|
|
145
|
-
return
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
def
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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().
|
|
336
|
-
result = self._agg_op().
|
|
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:
|
|
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:
|
|
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[
|
|
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
|
-
|
|
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(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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[
|
|
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[
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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] = "
|
|
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:
|
|
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[
|
|
15
|
-
|
|
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[
|
|
24
|
-
|
|
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[
|
|
33
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{upgini-1.2.156.dev5 → upgini-1.2.157.dev2}/src/upgini/resource_bundle/strings_widget.properties
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|