docstr: Tests `AutoARIMA` mixer individually, as it has a special notion of
  timestamps that we need to ensure are being used correctly (along with all other `sktime` mixers). 
  In particular, given a train-dev-test split, forecasts coming from any sktime
  mixer will start from the inferred relative offset w.r.t. the latest observed timestamp in the training set.
  
  This test also compares:
   - correct propagation of offset by K if the special `__mdb_forecast_offset` column is present
   - results against manual use of AutoARIMA to ensure equal results.
docstr_tok: ["Tests","`","AutoARIMA","`","mixer","individually",",","as","it","has","a","special","notion","of","timestamps","that","we","need","to","ensure","are","being","used","correctly","(","along","with","all","other","`","sktime","`","mixers",")",".","In","particular",",","given","a","train-dev-test","split",",","forecasts","coming","from","any","sktime","mixer","will","start","from","the","inferred","relative","offset","w.r.t",".","the","latest","observed","timestamp","in","the","training","set",".","This","test","also","compares",":","-","correct","propagation","of","offset","by","K","if","the","special","`","__mdb_forecast_offset","`","column","is","present","-","results","against","manual","use","of","AutoARIMA","to","ensure","equal","results","."]
code: def test_5_time_series_arima_mixer(self):
      """
      Tests `AutoARIMA` mixer individually, as it has a special notion of
      timestamps that we need to ensure are being used correctly (along with all other `sktime` mixers).
      In particular, given a train-dev-test split, forecasts coming from any sktime
      mixer will start from the inferred relative offset w.r.t. the latest observed timestamp in the training set.
  
      This test also compares:
       - correct propagation of offset by K if the special `__mdb_forecast_offset` column is present
       - results against manual use of AutoARIMA to ensure equal results.
      """  # noqa
  
      # synth square wave
      tsteps = 100
      target = "Value"
      horizon = 20
      t = np.linspace(0, 100, tsteps, endpoint=False)
      ts = [
          i + f
          for i, f in enumerate(
              signal.sawtooth(2 * np.pi * 5 * t, width=0.5)
          )
      ]
      df = pd.DataFrame(columns=["Time", target])
      df["Time"] = t
      df[target] = ts
      df[f"{target}_2x"] = [2 * elt for elt in ts]
  
      train = df[: int(len(df) * 0.8)]
      test = df[int(len(df) * 0.8) :]
  
      pdef = ProblemDefinition.from_dict(
          {
              "target": target,
              "time_aim": 200,
              "timeseries_settings": {
                  "order_by": "Time",
                  "window": 5,
                  "horizon": horizon,
                  "historical_columns": [f"{target}_2x"],
              },
          }
      )
  
      json_ai = json_ai_from_problem(df, problem_definition=pdef)
      json_ai.model["args"]["submodels"] = [
          {
              "module": "ARIMAMixer",
              "args": {
                  "stop_after": "$problem_definition.seconds_per_mixer",
                  "horizon": "$problem_definition.timeseries_settings.horizon",
              },
          }
      ]
  
      code = code_from_json_ai(json_ai)
      predictor = predictor_from_code(code)
  
      # Test with a longer time aim
      train_and_check_time_aim(predictor, train)
      ps = predictor.predict(test)
      assert (
          r2_score(test[target].values, ps["prediction"].iloc[0])
          >= 0.95
      )
  
      # test offsets
      ps1 = predictor.predict(
          test[1:]
      )  # one step after latest (inferred)
      ps0 = predictor.predict(test)  # normal
      psm1 = predictor.predict(
          train.iloc[[-1]]
      )  # one step before latest
      times_1 = psm1["order_Time"].tolist()[0]
      values_1 = psm1["prediction"].tolist()[0]
      times0 = ps0["order_Time"].tolist()[0]
      values0 = ps0["prediction"].tolist()[0]
      times1 = ps1["order_Time"].tolist()[0]
      values1 = ps1["prediction"].tolist()[0]
  
      # due to the offset, these intermediate indexes should be equal
      self.assertTrue(times_1[1:] == times0[0:-1])
      self.assertTrue(times0[1:] == times1[0:-1])
      self.assertTrue(values_1[1:] == values0[0:-1])
      self.assertTrue(values0[1:] == values1[0:-1])
  
      # the rest should be different
      self.assertTrue(times_1 != times0)
      self.assertTrue(times0 != times1)
      self.assertTrue(values_1 != values0)
      self.assertTrue(values0 != values1)
  
      # test historical columns asserts
      test[f"{target}_2x"].iloc[0] = np.nan
      self.assertRaises(Exception, predictor.predict, test)
  
      test.pop(f"{target}_2x")
      self.assertRaises(Exception, predictor.predict, test)
  
      # compare vs sktime manual usage
      if isinstance(
          predictor.ensemble.mixers[predictor.ensemble.best_index],
          SkTime,
      ):
          forecaster = AutoARIMA()
          fh = ForecastingHorizon(
              [i for i in range(int(tsteps * 0.8))], is_relative=True
          )
          forecaster.fit(train[target], fh=fh)
          manual_preds = forecaster.predict(
              fh[1 : horizon + 1]
          ).tolist()
          lw_preds = [p[0] for p in ps["prediction"]]
  
          assert np.allclose(manual_preds, lw_preds, atol=1)
  
imports: import random
  from datetime import datetime
  from datetime import timedelta
  import unittest
  import numpy
  import pandas
  from typing import List
  from scipy import signal
  from sklearn.metrics import r2_score
  from lightwood.api.types import ProblemDefinition
  from tests.utils.timing import train_and_check_time_aim
  from sktime.forecasting.base import ForecastingHorizon
  from sktime.forecasting.statsforecast import StatsForecastAutoARIMA
  from dataprep_ml.splitters import stratify
  from lightwood.api.high_level import json_ai_from_problem, code_from_json_ai, predictor_from_code, predictor_from_problem, predictor_from_json_ai
  from lightwood.mixer.sktime import SkTime
label_desc: Use image node_id 8 for calling the TestTimeseries obj's underlying member method code with example usage: obj.test_5_time_series_arima_mixer() without return types