class_docstr: PyTorch Dataset for fitting timeseries models.
  
  The dataset automates common tasks such as
  
  * scaling and encoding of variables
  * normalizing the target variable
  * efficiently converting timeseries in pandas dataframes to torch tensors
  * holding information about static and time-varying variables known and unknown in the future
  * holiding information about related categories (such as holidays)
  * downsampling for data augmentation
  * generating inference, validation and test datasets
  * etc.
class_docstr_tok: ["PyTorch","Dataset","for","fitting","timeseries","models",".","The","dataset","automates","common","tasks","such","as","*","scaling","and","encoding","of","variables","*","normalizing","the","target","variable","*","efficiently","converting","timeseries","in","pandas","dataframes","to","torch","tensors","*","holding","information","about","static","and","time-varying","variables","known","and","unknown","in","the","future","*","holiding","information","about","related","categories","(","such","as","holidays",")","*","downsampling","for","data","augmentation","*","generating","inference",",","validation","and","test","datasets","*","etc","."]
docstr: Create index of samples.
  
  Args:
      data (pd.DataFrame): preprocessed data
      predict_mode (bool): if to create one same per group with prediction length equals ``max_decoder_length``
  
  Returns:
      pd.DataFrame: index dataframe for timesteps and index dataframe for groups.
          It contains a list of all possible subsequences.
code: def _construct_index(
      self, data: pd.DataFrame, predict_mode: bool
  ) -> pd.DataFrame:
      """
      Create index of samples.
  
      Args:
          data (pd.DataFrame): preprocessed data
          predict_mode (bool): if to create one same per group with prediction length equals ``max_decoder_length``
  
      Returns:
          pd.DataFrame: index dataframe for timesteps and index dataframe for groups.
              It contains a list of all possible subsequences.
      """
      g = data.groupby(self._group_ids, observed=True)
  
      df_index_first = (
          g["__time_idx__"].transform("first").to_frame("time_first")
      )
      df_index_last = (
          g["__time_idx__"].transform("last").to_frame("time_last")
      )
      df_index_diff_to_next = (
          -g["__time_idx__"]
          .diff(-1)
          .fillna(-1)
          .astype(int)
          .to_frame("time_diff_to_next")
      )
      df_index = pd.concat(
          [df_index_first, df_index_last, df_index_diff_to_next], axis=1
      )
      df_index["index_start"] = np.arange(len(df_index))
      df_index["time"] = data["__time_idx__"]
      df_index["count"] = (
          df_index["time_last"] - df_index["time_first"]
      ).astype(int) + 1
      sequence_ids = g.ngroup()
      df_index["sequence_id"] = sequence_ids
  
      min_sequence_length = (
          self.min_prediction_length + self.min_encoder_length
      )
      max_sequence_length = (
          self.max_prediction_length + self.max_encoder_length
      )
  
      # calculate maximum index to include from current index_start
      max_time = (df_index["time"] + max_sequence_length - 1).clip(
          upper=df_index["count"] + df_index.time_first - 1
      )
  
      # if there are missing timesteps, we cannot say directly what is the last timestep to include
      # therefore we iterate until it is found
      if (df_index["time_diff_to_next"] != 1).any():
          assert (
              self.allow_missing_timesteps
          ), "Time difference between steps has been idenfied as larger than 1 - set allow_missing_timesteps=True"
  
      df_index["index_end"], missing_sequences = _find_end_indices(
          diffs=df_index.time_diff_to_next.to_numpy(),
          max_lengths=(max_time - df_index.time).to_numpy() + 1,
          min_length=min_sequence_length,
      )
      # add duplicates but mostly with shorter sequence length for start of timeseries
      # while the previous steps have ensured that we start a sequence on every time step, the missing_sequences
      # ensure that there is a sequence that finishes on every timestep
      if len(missing_sequences) > 0:
          shortened_sequences = df_index.iloc[
              missing_sequences[:, 0]
          ].assign(index_end=missing_sequences[:, 1])
  
          # concatenate shortened sequences
          df_index = pd.concat(
              [df_index, shortened_sequences], axis=0, ignore_index=True
          )
  
      # filter out where encode and decode length are not satisfied
      df_index["sequence_length"] = (
          df_index["time"].iloc[df_index["index_end"]].to_numpy()
          - df_index["time"]
          + 1
      )
  
      # filter too short sequences
      df_index = df_index[
          # sequence must be at least of minimal prediction length
          lambda x: (x.sequence_length >= min_sequence_length)
          &
          # prediction must be for after minimal prediction index + length of prediction
          (
              x["sequence_length"] + x["time"]
              >= self.min_prediction_idx + self.min_prediction_length
          )
      ]
  
      if (
          predict_mode
      ):  # keep longest element per series (i.e. the first element that spans to the end of the series)
          # filter all elements that are longer than the allowed maximum sequence length
          df_index = df_index[
              lambda x: (
                  x["time_last"] - x["time"] + 1 <= max_sequence_length
              )
              & (x["sequence_length"] >= min_sequence_length)
          ]
          # choose longest sequence
          df_index = df_index.loc[
              df_index.groupby("sequence_id").sequence_length.idxmax()
          ]
  
      # check that all groups/series have at least one entry in the index
      if not sequence_ids.isin(df_index.sequence_id).all():
          missing_groups = data.loc[
              ~sequence_ids.isin(df_index.sequence_id), self._group_ids
          ].drop_duplicates()
          # decode values
          for name, id in self._group_ids_mapping.items():
              missing_groups[id] = self.transform_values(
                  name, missing_groups[id], inverse=True, group_id=True
              )
          warnings.warn(
              "Min encoder length and/or min_prediction_idx and/or min prediction length and/or lags are "
              "too large for "
              f"{len(missing_groups)} series/groups which therefore are not present in the dataset index. "
              "This means no predictions can be made for those series. "
              f"First 10 removed groups: {list(missing_groups.iloc[:10].to_dict(orient='index').values())}",
              UserWarning,
          )
      assert (
          len(df_index) > 0
      ), "filters should not remove entries all entries - check encoder/decoder lengths and lags"
  
      return df_index
  
imports: from copy import copy, deepcopy
  from functools import lru_cache
  import inspect
  from typing import Any, Callable, Dict, List, Tuple, Union
  import warnings
  import matplotlib.pyplot
  import numpy
  import pandas
  from sklearn.exceptions import NotFittedError
  from sklearn.preprocessing import RobustScaler, StandardScaler
  from sklearn.utils.validation import check_is_fitted
  import torch
  from torch.distributions import Beta
  from torch.nn.utils import rnn
  from torch.utils.data import DataLoader, Dataset
  from torch.utils.data.sampler import Sampler, SequentialSampler
  from pytorch_forecasting.data.encoders import EncoderNormalizer, GroupNormalizer, MultiNormalizer, NaNLabelEncoder, TorchNormalizer
  from pytorch_forecasting.data.samplers import TimeSynchronizedBatchSampler
  from pytorch_forecasting.utils import repr_class
label_desc: Use image node_id 28 for calling the TimeSeriesDataSet obj's underlying member method code with example usage: obj._construct_index(data, predict_mode) and returns: df_index