Repository: scikit-learn/scikit-learn
Pull request: Fixed TimeSeriesSplit training set size formula
<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/main/CONTRIBUTING.md
-->

#### Reference Issues/PRs
Fixes #28321.


#### What does this implement/fix? Explain your changes.
This fixes the incorrect behavior of the formula to compute the training set size for `TimeSeriesSplit`

#### Any other comments?


<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
For more information, see our FAQ on this topic:
http://scikit-learn.org/dev/faq.html#why-is-my-pull-request-not-getting-any-attention.

Thanks for contributing!
-->


Problem statement:
TimeSeriesSplit Train Size formula correction
### Describe the issue linked to the documentation

The documentation states:
> The training set has size `i * n_samples // (n_splits + 1) + n_samples % (n_splits + 1)` in the `i`th split, with a test set of size `n_samples // (n_splits + 1)` by default, where `n_samples` is the number of samples.

The equation for the training set looks like it is flawed. Given a split with:

```python
test_set_size = 10
n_splits = X.shape[0] // test_set_size
max_train_size = 10000

cv = TimeSeriesSplit(n_splits=n_splits, test_size=test_size, max_train_size=max_train_size)

pairs
for i, (train_index, test_index) in enumerate(cv.split(X)):
    pairs.append([i, len(train_index), len(test_index)])
```

The previous code generates a list similar to this:
```
[[0, 7, 10],
[1, 17, 10],
[2, 27, 10],
[3, 37, 10],
...
[996, 9967, 10],
[997, 9977, 10],
[998, 9987, 10],
[999, 9997, 10],
[1000, 10000, 10],
[1001, 10000, 10],
...
[3177, 10000, 10],
[3178, 10000, 10]]
```

I tried using the formula in the documentation to determine the size of the training set at a given iteration:
```python
def get_train_size(i, n_samples, n_splits):
    return i * n_samples // (n_splits + 1) + n_samples % (n_splits + 1)

get_train_size(0, 31797, 3179)  # 3177, which is incorrect, it should be 7!


def get_train_size(i, n_samples, n_splits, max_train_size=float("inf")):
    start_value = n_samples % n_splits

    increment = n_samples // n_splits

    return int(min(start_value + i * increment, max_train_size))

get_train_size(0, 31797, 3179)  # 7, correct
get_train_size(999, 31797, 3179)  # 9997, correct
get_train_size(1000, 31797, 3179)  # 10007, correct
get_train_size(1000, 31797, 3179, 10000)  # 10000, correct
```

### Suggest a potential alternative/fix

So the correction to the formula should be that the training set size is equal to:
`i * (n_samples // n_splits) + n_samples % n_splits`
instead of:
`i * n_samples // (n_splits + 1) + n_samples % (n_splits + 1)`

Hints from issue discussion:

Commit message: Removed redundant parentheses

Patch to review:
diff --git a/sklearn/model_selection/_split.py b/sklearn/model_selection/_split.py
index 93898a5114191..8147f79ded361 100644
--- a/sklearn/model_selection/_split.py
+++ b/sklearn/model_selection/_split.py
@@ -1167,10 +1167,10 @@ class TimeSeriesSplit(_BaseKFold):
 
     Notes
     -----
-    The training set has size ``i * n_samples // (n_splits + 1)
-    + n_samples % (n_splits + 1)`` in the ``i`` th split,
-    with a test set of size ``n_samples//(n_splits + 1)`` by default,
-    where ``n_samples`` is the number of samples.
+    The training set has size ``i * (n_samples // n_splits) +
+    n_samples % n_splits`` in the ``i`` th split, with a test set
+    of size ``n_samples//(n_splits + 1)`` by default, where 
+    ``n_samples`` is the number of samples.
     """
 
     def __init__(self, n_splits=5, *, max_train_size=None, test_size=None, gap=0):


Code review comments:
diff_hunk: @@ -1167,10 +1167,10 @@ class TimeSeriesSplit(_BaseKFold):
 
     Notes
     -----
-    The training set has size ``i * n_samples // (n_splits + 1)
-    + n_samples % (n_splits + 1)`` in the ``i`` th split,
-    with a test set of size ``n_samples//(n_splits + 1)`` by default,
-    where ``n_samples`` is the number of samples.
+    The training set has size ``i * (n_samples // n_splits) +
+    n_samples % n_splits`` in the ``i`` th split, with a test set
+    of size ``n_samples//(n_splits + 1)`` by default, where 
path: sklearn/model_selection/_split.py
text: @author:
```suggestion
    of size ``n_samples//(n_splits + 1)`` by default, where
```