File size: 3,538 Bytes
2828671
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""
Temporal walk-forward splitting for time-series validation.
No random shuffle — strict chronological order only.
"""
import pandas as pd
from typing import List, Tuple
from datetime import datetime


def walk_forward_split(
    df: pd.DataFrame,
    train_size: int,
    test_size: int,
    step: int = None
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
    """
    Generate walk-forward train/test splits.
    
    Args:
        df: DataFrame with datetime index (sorted chronologically)
        train_size: Number of rows for training window
        test_size: Number of rows for test window
        step: Step size between splits (default: test_size)
        
    Returns:
        List of (train_df, test_df) tuples
        
    Example:
        If train_size=252, test_size=63 (1 year train, 1 quarter test):
        Split 1: rows 0-251 train, 252-314 test
        Split 2: rows 63-314 train, 315-377 test
        ...
    """
    if not isinstance(df.index, pd.DatetimeIndex):
        raise ValueError("DataFrame must have DatetimeIndex")
    
    if not df.index.is_monotonic_increasing:
        raise ValueError("Index must be sorted chronologically")
    
    if step is None:
        step = test_size
    
    splits = []
    total_rows = len(df)
    
    start = 0
    while start + train_size + test_size <= total_rows:
        train_end = start + train_size
        test_end = train_end + test_size
        
        train_df = df.iloc[start:train_end]
        test_df = df.iloc[train_end:test_end]
        
        splits.append((train_df, test_df))
        
        start += step
    
    return splits


def expanding_window_split(
    df: pd.DataFrame,
    initial_train_size: int,
    test_size: int,
    step: int = None
) -> List[Tuple[pd.DataFrame, pd.DataFrame]]:
    """
    Generate expanding window splits (train set grows over time).
    
    Args:
        df: DataFrame with datetime index (sorted chronologically)
        initial_train_size: Initial training window size
        test_size: Test window size
        step: Step size between splits (default: test_size)
        
    Returns:
        List of (train_df, test_df) tuples
    """
    if not isinstance(df.index, pd.DatetimeIndex):
        raise ValueError("DataFrame must have DatetimeIndex")
    
    if not df.index.is_monotonic_increasing:
        raise ValueError("Index must be sorted chronologically")
    
    if step is None:
        step = test_size
    
    splits = []
    total_rows = len(df)
    
    train_end = initial_train_size
    while train_end + test_size <= total_rows:
        test_end = train_end + test_size
        
        train_df = df.iloc[:train_end]
        test_df = df.iloc[train_end:test_end]
        
        splits.append((train_df, test_df))
        
        train_end += step
    
    return splits


def single_train_test_split(
    df: pd.DataFrame,
    train_ratio: float = 0.8
) -> Tuple[pd.DataFrame, pd.DataFrame]:
    """
    Simple train/test split maintaining temporal order.
    
    Args:
        df: DataFrame with datetime index (sorted chronologically)
        train_ratio: Fraction of data for training
        
    Returns:
        (train_df, test_df)
    """
    if not isinstance(df.index, pd.DatetimeIndex):
        raise ValueError("DataFrame must have DatetimeIndex")
    
    if not df.index.is_monotonic_increasing:
        raise ValueError("Index must be sorted chronologically")
    
    split_idx = int(len(df) * train_ratio)
    
    return df.iloc[:split_idx], df.iloc[split_idx:]