oracle-ads 2.13.2__py3-none-any.whl → 2.13.2rc1__py3-none-any.whl

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.
@@ -23,14 +23,14 @@ from ..operator_config import ForecastOperatorConfig
23
23
 
24
24
 
25
25
  class HistoricalData(AbstractData):
26
- def __init__(self, spec, historical_data = None):
26
+ def __init__(self, spec, historical_data=None):
27
27
  super().__init__(spec=spec, name="historical_data", data=historical_data)
28
28
 
29
29
  def _ingest_data(self, spec):
30
30
  try:
31
31
  self.freq = get_frequency_of_datetime(self.data.index.get_level_values(0))
32
32
  except TypeError as e:
33
- logger.warn(
33
+ logger.warning(
34
34
  f"Error determining frequency: {e.args}. Setting Frequency to None"
35
35
  )
36
36
  logger.debug(f"Full traceback: {e}")
@@ -106,7 +106,7 @@ class AdditionalData(AbstractData):
106
106
  _spec = spec
107
107
  self.additional_regressors = list(self.data.columns)
108
108
  if not self.additional_regressors:
109
- logger.warn(
109
+ logger.warning(
110
110
  f"No additional variables found in the additional_data. Only columns found: {self.data.columns}. Skipping for now."
111
111
  )
112
112
  # Check that datetime column matches historical datetime column
@@ -121,7 +121,13 @@ class TestData(AbstractData):
121
121
 
122
122
 
123
123
  class ForecastDatasets:
124
- def __init__(self, config: ForecastOperatorConfig, historical_data=None, additional_data=None, test_data=None):
124
+ def __init__(
125
+ self,
126
+ config: ForecastOperatorConfig,
127
+ historical_data=None,
128
+ additional_data=None,
129
+ test_data=None,
130
+ ):
125
131
  """Instantiates the DataIO instance.
126
132
 
127
133
  Properties
@@ -136,7 +142,9 @@ class ForecastDatasets:
136
142
  self._target_col = config.spec.target_column
137
143
  if historical_data is not None:
138
144
  self.historical_data = HistoricalData(config.spec, historical_data)
139
- self.additional_data = AdditionalData(config.spec, self.historical_data, additional_data)
145
+ self.additional_data = AdditionalData(
146
+ config.spec, self.historical_data, additional_data
147
+ )
140
148
  else:
141
149
  self._load_data(config.spec)
142
150
  self.test_data = TestData(config.spec, test_data)
@@ -147,7 +155,7 @@ class ForecastDatasets:
147
155
  self.additional_data = AdditionalData(spec, self.historical_data)
148
156
 
149
157
  if spec.generate_explanations and spec.additional_data is None:
150
- logger.warn(
158
+ logger.warning(
151
159
  "Unable to generate explanations as there is no additional data passed in. Either set generate_explanations to False, or pass in additional data."
152
160
  )
153
161
  spec.generate_explanations = False
@@ -168,8 +168,8 @@ class MLForecastOperatorModel(ForecastOperatorBaseModel):
168
168
  "error": str(e),
169
169
  "error_trace": traceback.format_exc(),
170
170
  }
171
- logger.warn(f"Encountered Error: {e}. Skipping.")
172
- logger.warn(traceback.format_exc())
171
+ logger.warning(f"Encountered Error: {e}. Skipping.")
172
+ logger.warning(traceback.format_exc())
173
173
  raise e
174
174
 
175
175
  def _build_model(self) -> pd.DataFrame:
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
 
3
- # Copyright (c) 2023, 2024 Oracle and/or its affiliates.
3
+ # Copyright (c) 2023, 2025 Oracle and/or its affiliates.
4
4
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
5
 
6
6
  import logging
@@ -40,7 +40,7 @@ from .forecast_datasets import ForecastDatasets, ForecastOutput
40
40
  # "rmse": MeanSquaredError,
41
41
  # }
42
42
  # if selected_metric not in metric_translation.keys():
43
- # logger.warn(
43
+ # logger.warning(
44
44
  # f"Could not find the metric: {selected_metric} in torchmetrics. Defaulting to MAE and RMSE"
45
45
  # )
46
46
  # return {"MAE": MeanAbsoluteError(), "RMSE": MeanSquaredError()}
@@ -149,28 +149,42 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
149
149
  logger.debug(f"-----------------Model {i}----------------------")
150
150
  logger.debug(forecast.tail())
151
151
 
152
- # TODO; could also extract trend and seasonality?
153
- cols_to_read = filter(
154
- lambda x: x.startswith("future_regressor"), forecast.columns
155
- )
156
- self.explanations_info[s_id] = forecast[cols_to_read]
157
- self.explanations_info[s_id]["Date"] = forecast["ds"]
158
- self.explanations_info[s_id] = self.explanations_info[s_id].set_index(
159
- "Date"
160
- )
161
-
162
152
  self.outputs[s_id] = forecast
153
+ upper_bound_col_name = f"yhat1 {model_kwargs['quantiles'][1]*100}%"
154
+ lower_bound_col_name = f"yhat1 {model_kwargs['quantiles'][0]*100}%"
163
155
  self.forecast_output.populate_series_output(
164
156
  series_id=s_id,
165
157
  fit_val=self.drop_horizon(forecast["yhat1"]).values,
166
158
  forecast_val=self.get_horizon(forecast["yhat1"]).values,
167
- upper_bound=self.get_horizon(
168
- forecast[f"yhat1 {model_kwargs['quantiles'][1]*100}%"]
169
- ).values,
170
- lower_bound=self.get_horizon(
171
- forecast[f"yhat1 {model_kwargs['quantiles'][0]*100}%"]
172
- ).values,
159
+ upper_bound=self.get_horizon(forecast[upper_bound_col_name]).values,
160
+ lower_bound=self.get_horizon(forecast[lower_bound_col_name]).values,
161
+ )
162
+ core_columns = set(forecast.columns) - set(
163
+ [
164
+ "y",
165
+ "yhat1",
166
+ upper_bound_col_name,
167
+ lower_bound_col_name,
168
+ "future_regressors_additive",
169
+ "future_regressors_multiplicative",
170
+ ]
171
+ )
172
+ exog_variables = set(
173
+ filter(lambda x: x.startswith("future_regressor_"), list(core_columns))
173
174
  )
175
+ combine_terms = list(core_columns - exog_variables - set(["ds"]))
176
+ temp_df = (
177
+ forecast[list(core_columns)]
178
+ .rename({"ds": "Date"}, axis=1)
179
+ .set_index("Date")
180
+ )
181
+ if combine_terms:
182
+ temp_df[self.spec.target_column] = temp_df[combine_terms].sum(axis=1)
183
+ temp_df = temp_df.drop(combine_terms, axis=1)
184
+ else:
185
+ temp_df[self.spec.target_column] = 0
186
+ # Todo: check for columns that were dropped, and set them to 0
187
+ self.explanations_info[s_id] = temp_df
174
188
 
175
189
  self.trainers[s_id] = model.trainer
176
190
  self.models[s_id] = {}
@@ -207,7 +221,7 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
207
221
  "error": str(e),
208
222
  "error_trace": traceback.format_exc(),
209
223
  }
210
- logger.warn(traceback.format_exc())
224
+ logger.warning(traceback.format_exc())
211
225
  raise e
212
226
 
213
227
  def _build_model(self) -> pd.DataFrame:
@@ -215,7 +229,6 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
215
229
  self.models = {}
216
230
  self.trainers = {}
217
231
  self.outputs = {}
218
- self.errors_dict = {}
219
232
  self.explanations_info = {}
220
233
  self.accepted_regressors = {}
221
234
  self.additional_regressors = self.datasets.get_additional_data_column_names()
@@ -363,7 +376,9 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
363
376
  pd.Series(
364
377
  m.state_dict(),
365
378
  index=m.state_dict().keys(),
366
- name=s_id if self.target_cat_col else self.original_target_column,
379
+ name=s_id
380
+ if self.target_cat_col
381
+ else self.original_target_column,
367
382
  )
368
383
  )
369
384
  all_model_states = pd.concat(model_states, axis=1)
@@ -377,11 +392,15 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
377
392
  self.explain_model()
378
393
 
379
394
  if not self.target_cat_col:
380
- self.formatted_global_explanation = self.formatted_global_explanation.rename(
381
- {"Series 1": self.original_target_column},
382
- axis=1,
395
+ self.formatted_global_explanation = (
396
+ self.formatted_global_explanation.rename(
397
+ {"Series 1": self.original_target_column},
398
+ axis=1,
399
+ )
400
+ )
401
+ self.formatted_local_explanation.drop(
402
+ "Series", axis=1, inplace=True
383
403
  )
384
- self.formatted_local_explanation.drop("Series", axis=1, inplace=True)
385
404
 
386
405
  # Create a markdown section for the global explainability
387
406
  global_explanation_section = rc.Block(
@@ -412,7 +431,7 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
412
431
  ]
413
432
  except Exception as e:
414
433
  # Do not fail the whole run due to explanations failure
415
- logger.warn(f"Failed to generate Explanations with error: {e}.")
434
+ logger.warning(f"Failed to generate Explanations with error: {e}.")
416
435
  logger.debug(f"Full Traceback: {traceback.format_exc()}")
417
436
 
418
437
  model_description = rc.Text(
@@ -453,9 +472,7 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
453
472
  for s_id, expl_df in self.explanations_info.items():
454
473
  expl_df = expl_df.rename(rename_cols, axis=1)
455
474
  # Local Expl
456
- self.local_explanation[s_id] = self.get_horizon(expl_df).drop(
457
- ["future_regressors_additive"], axis=1
458
- )
475
+ self.local_explanation[s_id] = self.get_horizon(expl_df)
459
476
  self.local_explanation[s_id]["Series"] = s_id
460
477
  self.local_explanation[s_id].index.rename(self.dt_column_name, inplace=True)
461
478
  # Global Expl
@@ -463,9 +480,6 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
463
480
  g_expl.name = s_id
464
481
  global_expl.append(g_expl)
465
482
  self.global_explanation = pd.concat(global_expl, axis=1)
466
- self.global_explanation = self.global_explanation.drop(
467
- index=["future_regressors_additive"], axis=0
468
- )
469
483
  self.formatted_global_explanation = (
470
484
  self.global_explanation / self.global_explanation.sum(axis=0) * 100
471
485
  )
@@ -22,7 +22,6 @@ from ads.opctl.operator.lowcode.forecast.utils import (
22
22
  from ..const import (
23
23
  DEFAULT_TRIALS,
24
24
  PROPHET_INTERNAL_DATE_COL,
25
- ForecastOutputColumns,
26
25
  SupportedModels,
27
26
  )
28
27
  from .base_model import ForecastOperatorBaseModel
@@ -44,12 +43,23 @@ def _fit_model(data, params, additional_regressors):
44
43
  from prophet import Prophet
45
44
 
46
45
  monthly_seasonality = params.pop("monthly_seasonality", False)
46
+ data_floor = params.pop("min", None)
47
+ data_cap = params.pop("max", None)
48
+ if data_cap or data_floor:
49
+ params["growth"] = "logistic"
47
50
  model = Prophet(**params)
48
51
  if monthly_seasonality:
49
52
  model.add_seasonality(name="monthly", period=30.5, fourier_order=5)
50
53
  params["monthly_seasonality"] = monthly_seasonality
51
54
  for add_reg in additional_regressors:
52
55
  model.add_regressor(add_reg)
56
+ if data_floor:
57
+ data["floor"] = float(data_floor)
58
+ params["floor"] = data_floor
59
+ if data_cap:
60
+ data["cap"] = float(data_cap)
61
+ params["cap"] = data_cap
62
+
53
63
  model.fit(data)
54
64
  return model
55
65
 
@@ -112,6 +122,41 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
112
122
  upper_bound=self.get_horizon(forecast["yhat_upper"]).values,
113
123
  lower_bound=self.get_horizon(forecast["yhat_lower"]).values,
114
124
  )
125
+ # Get all features that make up the forecast. Exclude CI (upper/lower)
126
+ core_columns = forecast.columns[
127
+ ~forecast.columns.str.endswith("_lower")
128
+ & ~forecast.columns.str.endswith("_upper")
129
+ ]
130
+ core_columns = set(core_columns) - {
131
+ "additive_terms",
132
+ "extra_regressors_additive",
133
+ "multiplicative_terms",
134
+ "extra_regressors_multiplicative",
135
+ "cap",
136
+ "floor",
137
+ "yhat",
138
+ }
139
+ combine_terms = list(
140
+ core_columns.intersection(
141
+ {
142
+ "trend",
143
+ "daily",
144
+ "weekly",
145
+ "yearly",
146
+ "monthly",
147
+ "holidays",
148
+ "zeros",
149
+ }
150
+ )
151
+ )
152
+
153
+ temp_df = (
154
+ forecast[list(core_columns)]
155
+ .rename({"ds": "Date"}, axis=1)
156
+ .set_index("Date")
157
+ )
158
+ temp_df[self.spec.target_column] = temp_df[combine_terms].sum(axis=1)
159
+ self.explanations_info[series_id] = temp_df.drop(combine_terms, axis=1)
115
160
 
116
161
  self.models[series_id] = {}
117
162
  self.models[series_id]["model"] = model
@@ -133,13 +178,14 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
133
178
  "error": str(e),
134
179
  "error_trace": traceback.format_exc(),
135
180
  }
136
- logger.warn(f"Encountered Error: {e}. Skipping.")
137
- logger.warn(traceback.format_exc())
181
+ logger.warning(f"Encountered Error: {e}. Skipping.")
182
+ logger.warning(traceback.format_exc())
138
183
 
139
184
  def _build_model(self) -> pd.DataFrame:
140
185
  full_data_dict = self.datasets.get_data_by_series()
141
186
  self.models = {}
142
187
  self.outputs = {}
188
+ self.explanations_info = {}
143
189
  self.additional_regressors = self.datasets.get_additional_data_column_names()
144
190
  model_kwargs = self.set_kwargs()
145
191
  self.forecast_output = ForecastOutput(
@@ -149,9 +195,6 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
149
195
  dt_column=self.spec.datetime_column.name,
150
196
  )
151
197
 
152
- # if os.environ["OCI__IS_SPARK"]:
153
- # pass
154
- # else:
155
198
  Parallel(n_jobs=-1, require="sharedmem")(
156
199
  delayed(ProphetOperatorModel._train_model)(
157
200
  self, i, series_id, df, model_kwargs.copy()
@@ -222,7 +265,7 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
222
265
  try:
223
266
  return np.mean(df_p[self.spec.metric])
224
267
  except KeyError:
225
- logger.warn(
268
+ logger.warning(
226
269
  f"Could not find the metric {self.spec.metric} within "
227
270
  f"the performance metrics: {df_p.columns}. Defaulting to `rmse`"
228
271
  )
@@ -249,6 +292,25 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
249
292
  model_kwargs_i = study.best_params
250
293
  return model_kwargs_i
251
294
 
295
+ def explain_model(self):
296
+ self.local_explanation = {}
297
+ global_expl = []
298
+
299
+ for s_id, expl_df in self.explanations_info.items():
300
+ # Local Expl
301
+ self.local_explanation[s_id] = self.get_horizon(expl_df)
302
+ self.local_explanation[s_id]["Series"] = s_id
303
+ self.local_explanation[s_id].index.rename(self.dt_column_name, inplace=True)
304
+ # Global Expl
305
+ g_expl = self.drop_horizon(expl_df).mean()
306
+ g_expl.name = s_id
307
+ global_expl.append(g_expl)
308
+ self.global_explanation = pd.concat(global_expl, axis=1)
309
+ self.formatted_global_explanation = (
310
+ self.global_explanation / self.global_explanation.sum(axis=0) * 100
311
+ )
312
+ self.formatted_local_explanation = pd.concat(self.local_explanation.values())
313
+
252
314
  def _generate_report(self):
253
315
  import report_creator as rc
254
316
  from prophet.plot import add_changepoints_to_plot
@@ -274,7 +336,9 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
274
336
  )
275
337
 
276
338
  sec2 = _select_plot_list(
277
- lambda s_id: self.models[s_id]["model"].plot_components(self.outputs[s_id]),
339
+ lambda s_id: self.models[s_id]["model"].plot_components(
340
+ self.outputs[s_id]
341
+ ),
278
342
  series_ids=series_ids,
279
343
  target_category_column=self.target_cat_col,
280
344
  )
@@ -283,11 +347,14 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
283
347
  )
284
348
 
285
349
  sec3_figs = {
286
- s_id: self.models[s_id]["model"].plot(self.outputs[s_id]) for s_id in series_ids
350
+ s_id: self.models[s_id]["model"].plot(self.outputs[s_id])
351
+ for s_id in series_ids
287
352
  }
288
353
  for s_id in series_ids:
289
354
  add_changepoints_to_plot(
290
- sec3_figs[s_id].gca(), self.models[s_id]["model"], self.outputs[s_id]
355
+ sec3_figs[s_id].gca(),
356
+ self.models[s_id]["model"],
357
+ self.outputs[s_id],
291
358
  )
292
359
  sec3 = _select_plot_list(
293
360
  lambda s_id: sec3_figs[s_id],
@@ -322,22 +389,6 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
322
389
  # If the key is present, call the "explain_model" method
323
390
  self.explain_model()
324
391
 
325
- # Convert the global explanation data to a DataFrame
326
- global_explanation_df = pd.DataFrame(self.global_explanation)
327
-
328
- self.formatted_global_explanation = (
329
- global_explanation_df / global_explanation_df.sum(axis=0) * 100
330
- )
331
-
332
- aggregate_local_explanations = pd.DataFrame()
333
- for s_id, local_ex_df in self.local_explanation.items():
334
- local_ex_df_copy = local_ex_df.copy()
335
- local_ex_df_copy[ForecastOutputColumns.SERIES] = s_id
336
- aggregate_local_explanations = pd.concat(
337
- [aggregate_local_explanations, local_ex_df_copy], axis=0
338
- )
339
- self.formatted_local_explanation = aggregate_local_explanations
340
-
341
392
  if not self.target_cat_col:
342
393
  self.formatted_global_explanation = (
343
394
  self.formatted_global_explanation.rename(
@@ -351,7 +402,7 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
351
402
 
352
403
  # Create a markdown section for the global explainability
353
404
  global_explanation_section = rc.Block(
354
- rc.Heading("Global Explanation of Models", level=2),
405
+ rc.Heading("Global Explainability", level=2),
355
406
  rc.Text(
356
407
  "The following tables provide the feature attribution for the global explainability."
357
408
  ),
@@ -360,7 +411,7 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
360
411
 
361
412
  blocks = [
362
413
  rc.DataTable(
363
- local_ex_df.div(local_ex_df.abs().sum(axis=1), axis=0) * 100,
414
+ local_ex_df.drop("Series", axis=1),
364
415
  label=s_id if self.target_cat_col else None,
365
416
  index=True,
366
417
  )
@@ -378,8 +429,10 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
378
429
  ]
379
430
  except Exception as e:
380
431
  # Do not fail the whole run due to explanations failure
381
- logger.warn(f"Failed to generate Explanations with error: {e}.")
432
+ logger.warning(f"Failed to generate Explanations with error: {e}.")
382
433
  logger.debug(f"Full Traceback: {traceback.format_exc()}")
434
+ self.errors_dict["explainer_error"] = str(e)
435
+ self.errors_dict["explainer_error_error"] = traceback.format_exc()
383
436
 
384
437
  model_description = rc.Text(
385
438
  """Prophet is a procedure for forecasting time series data based on an additive model where non-linear trends are fit with yearly, weekly, and daily seasonality, plus holiday effects. It works best with time series that have strong seasonal effects and several seasons of historical data. Prophet is robust to missing data and shifts in the trend, and typically handles outliers well."""