docstr: Internal method that fits forecasters to a given dataframe.
docstr_tok: ["Internal","method","that","fits","forecasters","to","a","given","dataframe","."]
code: def _fit(self, data):
      """
      Internal method that fits forecasters to a given dataframe.
      """
      df = data.data_frame.sort_values(
          by=f'__mdb_original_{self.ts_analysis["tss"].order_by}'
      )
      gby = self.ts_analysis["tss"].group_by
  
      if not self.hyperparam_search and not self.study:
          module_name = self.model_path
      else:
          finished_study = (
              sum(
                  [
                      int(trial.state.is_finished())
                      for trial in self.study.trials
                  ]
              )
              == self.n_trials
          )
          if finished_study:
              log.info(
                  f'Selected best model: {self.study.best_params["class"]}'
              )
              module_name = self.study.best_params["class"]
          else:
              module_name = self.hyperparam_dict[
                  "class"
              ]  # select active optuna choice
  
      sktime_module = importlib.import_module(
          ".".join(["sktime", "forecasting", module_name.split(".")[0]])
      )
      try:
          model_class = getattr(
              sktime_module, module_name.split(".")[1]
          )
      except AttributeError:
          model_class = AutoARIMA  # use AutoARIMA when the provided class does not exist
  
      grouped = (
          df.groupby(by=gby)
          if gby
          else df.groupby(lambda x: "__default")
      )
      for group, series_data in grouped:
          kwargs = {}
          sp = (
              self.sp
              if self.sp
              else self.ts_analysis["periods"].get(group, [1])[0]
          )
  
          options = self.model_kwargs
          options["sp"] = sp  # seasonality period
          options[
              "suppress_warnings"
          ] = True  # ignore warnings if possible
          options[
              "error_action"
          ] = "raise"  # avoids fit() failing silently
  
          if self.model_path == "fbprophet.Prophet":
              options["freq"] = self.freq
  
          for k, v in options.items():
              kwargs = _add_cls_kwarg(model_class, kwargs, k, v)
  
          model_pipeline = []
  
          if self.use_stl and self.ts_analysis["stl_transforms"].get(
              group, False
          ):
              model_pipeline.insert(
                  0,
                  (
                      "detrender",
                      self.ts_analysis["stl_transforms"][group][
                          "transformer"
                      ].detrender,
                  ),
              )
              model_pipeline.insert(
                  0,
                  (
                      "deseasonalizer",
                      self.ts_analysis["stl_transforms"][group][
                          "transformer"
                      ].deseasonalizer,
                  ),
              )
              kwargs["sp"] = None
  
          model_pipeline.append(("forecaster", model_class(**kwargs)))
  
          self.models[group] = TransformedTargetForecaster(
              model_pipeline
          )
  
          oby_col = self.ts_analysis["tss"].order_by
          if self.grouped_by == ["__default"]:
              series_oby = df[oby_col]
          else:
              series_oby = series_data[oby_col]
  
          self.cutoffs[group] = series_oby.index[
              -1
          ]  # defines the 'present' time for each partition
          series = series_data[self.target]
          if series_data.size > self.ts_analysis["tss"].window:
              series = series.sort_index(ascending=True)
              series = series.reset_index(drop=True)
              series = series.loc[
                  ~pd.isnull(series.values)
              ]  # remove NaN  # @TODO: benchmark imputation vs this?
  
              if self.model_path == "fbprophet.Prophet":
                  try:
                      series = self._transform_index_to_datetime(
                          series, series_oby, options["freq"]
                      )
                  except Exception:
                      if group == "__default":
                          # out of bounds with true freq in __default group is fine, we skip it
                          continue
  
              series = series.astype(float)
  
              # if data is huge, filter out old records for quicker fitting
              if self.auto_size:
                  cutoff = min(
                      len(series),
                      max(
                          500, options.get("sp", 1) * self.cutoff_factor
                      ),
                  )
                  series = series.iloc[-cutoff:]
              try:
                  self.models[group].fit(series, fh=self.fh)
              except Exception:
                  self.models[
                      group
                  ] = (
                      model_class()
                  )  # with default options (i.e. no seasonality, among others)
                  self.models[group].fit(series, fh=self.fh)
  
imports: import importlib
  from copy import deepcopy
  from datetime import datetime
  from typing import Dict, Union, Optional
  import optuna
  import numpy
  import pandas
  from sktime.forecasting.compose import TransformedTargetForecaster
  from sktime.forecasting.base import ForecastingHorizon, BaseForecaster
  from sktime.performance_metrics.forecasting import MeanAbsolutePercentageError
  from sktime.forecasting.statsforecast import StatsForecastAutoARIMA
  from lightwood.helpers.log import log
  from lightwood.helpers.templating import _add_cls_kwarg
  from lightwood.mixer.base import BaseMixer
  from lightwood.api.types import PredictionArguments
  from lightwood.data.encoded_ds import EncodedDs, ConcatedEncodedDs
label_desc: Use image node_id 3 for calling the SkTime obj's underlying member method code with example usage: obj._fit(data) without return types