def create_lagged_data(
    target_series: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None,
    past_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None,
    future_covariates: Optional[Union[TimeSeries, Sequence[TimeSeries]]] = None,
    lags: Optional[Union[Sequence[int], dict[str, list[int]]]] = None,
    lags_past_covariates: Optional[Union[Sequence[int], dict[str, list[int]]]] = None,
    lags_future_covariates: Optional[Union[Sequence[int], dict[str, list[int]]]] = None,
    output_chunk_length: int = 1,
    output_chunk_shift: int = 0,
    uses_static_covariates: bool = True,
    last_static_covariates_shape: Optional[tuple[int, int]] = None,
    max_samples_per_ts: Optional[int] = None,
    multi_models: bool = True,
    check_inputs: bool = True,
    use_moving_windows: bool = True,
    is_training: bool = True,
    concatenate: bool = True,
    sample_weight: Optional[Union[str, TimeSeries, Sequence[TimeSeries]]] = None,
    stride: int = 1,
    show_warnings: bool = True,
) -> tuple[
    ArrayOrArraySequence,
    Union[None, ArrayOrArraySequence],
    Sequence[pd.Index],
    Optional[tuple[int, int]],
    Optional[ArrayOrArraySequence],
]:
    """
    Creates the features array `X` and labels array `y` to train a lagged-variables `SKLearnModel` when
    `is_training = True`; alternatively, creates the features array `X` to produce a series of prediction from an
    already-trained model when `is_training = False`. In both cases, a list of time indices corresponding to
    each generated observation is also returned.

    Notes
    -----
    Instead of calling `create_lagged_data` directly, it is instead recommended that:
        - `create_lagged_training_data` be called if one wishes to create the `X` and `y` arrays
        to train an `SKLearnModel`.
        - `create_lagged_prediction_data` be called if one wishes to create the `X` array required
        to generate a prediction from an already-trained `SKLearnModel`.
    This is because even though both of these functions are merely wrappers around `create_lagged_data`, their
    call signatures are more easily interpreted than `create_lagged_data`. For example,
    `create_lagged_prediction_data` does not accept `output_chunk_length` nor `multi_models` as inputs, since
    these inputs are not used when constructing prediction data. Similarly, `create_lagged_prediction_data`
    returns only `X` and `times` as outputs, as opposed to returning `y` as `None` along with `X` and `times`.

    The `X` array is constructed from the lagged values of up to three separate timeseries:
        1. The `target_series`, which contains the values we're trying to predict. An `SKLearnModel` that
        uses previous values of the target its predicting is referred to as *autoregressive*; please refer to
        [1]_ for further details about autoregressive timeseries models.
        2. The past covariates series, which contains values that are *not* known into the future. Unlike
        the target series, however, past covariates are *not* to be predicted by the `SKLearnModel`.
        3. The future covariates (AKA 'exogenous' covariates) series, which contains values that are known
        into the future, even beyond the data in `target_series` and `past_covariates`.
    See [2]_ for a more detailed discussion about target, past, and future covariates. Conversely, `y` is
    comprised only of the lagged values of `target_series`.

    The shape of `X` is:
        `X.shape = (n_observations, n_lagged_features, n_samples)`,
    where `n_observations` equals either the number of time points shared between all specified series,
    or `max_samples_per_ts`, whichever is smallest.
    The shape of `y` is:
        `y.shape = (n_observations, output_chunk_length, n_samples)`,
    if `multi_models = True`, otherwise:
        `y.shape = (n_observations, 1, n_samples)`.

    Along the `n_lagged_features` axis, `X` has the following structure (for `*_lags=[-2,-1]` and
    `*_series.n_components = 2`):
        lagged_target | lagged_past_covariates | lagged_future_covariates
    where each `lagged_*` has the following structure:
        lag_-2_comp_1_* | lag_-2_comp_2_* | lag_-1_comp_1_* | lag_-1_comp_2_*

    Along the `n_lagged_labels` axis, `y` has the following structure (for `output_chunk_length=4` and
    `target_series.n_components=2`):
        lag_+0_comp_1_target | lag_+0_comp_2_target | ... | lag_+3_comp_1_target | lag_+3_comp_2_target

    The `lags` and `lags_past_covariates` must contain only values less than or equal to -1. In other words, one
    cannot use the value of either of these series at time `t` to predict the value of the target series at the
    same time `t`; this is because the values of `target_series` and `past_covariates` at time `t` aren't available
    at prediction time, by definition. Conversely, since the values of `future_covariates` are known into the future,
    `lags_future_covariates` can contain negative, positive, and/or zero lag values (i.e. we *can* use the values of
    `future_covariates` at time `t` or beyond to predict the value of `target_series` at time `t`).

    The exact method used to construct `X` and `y` depends on whether all specified timeseries are
    of the same frequency or not:
        - If all specified timeseries are of the same frequency, `strided_moving_window` is used to extract
        contiguous time blocks from each timeseries; the lagged variables are then extracted from each window.
        - If all specified timeseries are *not* of the same frequency, then `find_shared_times` is first used
        to find those times common to all three timeseries, after which the lagged features are extracted by
        offsetting the time indices of these common times by the requested lags.
    In cases where it can be validly applied, the 'moving window' method is expected to be faster than the
    'intersecting time' method. However, in exceptional cases where only a small number of lags are being
    extracted, but the difference between the lag values is large (e.g. `lags = [-1, -1000]`), the 'moving
    window' method is expected to consume significantly more memory, since it extracts all series values
    between the maximum and minimum lags as 'windows', before actually extracting the specific requested lag values.

    In order for the lagged features of a series to be added to `X`, *both* that series and the corresponding lags
    must be specified; if a series is specified without the corresponding lags, that series will be ignored and not
    added to `X`. `X` and `y` arrays are constructed independently over the samples dimension (i.e. the second axis)
    of each series.

    If the provided series are stochastic (i.e. `series.n_components > 1`), then an `X` and `y` array will be
    constructed for each sample; the arrays corresponding to each sample are concatenated together along the `2`nd
    axis of `X` and `y`. In other words, `create_lagged_data` is vectorized over the sample axis of the `target_series`,
    `past_covariates`, and `future_covariates` inputs. Importantly, if stochastic series are provided, each series must
    have the same number of samples, otherwise an error will be thrown.

    Each series input (i.e. `target_series`, `past_covariates`, and `future_covariates`) can be specified either as
    a single `TimeSeries`, or as a `Sequence` of `TimeSeries`; the specified series must all be of the same type,
    however (i.e. either all `TimeSeries` or all `Sequence[TimeSeries]`). If `Sequence[TimeSeries]` are specified,
    then a feature matrix `X` and labels array `y` will be constructed using the corresponding `TimeSeries` in
    each `Sequence` (i.e. the first `TimeSeries` in each `Sequence` are used to create an `X` and `y`, then
    the second `TimeSeries` in each `Sequence` are used to create an `X` and `y`, etc.). If `concatenate = True`,
    these `X`'s and `y`'s will be concatenated along the `0`th axis; otherwise, a list of `X` and `y` array will
    be returned. Note that `times` is always returned as a `Sequence[pd.Index]`, however, even when
    `concatenate = True`.

    Parameters
    ----------
    target_series
        Optionally, the series for the `SKLearnModel` to predict. Must be specified if `is_training = True`.
        Can be specified as either a `TimeSeries` or as a `Sequence[TimeSeries]`.
    past_covariates
        Optionally, the past covariates series that the `SKLearnModel` will use as inputs. Unlike the
        `target_series`, `past_covariates` are *not* to be predicted by the `SKLearnModel`. Can be
        specified as either a `TimeSeries` or as a `Sequence[TimeSeries]`.
    future_covariates
        Optionally, the future covariates (i.e. exogenous covariates) series that the `SKLearnModel` will
        use as inputs. Can be specified as either a `TimeSeries` or as a `Sequence[TimeSeries]`.
    lags
        Optionally, the lags of the target series to be used as (autoregressive) features. If not specified,
        autoregressive features will *not* be added to `X`. Each lag value is assumed to be negative (e.g.
        `lags = [-3, -1]` will extract `target_series` values which are 3 time steps and 1 time step away from
        the current value). If the lags are provided as a dictionary, the lags values are specific to each
        component in the target series.
    lags_past_covariates
        Optionally, the lags of `past_covariates` to be used as features. Like `lags`, each lag value is assumed to
        be less than or equal to -1. If the lags are provided as a dictionary, the lags values are specific to each
        component in the past covariates series.
    lags_future_covariates
        Optionally, the lags of `future_covariates` to be used as features. Unlike `lags` and
        `lags_past_covariates`, `lags_future_covariates` values can be positive (i.e. use values *after* time `t`
        to predict target at time `t`), zero (i.e. use values *at* time `t` to predict target at time `t`), and/or
        negative (i.e. use values *before* time `t` to predict target at time `t`). If `output_chunk_shift > 0`, the
        lags are relative to the first time step of the shifted output chunk. If the lags are provided as
        a dictionary, the lags values are specific to each component in the future covariates series.
    output_chunk_length
        Optionally, the number of time steps ahead into the future the `SKLearnModel` is to predict. Must
        best specified if `is_training = True`.
    output_chunk_shift
        Optionally, the number of time steps to shift the output chunk ahead into the future.
    uses_static_covariates
        Whether the model uses/expects static covariates. If `True`, it enforces that static covariates must
        have identical shapes across all target series.
    last_static_covariates_shape
        Optionally, the last observed shape of the static covariates. This is ``None`` before fitting, or when
        `uses_static_covariates` is ``False``.
    max_samples_per_ts
        Optionally, the maximum number of samples to be drawn for training/validation; only the most recent
        samples are kept. In theory, specifying a smaller `max_samples_per_ts` should reduce computation time,
        especially in cases where many observations could be generated.
    multi_models
        Optionally, specifies whether the `SKLearnModel` predicts multiple time steps into the future. If `True`,
        then the `SKLearnModel` is assumed to predict all time steps from time `t` to `t+output_chunk_length`.
        If `False`, then the `SKLearnModel` is assumed to predict *only* the time step at `t+output_chunk_length`.
        This input is ignored if `is_training = False`.
    check_inputs
        Optionally, specifies that the `lags_*` and `series_*` inputs should be checked for validity. Should be set
        to `False` if inputs have already been checked for validity (e.g. inside the `__init__` of a class), otherwise
        should be set to `True`.
    use_moving_windows
        Optionally, specifies that the 'moving window' method should be used to construct `X` and `y` if all
        provided series are of the same frequency. If `use_moving_windows = False`, the 'time intersection' method
        will always be used, even when all provided series are of the same frequency. In general, setting
        to `True` results in faster tabularization at the potential cost of higher memory usage. See Notes for further
        details.
    is_training
        Optionally, specifies whether the constructed lagged data are to be used for training an `SKLearnModel`
        (i.e. `is_training = True`), or for generating predictions from an already-trained `SKLearnModel` (i.e.
        `is_training = False`). If `is_training = True`, `target_series` and `output_chunk_length` must be specified,
        the `multi_models` input is utilised, and a label array `y` is returned. Conversely, if `is_training = False`,
        then `target_series` and `output_chunk_length` do not need to be specified, the `multi_models` input is ignored,
        and the returned `y` value is `None`.
    concatenate
        Optionally, specifies that `X` and `y` should both be returned as single `np.ndarray`s, instead of as
        a `Sequence[np.ndarray]`. If each series input is specified as a `Sequence[TimeSeries]` and
        `concatenate = False`, `X` and `y` will be lists whose `i`th element corresponds to the feature matrix or label
        array formed by the `i`th `TimeSeries` in each `Sequence[TimeSeries]` input. Conversely, if `concatenate = True`
        when `Sequence[TimeSeries]` are provided, then `X` and `y` will be arrays created by concatenating all
        feature/label arrays formed by each `TimeSeries` along the `0`th axis. Note that `times` is still returned as
        `Sequence[pd.Index]`, even when `concatenate = True`.
    sample_weight
        Optionally, some sample weights to apply to the target `series` labels. They are applied per observation,
        per label (each step in `output_chunk_length`), and per component.
        If a series or sequence of series, then those weights are used. If the weight series only have a single
        component / column, then the weights are applied globally to all components in `series`. Otherwise, for
        component-specific weights, the number of components must match those of `series`.
        If a string, then the weights are generated using built-in weighting functions. The available options are
        `"linear"` or `"exponential"` decay - the further in the past, the lower the weight. The weights are
        computed globally based on the length of the longest series in `series`. Then for each series, the weights
        are extracted from the end of the global weights. This gives a common time weighting across all series.
    stride
        The number of time steps between consecutive samples, applied starting from the end of the series. This should
        be used with caution as it might introduce bias in the forecasts.
    show_warnings
        Whether to show warnings.

    Returns
    -------
    X
        The constructed features array(s), with shape `(n_observations, n_lagged_features, n_samples)`.
        If the series inputs were specified as `Sequence[TimeSeries]` and `concatenate = False`, then `X`
        is returned as a `Sequence[np.array]`; otherwise, `X` is returned as a single `np.array`.
    y
        The constructed labels array. If `multi_models = True`, then `y` is a
        `(n_observations, output_chunk_length, n_samples)`-shaped array; conversely, if
        `multi_models =  False`, then `y` is a `(n_observations, 1, n_samples)`-shaped array.
        If the series inputs were specified as `Sequence[TimeSeries]` and `concatenate = False`, then `y`
        is returned as a `Sequence[np.array]`; otherwise, `y` is returned as a single `np.array`.
    times
        The `time_index` of each observation in `X` and `y`, returned as a `Sequence` of `pd.Index`es.
        If the series inputs were specified as `Sequence[TimeSeries]`, then the `i`th list element
        gives the times of those observations formed using the `i`th `TimeSeries` object in each
        `Sequence`. Otherwise, if the series inputs were specified as `TimeSeries`, the only
        element is the times of those observations formed from the lone `TimeSeries` inputs.
    last_static_covariates_shape
        The last observed shape of the static covariates. This is ``None`` when `uses_static_covariates`
        is ``False``.
    sample_weight
        The weights to apply to each observation in `X` and output step `y`, returned as a `Sequence` of `np.ndarray`.

    Raises
    ------
    ValueError
        If the specified time series do not share any times for which features (and labels if `is_training = True`) can
        be constructed.
    ValueError
        If no lags are specified, or if any of the specified lag values are non-negative.
    ValueError
        If any of the series are too short to create features and/or labels for the requested lags and
        `output_chunk_length` values.
    ValueError
        If `target_series` and/or `output_chunk_length` are *not* specified when `is_training = True`.
    ValueError
        If the provided series do not share the same type of `time_index` (e.g. `target_series` uses a
        pd.RangeIndex, but `future_covariates` uses a `pd.DatetimeIndex`).

    References
    ----------
    .. [1] https://otexts.com/fpp2/AR.html#AR
    .. [2] https://unit8.com/resources/time-series-forecasting-using-past-and-future-external-data-with-darts/

    See Also
    --------
        tabularization.create_lagged_component_names : return the lagged features names as a list of strings.

    """
    if is_training and (target_series is None):
        raise_log(
            ValueError("Must specify `target_series` if `is_training = True`."),
            logger=logger,
        )

    # ensure list of TimeSeries format
    target_series = series2seq(target_series)
    past_covariates = series2seq(past_covariates)
    future_covariates = series2seq(future_covariates)

    seq_ts_lens = [
        len(seq_ts)
        for seq_ts in (target_series, past_covariates, future_covariates)
        if seq_ts is not None
    ]
    seq_ts_lens = set(seq_ts_lens)
    if len(seq_ts_lens) > 1:
        raise_log(
            ValueError(
                "Must specify the same number of `TimeSeries` for each series input."
            ),
            logger,
        )

    # process / check sample weight and generate series in case of built-in weight generator
    sample_weight = _process_sample_weight(sample_weight, target_series)

    lags_passed_as_dict = any(
        isinstance(lags_, dict)
        for lags_ in [lags, lags_past_covariates, lags_future_covariates]
    )
    if (not use_moving_windows) and lags_passed_as_dict:
        raise_log(
            ValueError(
                "`use_moving_windows=False` is not supported when any of the lags is provided as a dictionary. "
                f"Received: {[lags, lags_past_covariates, lags_future_covariates]}."
            ),
            logger,
        )

    if max_samples_per_ts is None:
        max_samples_per_ts = inf

    # lags are identical for multiple series: pre-compute lagged features and reordered lagged features
    lags_extract, lags_order = _get_lagged_indices(
        lags,
        lags_past_covariates,
        lags_future_covariates,
    )
    X, y, times, sample_weights = [], [], [], []
    for i in range(max(seq_ts_lens)):
        target_i = target_series[i] if target_series else None
        past_i = past_covariates[i] if past_covariates else None
        future_i = future_covariates[i] if future_covariates else None
        sample_weight_i = sample_weight[i] if sample_weight else None
        series_equal_freq = _all_equal_freq(target_i, past_i, future_i)
        # component-wise lags extraction is not support with times intersection at the moment
        if use_moving_windows and lags_passed_as_dict and (not series_equal_freq):
            raise_log(
                ValueError(
                    f"Cannot create tabularized data for the {i}th series because target and covariates don't have "
                    "the same frequency and some of the lags are provided as a dictionary. Either resample the "
                    "series or change the lags definition."
                ),
                logger,
            )
        if use_moving_windows and series_equal_freq:
            X_i, y_i, times_i, weights_i = _create_lagged_data_by_moving_window(
                target_series=target_i,
                output_chunk_length=output_chunk_length,
                output_chunk_shift=output_chunk_shift,
                past_covariates=past_i,
                future_covariates=future_i,
                sample_weight=sample_weight_i,
                lags=lags,
                lags_past_covariates=lags_past_covariates,
                lags_future_covariates=lags_future_covariates,
                lags_extract=lags_extract,
                lags_order=lags_order,
                max_samples_per_ts=max_samples_per_ts,
                multi_models=multi_models,
                check_inputs=check_inputs,
                is_training=is_training,
                stride=stride,
                show_warnings=show_warnings,
            )
        else:
            X_i, y_i, times_i, weights_i = _create_lagged_data_by_intersecting_times(
                target_series=target_i,
                output_chunk_length=output_chunk_length,
                output_chunk_shift=output_chunk_shift,
                past_covariates=past_i,
                future_covariates=future_i,
                sample_weight=sample_weight_i,
                lags=lags,
                lags_past_covariates=lags_past_covariates,
                lags_future_covariates=lags_future_covariates,
                max_samples_per_ts=max_samples_per_ts,
                multi_models=multi_models,
                check_inputs=check_inputs,
                is_training=is_training,
                stride=stride,
                show_warnings=show_warnings,
            )
        X_i, last_static_covariates_shape = add_static_covariates_to_lagged_data(
            features=X_i,
            target_series=target_i,
            uses_static_covariates=uses_static_covariates,
            last_shape=last_static_covariates_shape,
        )
        X.append(X_i)
        y.append(y_i)
        times.append(times_i)
        if weights_i is not None:
            sample_weights.append(weights_i)

    if concatenate:
        X = np.concatenate(X, axis=0)
    if not is_training:
        y = None
    elif concatenate:
        y = np.concatenate(y, axis=0)

    if sample_weights and concatenate:
        sample_weights = np.concatenate(sample_weights, axis=0)
    elif not sample_weights:
        sample_weights = None
    return X, y, times, last_static_covariates_shape, sample_weights

    Creates the features array `X` and labels array `y` to train a lagged-variables `SKLearnModel` when
    `is_training = True`; alternatively, creates the features array `X` to produce a series of prediction from an
    already-trained model when `is_training = False`. In both cases, a list of time indices corresponding to
    each generated observation is also returned.

    Notes
    -----
    Instead of calling `create_lagged_data` directly, it is instead recommended that:
        - `create_lagged_training_data` be called if one wishes to create the `X` and `y` arrays
        to train an `SKLearnModel`.
        - `create_lagged_prediction_data` be called if one wishes to create the `X` array required
        to generate a prediction from an already-trained `SKLearnModel`.
    This is because even though both of these functions are merely wrappers around `create_lagged_data`, their
    call signatures are more easily interpreted than `create_lagged_data`. For example,
    `create_lagged_prediction_data` does not accept `output_chunk_length` nor `multi_models` as inputs, since
    these inputs are not used when constructing prediction data. Similarly, `create_lagged_prediction_data`
    returns only `X` and `times` as outputs, as opposed to returning `y` as `None` along with `X` and `times`.

    The `X` array is constructed from the lagged values of up to three separate timeseries:
        1. The `target_series`, which contains the values we're trying to predict. An `SKLearnModel` that
        uses previous values of the target its predicting is referred to as *autoregressive*; please refer to
        [1]_ for further details about autoregressive timeseries models.
        2. The past covariates series, which contains values that are *not* known into the future. Unlike
        the target series, however, past covariates are *not* to be predicted by the `SKLearnModel`.
        3. The future covariates (AKA 'exogenous' covariates) series, which contains values that are known
        into the future, even beyond the data in `target_series` and `past_covariates`.
    See [2]_ for a more detailed discussion about target, past, and future covariates. Conversely, `y` is
    comprised only of the lagged values of `target_series`.

    The shape of `X` is:
        `X.shape = (n_observations, n_lagged_features, n_samples)`,
    where `n_observations` equals either the number of time points shared between all specified series,
    or `max_samples_per_ts`, whichever is smallest.
    The shape of `y` is:
        `y.shape = (n_observations, output_chunk_length, n_samples)`,
    if `multi_models = True`, otherwise:
        `y.shape = (n_observations, 1, n_samples)`.

    Along the `n_lagged_features` axis, `X` has the following structure (for `*_lags=[-2,-1]` and
    `*_series.n_components = 2`):
        lagged_target | lagged_past_covariates | lagged_future_covariates
    where each `lagged_*` has the following structure:
        lag_-2_comp_1_* | lag_-2_comp_2_* | lag_-1_comp_1_* | lag_-1_comp_2_*

    Along the `n_lagged_labels` axis, `y` has the following structure (for `output_chunk_length=4` and
    `target_series.n_components=2`):
        lag_+0_comp_1_target | lag_+0_comp_2_target | ... | lag_+3_comp_1_target | lag_+3_comp_2_target

    The `lags` and `lags_past_covariates` must contain only values less than or equal to -1. In other words, one
    cannot use the value of either of these series at time `t` to predict the value of the target series at the
    same time `t`; this is because the values of `target_series` and `past_covariates` at time `t` aren't available
    at prediction time, by definition. Conversely, since the values of `future_covariates` are known into the future,
    `lags_future_covariates` can contain negative, positive, and/or zero lag values (i.e. we *can* use the values of
    `future_covariates` at time `t` or beyond to predict the value of `target_series` at time `t`).

    The exact method used to construct `X` and `y` depends on whether all specified timeseries are
    of the same frequency or not:
        - If all specified timeseries are of the same frequency, `strided_moving_window` is used to extract
        contiguous time blocks from each timeseries; the lagged variables are then extracted from each window.
        - If all specified timeseries are *not* of the same frequency, then `find_shared_times` is first used
        to find those times common to all three timeseries, after which the lagged features are extracted by
        offsetting the time indices of these common times by the requested lags.
    In cases where it can be validly applied, the 'moving window' method is expected to be faster than the
    'intersecting time' method. However, in exceptional cases where only a small number of lags are being
    extracted, but the difference between the lag values is large (e.g. `lags = [-1, -1000]`), the 'moving
    window' method is expected to consume significantly more memory, since it extracts all series values
    between the maximum and minimum lags as 'windows', before actually extracting the specific requested lag values.

    In order for the lagged features of a series to be added to `X`, *both* that series and the corresponding lags
    must be specified; if a series is specified without the corresponding lags, that series will be ignored and not
    added to `X`. `X` and `y` arrays are constructed independently over the samples dimension (i.e. the second axis)
    of each series.

    If the provided series are stochastic (i.e. `series.n_components > 1`), then an `X` and `y` array will be
    constructed for each sample; the arrays corresponding to each sample are concatenated together along the `2`nd
    axis of `X` and `y`. In other words, `create_lagged_data` is vectorized over the sample axis of the `target_series`,
    `past_covariates`, and `future_covariates` inputs. Importantly, if stochastic series are provided, each series must
    have the same number of samples, otherwise an error will be thrown.

    Each series input (i.e. `target_series`, `past_covariates`, and `future_covariates`) can be specified either as
    a single `TimeSeries`, or as a `Sequence` of `TimeSeries`; the specified series must all be of the same type,
    however (i.e. either all `TimeSeries` or all `Sequence[TimeSeries]`). If `Sequence[TimeSeries]` are specified,
    then a feature matrix `X` and labels array `y` will be constructed using the corresponding `TimeSeries` in
    each `Sequence` (i.e. the first `TimeSeries` in each `Sequence` are used to create an `X` and `y`, then
    the second `TimeSeries` in each `Sequence` are used to create an `X` and `y`, etc.). If `concatenate = True`,
    these `X`'s and `y`'s will be concatenated along the `0`th axis; otherwise, a list of `X` and `y` array will
    be returned. Note that `times` is always returned as a `Sequence[pd.Index]`, however, even when
    `concatenate = True`.

    Parameters
    ----------
    target_series
        Optionally, the series for the `SKLearnModel` to predict. Must be specified if `is_training = True`.
        Can be specified as either a `TimeSeries` or as a `Sequence[TimeSeries]`.
    past_covariates
        Optionally, the past covariates series that the `SKLearnModel` will use as inputs. Unlike the
        `target_series`, `past_covariates` are *not* to be predicted by the `SKLearnModel`. Can be
        specified as either a `TimeSeries` or as a `Sequence[TimeSeries]`.
    future_covariates
        Optionally, the future covariates (i.e. exogenous covariates) series that the `SKLearnModel` will
        use as inputs. Can be specified as either a `TimeSeries` or as a `Sequence[TimeSeries]`.
    lags
        Optionally, the lags of the target series to be used as (autoregressive) features. If not specified,
        autoregressive features will *not* be added to `X`. Each lag value is assumed to be negative (e.g.
        `lags = [-3, -1]` will extract `target_series` values which are 3 time steps and 1 time step away from
        the current value). If the lags are provided as a dictionary, the lags values are specific to each
        component in the target series.
    lags_past_covariates
        Optionally, the lags of `past_covariates` to be used as features. Like `lags`, each lag value is assumed to
        be less than or equal to -1. If the lags are provided as a dictionary, the lags values are specific to each
        component in the past covariates series.
    lags_future_covariates
        Optionally, the lags of `future_covariates` to be used as features. Unlike `lags` and
        `lags_past_covariates`, `lags_future_covariates` values can be positive (i.e. use values *after* time `t`
        to predict target at time `t`), zero (i.e. use values *at* time `t` to predict target at time `t`), and/or
        negative (i.e. use values *before* time `t` to predict target at time `t`). If `output_chunk_shift > 0`, the
        lags are relative to the first time step of the shifted output chunk. If the lags are provided as
        a dictionary, the lags values are specific to each component in the future covariates series.
    output_chunk_length
        Optionally, the number of time steps ahead into the future the `SKLearnModel` is to predict. Must
        best specified if `is_training = True`.
    output_chunk_shift
        Optionally, the number of time steps to shift the output chunk ahead into the future.
    uses_static_covariates
        Whether the model uses/expects static covariates. If `True`, it enforces that static covariates must
        have identical shapes across all target series.
    last_static_covariates_shape
        Optionally, the last observed shape of the static covariates. This is ``None`` before fitting, or when
        `uses_static_covariates` is ``False``.
    max_samples_per_ts
        Optionally, the maximum number of samples to be drawn for training/validation; only the most recent
        samples are kept. In theory, specifying a smaller `max_samples_per_ts` should reduce computation time,
        especially in cases where many observations could be generated.
    multi_models
        Optionally, specifies whether the `SKLearnModel` predicts multiple time steps into the future. If `True`,
        then the `SKLearnModel` is assumed to predict all time steps from time `t` to `t+output_chunk_length`.
        If `False`, then the `SKLearnModel` is assumed to predict *only* the time step at `t+output_chunk_length`.
        This input is ignored if `is_training = False`.
    check_inputs
        Optionally, specifies that the `lags_*` and `series_*` inputs should be checked for validity. Should be set
        to `False` if inputs have already been checked for validity (e.g. inside the `__init__` of a class), otherwise
        should be set to `True`.
    use_moving_windows
        Optionally, specifies that the 'moving window' method should be used to construct `X` and `y` if all
        provided series are of the same frequency. If `use_moving_windows = False`, the 'time intersection' method
        will always be used, even when all provided series are of the same frequency. In general, setting
        to `True` results in faster tabularization at the potential cost of higher memory usage. See Notes for further
        details.
    is_training
        Optionally, specifies whether the constructed lagged data are to be used for training an `SKLearnModel`
        (i.e. `is_training = True`), or for generating predictions from an already-trained `SKLearnModel` (i.e.
        `is_training = False`). If `is_training = True`, `target_series` and `output_chunk_length` must be specified,
        the `multi_models` input is utilised, and a label array `y` is returned. Conversely, if `is_training = False`,
        then `target_series` and `output_chunk_length` do not need to be specified, the `multi_models` input is ignored,
        and the returned `y` value is `None`.
    concatenate
        Optionally, specifies that `X` and `y` should both be returned as single `np.ndarray`s, instead of as
        a `Sequence[np.ndarray]`. If each series input is specified as a `Sequence[TimeSeries]` and
        `concatenate = False`, `X` and `y` will be lists whose `i`th element corresponds to the feature matrix or label
        array formed by the `i`th `TimeSeries` in each `Sequence[TimeSeries]` input. Conversely, if `concatenate = True`
        when `Sequence[TimeSeries]` are provided, then `X` and `y` will be arrays created by concatenating all
        feature/label arrays formed by each `TimeSeries` along the `0`th axis. Note that `times` is still returned as
        `Sequence[pd.Index]`, even when `concatenate = True`.
    sample_weight
        Optionally, some sample weights to apply to the target `series` labels. They are applied per observation,
        per label (each step in `output_chunk_length`), and per component.
        If a series or sequence of series, then those weights are used. If the weight series only have a single
        component / column, then the weights are applied globally to all components in `series`. Otherwise, for
        component-specific weights, the number of components must match those of `series`.
        If a string, then the weights are generated using built-in weighting functions. The available options are
        `"linear"` or `"exponential"` decay - the further in the past, the lower the weight. The weights are
        computed globally based on the length of the longest series in `series`. Then for each series, the weights
        are extracted from the end of the global weights. This gives a common time weighting across all series.
    stride
        The number of time steps between consecutive samples, applied starting from the end of the series. This should
        be used with caution as it might introduce bias in the forecasts.
    show_warnings
        Whether to show warnings.

    Returns
    -------
    X
        The constructed features array(s), with shape `(n_observations, n_lagged_features, n_samples)`.
        If the series inputs were specified as `Sequence[TimeSeries]` and `concatenate = False`, then `X`
        is returned as a `Sequence[np.array]`; otherwise, `X` is returned as a single `np.array`.
    y
        The constructed labels array. If `multi_models = True`, then `y` is a
        `(n_observations, output_chunk_length, n_samples)`-shaped array; conversely, if
        `multi_models =  False`, then `y` is a `(n_observations, 1, n_samples)`-shaped array.
        If the series inputs were specified as `Sequence[TimeSeries]` and `concatenate = False`, then `y`
        is returned as a `Sequence[np.array]`; otherwise, `y` is returned as a single `np.array`.
    times
        The `time_index` of each observation in `X` and `y`, returned as a `Sequence` of `pd.Index`es.
        If the series inputs were specified as `Sequence[TimeSeries]`, then the `i`th list element
        gives the times of those observations formed using the `i`th `TimeSeries` object in each
        `Sequence`. Otherwise, if the series inputs were specified as `TimeSeries`, the only
        element is the times of those observations formed from the lone `TimeSeries` inputs.
    last_static_covariates_shape
        The last observed shape of the static covariates. This is ``None`` when `uses_static_covariates`
        is ``False``.
    sample_weight
        The weights to apply to each observation in `X` and output step `y`, returned as a `Sequence` of `np.ndarray`.

    Raises
    ------
    ValueError
        If the specified time series do not share any times for which features (and labels if `is_training = True`) can
        be constructed.
    ValueError
        If no lags are specified, or if any of the specified lag values are non-negative.
    ValueError
        If any of the series are too short to create features and/or labels for the requested lags and
        `output_chunk_length` values.
    ValueError
        If `target_series` and/or `output_chunk_length` are *not* specified when `is_training = True`.
    ValueError
        If the provided series do not share the same type of `time_index` (e.g. `target_series` uses a
        pd.RangeIndex, but `future_covariates` uses a `pd.DatetimeIndex`).

    References
    ----------
    .. [1] https://otexts.com/fpp2/AR.html#AR
    .. [2] https://unit8.com/resources/time-series-forecasting-using-past-and-future-external-data-with-darts/

    See Also
    --------
        tabularization.create_lagged_component_names : return the lagged features names as a list of strings.

    
darts/utils/data/tabularization/tabularization.py