class_docstr: Provides train/test indices to split time series data samples.
  
  This cross-validation object is a variation of TimeSeriesSplit with the following  differences:
  
  - The splits are made based on datetime duration, instead of number of rows.
  - The user specifies the `valid_duration` and either `train_duration` or `n_splits`.
  - The user can specify a `gap_duration` that is added after the training split and before the validation split.
  
  The 3 duration parameters can be used to really replicate how the model is going to be used in production in batch
  learning.
  
  Each validation fold doesn't overlap. The entire `window` moves by 1 `valid_duration` until there is not enough
  data.
  
  If this would lead to more splits then specified with `n_splits`, the `window` moves by `valid_duration` times the
  fraction of possible splits and requested splits:
  
  - `n_possible_splits = (total_length - train_duration-gap_duration) // valid_duration`
  - `time_shift = valid_duration * n_possible_splits / n_slits`
  
  so the CV spans the whole dataset.
  
  If `train_duration` is not passed but `n_splits` is, the training duration is increased to:
  
  `train_duration = total_length - (gap_duration + valid_duration * n_splits)`
  
  such that the shifting the entire window by one validation duration spans the whole training set.
  
  Parameters
  ----------
  date_serie : pd.Series
      Series with the date, that should have all the indices of X used in the split() method.
  valid_duration : datetime.timedelta
      Retraining period.
  train_duration : datetime.timedelta | None, default=None
      Historical training data.
  gap_duration : datetime.timedelta, default=timedelta(0)
      Forward looking window of the target. The period of the forward looking window necessary to create your target
      variable.
  
      This period is dropped at the end of your training folds due to lack of recent data.
  
      In production you would have not been able to create the target for that period, and you would have drop it from
      the training data.
  n_splits : int | None, default=None
      Number of splits.
  window : Literal["rolling", "expanding"], default="rolling"
      Type of moving window to use.
  
      - `"rolling"` window has fixed size and is shifted entirely.
      - `"expanding"` left side of window is fixed, right border increases each fold.
class_docstr_tok: ["Provides","train\/test","indices","to","split","time","series","data","samples",".","This","cross-validation","object","is","a","variation","of","TimeSeriesSplit","with","the","following","differences",":","-","The","splits","are","made","based","on","datetime","duration",",","instead","of","number","of","rows",".","-","The","user","specifies","the","`","valid_duration","`","and","either","`","train_duration","`","or","`","n_splits","`",".","-","The","user","can","specify","a","`","gap_duration","`","that","is","added","after","the","training","split","and","before","the","validation","split",".","The","3","duration","parameters","can","be","used","to","really","replicate","how","the","model","is","going","to","be","used","in","production","in","batch","learning",".","Each","validation","fold","does","n't","overlap",".","The","entire","`","window","`","moves","by","1","`","valid_duration","`","until","there","is","not","enough","data",".","If","this","would","lead","to","more","splits","then","specified","with","`","n_splits","`",",","the","`","window","`","moves","by","`","valid_duration","`","times","the","fraction","of","possible","splits","and","requested","splits",":","-","`","n_possible_splits","=","(","total_length","-","train_duration-gap_duration",")","\/\/","valid_duration","`","-","`","time_shift","=","valid_duration","*","n_possible_splits","\/","n_slits","`","so","the","CV","spans","the","whole","dataset",".","If","`","train_duration","`","is","not","passed","but","`","n_splits","`","is",",","the","training","duration","is","increased","to",":","`","train_duration","=","total_length","-","(","gap_duration","+","valid_duration","*","n_splits",")","`","such","that","the","shifting","the","entire","window","by","one","validation","duration","spans","the","whole","training","set",".","Parameters","--","--","--","--","--","date_serie",":","pd.Series","Series","with","the","date",",","that","should","have","all","the","indices","of","X","used","in","the","split","(",")","method",".","valid_duration",":","datetime.timedelta","Retraining","period",".","train_duration",":","datetime.timedelta","|","None",",","default=None","Historical","training","data",".","gap_duration",":","datetime.timedelta",",","default=timedelta","(","0",")","Forward","looking","window","of","the","target",".","The","period","of","the","forward","looking","window","necessary","to","create","your","target","variable",".","This","period","is","dropped","at","the","end","of","your","training","folds","due","to","lack","of","recent","data",".","In","production","you","would","have","not","been","able","to","create","the","target","for","that","period",",","and","you","would","have","drop","it","from","the","training","data",".","n_splits",":","int","|","None",",","default=None","Number","of","splits",".","window",":","Literal","[","``","rolling","''",",","``","expanding","''","]",",","default=","''","rolling","''","Type","of","moving","window","to","use",".","-","`","``","rolling","''","`","window","has","fixed","size","and","is","shifted","entirely",".","-","`","``","expanding","''","`","left","side","of","window","is","fixed",",","right","border","increases","each","fold","."]
docstr: Generate indices to split data into training and test set.
  
  Parameters
  ----------
  X : pd.DataFrame
      Dataframe with the data to split.
  y : array-like | None, default=None
      Ignored, present for compatibility.
  groups : array-like | None, default=None
      Ignored, present for compatibility.
  
  Yields
  -------
  tuple[np.ndarray, np.ndarray]
      Train and test indices of the same fold.
