upgini 1.2.157.dev3__tar.gz → 1.2.158a4180.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.157.dev3 → upgini-1.2.158a4180.dev1}/PKG-INFO +2 -2
  2. upgini-1.2.158a4180.dev1/src/upgini/__about__.py +1 -0
  3. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/binary.py +2 -0
  4. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/date.py +204 -46
  5. upgini-1.2.157.dev3/src/upgini/__about__.py +0 -1
  6. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/.gitignore +0 -0
  7. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/LICENSE +0 -0
  8. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/README.md +0 -0
  9. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/pyproject.toml +0 -0
  10. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/__init__.py +0 -0
  11. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/ads.py +0 -0
  12. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/ads_management/__init__.py +0 -0
  13. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/ads_management/ads_manager.py +0 -0
  14. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/__init__.py +0 -0
  15. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/all_operators.py +0 -0
  16. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/feature.py +0 -0
  17. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/groupby.py +0 -0
  18. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/operand.py +0 -0
  19. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/operator.py +0 -0
  20. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/__init__.py +0 -0
  21. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/base.py +0 -0
  22. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/cross.py +0 -0
  23. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/delta.py +0 -0
  24. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/lag.py +0 -0
  25. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/roll.py +0 -0
  26. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/trend.py +0 -0
  27. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/timeseries/volatility.py +0 -0
  28. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/unary.py +0 -0
  29. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/utils.py +0 -0
  30. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/autofe/vector.py +0 -0
  31. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/data_source/__init__.py +0 -0
  32. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/data_source/data_source_publisher.py +0 -0
  33. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/dataset.py +0 -0
  34. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/errors.py +0 -0
  35. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/features_enricher.py +0 -0
  36. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/http.py +0 -0
  37. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/mdc/__init__.py +0 -0
  38. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/mdc/context.py +0 -0
  39. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/metadata.py +0 -0
  40. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/metrics.py +0 -0
  41. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/normalizer/__init__.py +0 -0
  42. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/normalizer/normalize_utils.py +0 -0
  43. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/resource_bundle/__init__.py +0 -0
  44. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/resource_bundle/exceptions.py +0 -0
  45. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/resource_bundle/strings.properties +0 -0
  46. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/resource_bundle/strings_widget.properties +0 -0
  47. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/sampler/__init__.py +0 -0
  48. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/sampler/base.py +0 -0
  49. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/sampler/random_under_sampler.py +0 -0
  50. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/sampler/utils.py +0 -0
  51. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/search_task.py +0 -0
  52. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/spinner.py +0 -0
  53. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/Roboto-Regular.ttf +0 -0
  54. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/__init__.py +0 -0
  55. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/base_search_key_detector.py +0 -0
  56. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/blocked_time_series.py +0 -0
  57. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/config.py +0 -0
  58. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/country_utils.py +0 -0
  59. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/custom_loss_utils.py +0 -0
  60. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/cv_utils.py +0 -0
  61. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/datetime_utils.py +0 -0
  62. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/deduplicate_utils.py +0 -0
  63. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/display_utils.py +0 -0
  64. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/email_utils.py +0 -0
  65. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/fallback_progress_bar.py +0 -0
  66. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/feature_info.py +0 -0
  67. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/features_validator.py +0 -0
  68. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/format.py +0 -0
  69. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/hash_utils.py +0 -0
  70. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/ip_utils.py +0 -0
  71. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/mstats.py +0 -0
  72. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/one_hot_encoder.py +0 -0
  73. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/phone_utils.py +0 -0
  74. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/postal_code_utils.py +0 -0
  75. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/progress_bar.py +0 -0
  76. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/psi.py +0 -0
  77. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/sample_utils.py +0 -0
  78. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/sklearn_ext.py +0 -0
  79. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/sort.py +0 -0
  80. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/target_utils.py +0 -0
  81. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/track_info.py +0 -0
  82. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/ts_utils.py +0 -0
  83. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/utils/warning_counter.py +0 -0
  84. {upgini-1.2.157.dev3 → upgini-1.2.158a4180.dev1}/src/upgini/version_validator.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: upgini
3
- Version: 1.2.157.dev3
3
+ Version: 1.2.158a4180.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.158.a4180.dev1"
@@ -165,6 +165,8 @@ class Distance(PandasOperator):
165
165
  def _calculate_binary_matrices(self, left: OperandValue, right: OperandValue) -> pd.Series:
166
166
  left_mat = left.as_matrix()
167
167
  right_mat = right.as_matrix()
168
+ if left_mat.shape[1] != right_mat.shape[1]:
169
+ return pd.Series(np.full(len(left_mat), np.nan), index=left.index, dtype=np.float64)
168
170
  dot = np.sum(left_mat * right_mat, axis=1)
169
171
  left_norm = np.sqrt(np.sum(left_mat * left_mat, axis=1))
170
172
  right_norm = np.sqrt(np.sum(right_mat * right_mat, axis=1))
@@ -1,13 +1,13 @@
1
1
  import abc
2
2
  import json
3
- from typing import Dict, List, Optional, Union
3
+ from typing import Dict, List, NamedTuple, Optional, Union
4
4
 
5
5
  import numpy as np
6
6
  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
+ from upgini.autofe.operand import OperandKind, OperandValue
11
11
  from upgini.autofe.operator import PandasOperator, ParametrizedOperator
12
12
  from upgini.autofe.utils import bin_index, bin_index_many, bin_index_vectorized, pydantic_validator
13
13
 
@@ -114,8 +114,57 @@ class DateDiffType2(PandasOperator, DateDiffMixin):
114
114
  return diff
115
115
 
116
116
 
117
- _ext_aggregations = {"nunique": (lambda x: len(np.unique(x)), 0), "count": (len, 0)}
117
+ _ext_aggregations = {"nunique": (lambda x: float(np.unique(x).size), 0), "count": (len, 0)}
118
118
  _count_aggregations = ["nunique", "count"]
119
+ _DATE_DIFF_LISTS_LENGTH_COL = 0
120
+ _NS_PER_DAY = np.float64(86400 * 10**9)
121
+ _NS_PER_YEAR = np.float64(365 * 86400 * 10**9)
122
+ _MATRIX_AGGREGATIONS = {
123
+ "nunique": "_matrix_agg_nunique",
124
+ "count": "_matrix_agg_count",
125
+ "sum": "_matrix_agg_sum",
126
+ "mean": "_matrix_agg_mean",
127
+ "min": "_matrix_agg_min",
128
+ "max": "_matrix_agg_max",
129
+ }
130
+
131
+
132
+ class _MatrixAggContext(NamedTuple):
133
+ lengths: np.ndarray
134
+ masked_values: np.ndarray
135
+ valid_mask: np.ndarray
136
+ missing: np.ndarray
137
+ empty: np.ndarray
138
+ agg_source: np.ndarray
139
+ count_source: np.ndarray
140
+ has_bounds: bool
141
+ results: np.ndarray
142
+
143
+
144
+ def _timedelta_ns_to_diff_unit(delta_ns: np.ndarray, diff_unit: str) -> np.ndarray:
145
+ if diff_unit == "D":
146
+ return delta_ns / _NS_PER_DAY
147
+ if diff_unit == "Y":
148
+ return (delta_ns / _NS_PER_YEAR).astype(np.int64).astype(np.float64)
149
+ raise ValueError(f"Unsupported difference unit: {diff_unit}")
150
+
151
+
152
+ def _group_cumcount(group_keys: np.ndarray) -> np.ndarray:
153
+ n = len(group_keys)
154
+ if n == 0:
155
+ return np.zeros(0, dtype=np.intp)
156
+ order = np.argsort(group_keys, kind="stable")
157
+ sorted_keys = group_keys[order]
158
+ group_change = np.empty(n, dtype=bool)
159
+ group_change[0] = True
160
+ if n > 1:
161
+ group_change[1:] = sorted_keys[1:] != sorted_keys[:-1]
162
+ group_ids = np.cumsum(group_change) - 1
163
+ group_start_idx = np.flatnonzero(group_change)
164
+ sorted_cumcount = np.arange(n, dtype=np.intp) - group_start_idx[group_ids]
165
+ cumcount = np.empty(n, dtype=np.intp)
166
+ cumcount[order] = sorted_cumcount
167
+ return cumcount
119
168
 
120
169
 
121
170
  def _aggregate_diffs(values: np.ndarray, aggregation: str) -> float:
@@ -163,54 +212,71 @@ class DateListDiffLists(PandasOperator, DateDiffMixin, ParametrizedOperator):
163
212
  return cls(diff_unit=diff_unit)
164
213
  return None
165
214
 
166
- @staticmethod
167
- def _non_empty_list_mask(right: pd.Series) -> pd.Series:
168
- values = right.to_numpy()
169
- mask = np.empty(len(values), dtype=bool)
170
- for i, value in enumerate(values):
171
- if value is None or (isinstance(value, float) and np.isnan(value)):
172
- mask[i] = False
173
- elif isinstance(value, (list, tuple, np.ndarray)):
174
- mask[i] = len(value) > 0
175
- else:
176
- mask[i] = False
177
- return pd.Series(mask, index=right.index)
178
-
179
- def _convert_date_lists(self, lists: pd.Series) -> pd.Series:
180
- exploded = lists.explode()
181
- converted = pd.to_datetime(exploded, unit=self.right_unit, errors="coerce")
182
- return pd.Series(
183
- {
184
- idx: pd.arrays.DatetimeArray(values.to_numpy())
185
- for idx, values in converted.groupby(converted.index, sort=False)
186
- }
187
- )
215
+ def _non_empty_list_mask(self, right: pd.Series) -> pd.Series:
216
+ return right.map(lambda value: isinstance(value, (list, tuple, np.ndarray)) and len(value) > 0).fillna(False)
217
+
218
+ def _build_matrix(self, left: pd.Series, right: pd.Series) -> np.ndarray:
219
+ n = len(left)
220
+ if n == 0:
221
+ return np.empty((0, 1), dtype=np.float64)
222
+
223
+ left_dates = pd.to_datetime(left, unit=self.left_unit, errors="coerce")
224
+ date_unit = self.right_unit
225
+ right_mask = self._non_empty_list_mask(right).to_numpy()
226
+ right_notna = right.notna().to_numpy()
227
+ left_notna = left_dates.notna().to_numpy()
228
+
229
+ compute_mask = left_notna & right_notna & right_mask
230
+ empty_right = right_notna & ~right_mask
231
+
232
+ lengths = np.full(n, np.nan, dtype=np.float64)
233
+ lengths[empty_right] = 0.0
234
+
235
+ compute_idx = np.flatnonzero(compute_mask)
236
+ if len(compute_idx) == 0:
237
+ return lengths.reshape(n, 1)
238
+
239
+ exploded = right.iloc[compute_idx].explode()
240
+ row_indices_arr = right.index.get_indexer(exploded.index).astype(np.intp)
241
+ raw_dates = exploded.to_numpy()
242
+ pos_in_row_arr = _group_cumcount(row_indices_arr)
243
+ converted = pd.to_datetime(pd.Series(raw_dates), unit=date_unit, errors="coerce")
244
+ left_ns = left_dates.iloc[row_indices_arr].astype(np.int64).to_numpy()
245
+ right_ns = converted.astype(np.int64).to_numpy()
246
+ diffs = np.full(len(row_indices_arr), np.nan, dtype=np.float64)
247
+ valid_ts = converted.notna().to_numpy()
248
+ if valid_ts.any():
249
+ diffs[valid_ts] = _timedelta_ns_to_diff_unit(left_ns[valid_ts] - right_ns[valid_ts], self.diff_unit)
188
250
 
189
- def _row_diffs(self, left_date, right_dates: pd.arrays.DatetimeArray) -> List[float]:
190
- diffs = self._convert_diff_to_unit(left_date - right_dates)
191
251
  if self.replace_negative:
192
- diffs = diffs[diffs > 0]
193
- return np.atleast_1d(np.asarray(diffs, dtype=np.float64)).tolist()
194
-
195
- def calculate_binary(self, left: OperandValue, right: OperandValue) -> pd.Series:
252
+ keep = diffs > 0
253
+ row_indices_arr = row_indices_arr[keep]
254
+ diffs = diffs[keep]
255
+ lengths[compute_mask] = 0.0
256
+ if len(row_indices_arr):
257
+ pos_in_row_arr = _group_cumcount(row_indices_arr)
258
+ row_lengths = np.bincount(row_indices_arr, minlength=n).astype(np.float64)
259
+ positive_rows = np.flatnonzero(row_lengths > 0)
260
+ lengths[positive_rows] = row_lengths[positive_rows]
261
+ else:
262
+ row_lengths = np.bincount(row_indices_arr, minlength=n).astype(np.float64)
263
+ lengths[compute_mask] = row_lengths[compute_mask]
264
+
265
+ finite_lengths = lengths[np.isfinite(lengths)]
266
+ k_max = int(finite_lengths.max()) if finite_lengths.size else 0
267
+ matrix = np.full((n, 1 + k_max), np.nan, dtype=np.float64)
268
+ matrix[:, _DATE_DIFF_LISTS_LENGTH_COL] = lengths
269
+ if k_max > 0 and len(row_indices_arr):
270
+ matrix[row_indices_arr, pos_in_row_arr + 1] = diffs
271
+ return matrix
272
+
273
+ def calculate_binary(self, left: OperandValue, right: OperandValue) -> np.ndarray:
196
274
  left = left.as_series()
197
275
  right = right.as_series()
198
276
  if left.isna().all() or right.isna().all():
199
- return pd.Series([None] * len(left), index=left.index, dtype=object)
200
-
201
- left = self._convert_to_date(left, self.left_unit)
202
- right_mask = self._non_empty_list_mask(right)
203
- mask = left.notna() & right.notna() & right_mask
277
+ return np.full((len(left), 1), np.nan, dtype=np.float64)
204
278
 
205
- results = pd.Series([None] * len(left), index=left.index, dtype=object)
206
- if not mask.any():
207
- return results
208
-
209
- masked_left = left[mask]
210
- converted_lists = self._convert_date_lists(right[mask])
211
- for idx, left_date in masked_left.items():
212
- results.loc[idx] = self._row_diffs(left_date, converted_lists[idx])
213
- return results
279
+ return self._build_matrix(left, right)
214
280
 
215
281
 
216
282
  class DateListDiffAggWithinBounds(PandasOperator, ParametrizedOperator):
@@ -263,6 +329,95 @@ class DateListDiffAggWithinBounds(PandasOperator, ParametrizedOperator):
263
329
  normalize=normalize,
264
330
  )
265
331
 