docstr_tok: ["Generate","indices","to","split","data","into","training","and","test","set",".","Parameters","--","--","--","--","--","X",":","pd.DataFrame","Dataframe","with","the","data","to","split",".","y",":","array-like","|","None",",","default=None","Ignored",",","present","for","compatibility",".","groups",":","array-like","|","None",",","default=None","Ignored",",","present","for","compatibility",".","Yields","--","--","--","-","tuple","[","np.ndarray",",","np.ndarray","]","Train","and","test","indices","of","the","same","fold","."]
code: def split(self, X, y=None, groups=None):
      """Generate indices to split data into training and test set.
  
      Parameters
      ----------
      X : pd.DataFrame
          Dataframe with the data to split.
      y : array-like | None, default=None
          Ignored, present for compatibility.
      groups : array-like | None, default=None
          Ignored, present for compatibility.
  
      Yields
      -------
      tuple[np.ndarray, np.ndarray]
          Train and test indices of the same fold.
      """
  
      X_index_df = self._join_date_and_x(X)
      X_index_df = X_index_df.sort_values("__date__", ascending=True)
  
      if len(X) != len(X_index_df):
          raise AssertionError(
              "X and X_index_df are not the same length, "
              "there must be some index missing in 'self.date_serie'"
          )
  
      date_min = X_index_df["__date__"].min()
      date_max = X_index_df["__date__"].max()
      date_length = (
          X_index_df["__date__"].max() - X_index_df["__date__"].min()
      )
  
      if (self.train_duration is None) and (self.n_splits is not None):
          self.train_duration = date_length - (
              self.gap_duration + self.valid_duration * self.n_splits
          )
  
      if (self.train_duration is not None) and (
          self.train_duration <= self.gap_duration
      ):
          raise ValueError(
              "gap_duration is longer than train_duration, it should be shorter."
          )
  
      n_split_max = (
          date_length - self.train_duration - self.gap_duration
      ) / self.valid_duration
      if self.n_splits:
          if n_split_max < self.n_splits:
              raise ValueError(
                  (
                      "Number of folds requested = {1} are greater"
                      " than maximum  ={0} possible without"
                      " overlapping validation sets."
                  ).format(n_split_max, self.n_splits)
              )
  
      current_date = date_min
      start_date = date_min
      # if the n_splits is smaller than what would usually be done for train val and gap duration,
      # the next fold is slightly further in time than just valid_duration
      if self.n_splits is not None:
          time_shift = self.valid_duration * n_split_max / self.n_splits
      else:
          time_shift = self.valid_duration
      while True:
          if (
              current_date
              + self.train_duration
              + time_shift
              + self.gap_duration
              > date_max
          ):
              break
  
          X_train_df = X_index_df[
              (X_index_df["__date__"] >= start_date)
              & (
                  X_index_df["__date__"]
                  < current_date + self.train_duration
              )
          ]
          X_valid_df = X_index_df[
              (
                  X_index_df["__date__"]
                  >= current_date
                  + self.train_duration
                  + self.gap_duration
              )
              & (
                  X_index_df["__date__"]
                  < current_date
                  + self.train_duration
                  + self.valid_duration
                  + self.gap_duration
              )
          ]
  
          current_date = current_date + time_shift
          if self.window == "rolling":
              start_date = current_date
          yield (
              X_train_df["np_index"].values,
              X_valid_df["np_index"].values,
          )
  
imports: import numbers
  from datetime import timedelta
  from itertools import combinations
  from warnings import warn
  import numpy
  import pandas
  from sklearn.exceptions import NotFittedError
  from sklearn.model_selection._split import _BaseKFold, check_array
  from sklearn.utils.validation import indexable
  from sklego.base import Clusterer
  from sklego.common import sliding_window
label_desc: Use image node_id 3 for calling the TimeGapSplit obj's underlying member method code with example usage: obj.split(X, y, groups) without return types