332
+ def _masked_values(self, matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
333
+ lengths = matrix[:, _DATE_DIFF_LISTS_LENGTH_COL]
334
+ values = matrix[:, _DATE_DIFF_LISTS_LENGTH_COL + 1 :]
335
+ missing = np.isnan(lengths)
336
+ empty = (~missing) & (lengths == 0)
337
+ if values.shape[1] == 0:
338
+ valid_mask = np.zeros((len(lengths), 0), dtype=bool)
339
+ else:
340
+ valid_mask = np.arange(values.shape[1])[None, :] < lengths[:, None]
341
+ masked_values = np.where(valid_mask, values, np.nan)
342
+ return lengths, masked_values, valid_mask, missing, empty
343
+
344
+ def _matrix_agg_nunique(self, ctx: _MatrixAggContext) -> None:
345
+ ctx.results[ctx.empty] = 0.0
346
+ active = ~ctx.missing & ~ctx.empty
347
+ if ctx.has_bounds:
348
+ select_mask = ctx.count_source & active[:, None]
349
+ else:
350
+ select_mask = ctx.valid_mask & active[:, None]
351
+ rows, _ = np.nonzero(select_mask)
352
+ ctx.results[active] = 0.0
353
+ if rows.size:
354
+ vals = ctx.masked_values[select_mask]
355
+ counts = pd.Series(vals).groupby(rows, sort=False).nunique(dropna=False)
356
+ ctx.results[counts.index.to_numpy(dtype=np.intp)] = counts.to_numpy(dtype=np.float64)
357
+
358
+ def _matrix_agg_count(self, ctx: _MatrixAggContext) -> None:
359
+ ctx.results[~ctx.missing] = ctx.count_source[~ctx.missing].sum(axis=1).astype(np.float64)
360
+
361
+ def _matrix_agg_sum(self, ctx: _MatrixAggContext) -> None:
362
+ agg_rows = ~ctx.missing & ~ctx.empty
363
+ if agg_rows.any():
364
+ with np.errstate(all="ignore"):
365
+ ctx.results[agg_rows] = np.nansum(ctx.agg_source[agg_rows], axis=1)
366
+
367
+ def _matrix_agg_nanaxis(self, ctx: _MatrixAggContext, reducer) -> None:
368
+ agg_rows = ~ctx.missing & ~ctx.empty
369
+ if not agg_rows.any():
370
+ return
371
+ has_finite = np.any(np.isfinite(ctx.agg_source[agg_rows]), axis=1)
372
+ finite_rows = np.flatnonzero(agg_rows)[has_finite]
373
+ with np.errstate(all="ignore"):
374
+ ctx.results[finite_rows] = reducer(ctx.agg_source[finite_rows], axis=1)
375
+
376
+ def _matrix_agg_mean(self, ctx: _MatrixAggContext) -> None:
377
+ self._matrix_agg_nanaxis(ctx, np.nanmean)
378
+
379
+ def _matrix_agg_min(self, ctx: _MatrixAggContext) -> None:
380
+ self._matrix_agg_nanaxis(ctx, np.nanmin)
381
+
382
+ def _matrix_agg_max(self, ctx: _MatrixAggContext) -> None:
383
+ self._matrix_agg_nanaxis(ctx, np.nanmax)
384
+
385
+ def _calculate_unary_matrix(self, matrix: np.ndarray, index: pd.Index) -> pd.Series:
386
+ lengths, masked_values, valid_mask, missing, empty = self._masked_values(matrix)
387
+ results = np.full(len(lengths), np.nan, dtype=np.float64)
388
+ has_bounds = self.lower_bound is not None or self.upper_bound is not None
389
+
390
+ if has_bounds:
391
+ lower = self.lower_bound if self.lower_bound is not None else -np.inf
392
+ upper = self.upper_bound if self.upper_bound is not None else np.inf
393
+ in_bounds = (masked_values >= lower) & (masked_values < upper)
394
+ agg_source = np.where(in_bounds & valid_mask, masked_values, np.nan)
395
+ count_source = in_bounds & valid_mask
396
+ else:
397
+ agg_source = masked_values
398
+ count_source = valid_mask
399
+
400
+ ctx = _MatrixAggContext(
401
+ lengths=lengths,
402
+ masked_values=masked_values,
403
+ valid_mask=valid_mask,
404
+ missing=missing,
405
+ empty=empty,
406
+ agg_source=agg_source,
407
+ count_source=count_source,
408
+ has_bounds=has_bounds,
409
+ results=results,
410
+ )
411
+ method_name = _MATRIX_AGGREGATIONS.get(self.aggregation)
412
+ if method_name is None:
413
+ raise ValueError(f"Unsupported aggregation: {self.aggregation}")
414
+ getattr(self, method_name)(ctx)
415
+
416
+ if self.normalize:
417
+ normalize_mask = ~missing & ~empty & (lengths > 0)
418
+ results[normalize_mask] = results[normalize_mask] / lengths[normalize_mask]
419
+ return pd.Series(results, index=index, dtype=np.float64)
420
+
266
421
  def _aggregate_row(self, diffs) -> float:
267
422
  if diffs is None or (isinstance(diffs, float) and np.isnan(diffs)):
268
423
  return np.nan
@@ -279,6 +434,9 @@ class DateListDiffAggWithinBounds(PandasOperator, ParametrizedOperator):
279
434
  return agg_res
280
435
 
281
436
  def calculate_unary(self, data: OperandValue) -> pd.Series:
437
+ if data.kind == OperandKind.MATRIX:
438
+ return self._calculate_unary_matrix(data.as_matrix(), data.index)
439
+
282
440
  data = data.as_series()
283
441
  results = np.empty(len(data), dtype=np.float64)
284
442
  results[:] = np.nan
@@ -339,7 +497,7 @@ class DateListDiff(PandasOperator, DateDiffMixin, ParametrizedOperator):
339
497
  if left.isna().all() or right.isna().all():
340
498
  return pd.Series([None] * len(left), index=left.index, dtype=np.float64)
341
499
 
342
- right_mask = DateListDiffLists._non_empty_list_mask(right)
500
+ right_mask = self._lists_op()._non_empty_list_mask(right)
343
501
  diff_lists = self._lists_op().calculate(left=left, right=right)
344
502
  result = self._agg_op().calculate(data=diff_lists).as_series()
345
503
  if self.aggregation in _count_aggregations:
@@ -1 +0,0 @@
1
- __version__ = "1.2.157.dev3"