Train/test split: private API
PortfolioOptimisers.safe_index — Function
safe_index(
lo::Union{Nothing, Number},
hi::Union{Nothing, Number},
N::Integer
) -> Tuple{Any, Any}
safe_index(
lo::Union{Nothing, Number},
hi::Union{Nothing, Number},
N::Integer,
D
) -> Tuple{Any, Any}
Return the (train, test) observation ranges of a holdout split over N time-ordered rows.
Training rows come from the head of the data and test rows from the tail, so the test window is always the most recent one. Each size is a row count (Integer) or a fraction of the observations (AbstractFloat in (0, 1)), resolved by split_count.
- Neither given: the split falls at
D(75 % train, 25 % test). - One given: the other side is its complement, so the two windows partition the data.
- Both given: the head supplies
lotraining rows, the tail supplieshitest rows, and any rows between them are embargoed — they belong to neither window. This is how a gap between train and test is expressed. The gap is declared by the two sizes and nothing else; a rule that derives one from the label horizon belongs to the purged cross-validators (CombinatorialCrossValidation), not here.
Algorithm
Resolve the two window lengths
N_landN_h, through the branch thatloandhiselect:- Neither is given: take
n = clamp(floor(Int, D * N), 1, N), thenN_l = nandN_h = N - n. - Only
lois given: resolve it withsplit_count, thenN_l = nandN_h = N - n. - Only
hiis given: resolve it withsplit_count, thenN_l = N - nandN_h = n. - Both are given: resolve each with
split_counton its own. Neither is the complement of the other, so the rows between the two windows are embargoed.
- Neither is given: take
Check that both windows are non-empty, and that the two do not overlap.
Return the two ranges
1:N_land(N - N_h + 1):N. The training window is the head of the data, and the test window is the tail, so the embargoed rows sit between them.
Arguments
lo: Training rows, as a count (Integer) or a fraction (AbstractFloatin(0, 1));nothingtakes the complement ofhi.hi: Test rows, likewise;nothingtakes the complement oflo.N: Number of observations available.D = 0.75: Training fraction taken when neither size is given.
Validation
- Both windows are non-empty. A split whose sizes saturate the data on one side (
train_size = N) leaves nothing to test on and throws. - The windows do not overlap:
lo + hi <= N.
Returns
(train, test): The training and test row ranges, as twoUnitRange{Int}s.
Related
PortfolioOptimisers.split_count — Function
split_count(s::Integer, N::Integer, name::Symbol) -> Int64
Resolve one side of a train/test split into a row count.
A size is either an Integer count of observations, or an AbstractFloat fraction of them in (0, 1). Counts saturate at N (asking for more rows than exist takes all of them); the safe_index window guards then reject a split that leaves either side empty.
Algorithm
The method that Julia selects is the algorithm. Each step is one method, and the two mean different things: a count and a fraction.
sis anInteger, so it is a count of rows: check thats > 0, and returnmin(Int(s), N). A count larger than the data takes every row.sis anAbstractFloat, so it is a fraction of the rows: check that0 < s < 1, and returnclamp(floor(Int, s * N), 1, N). The fraction rounds down to whole rows, and the clamp keeps a small fraction of a short window from resolving to zero rows.
Arguments
s: One side of the split, as a row count (Integer) or a fraction of the observations (AbstractFloatin(0, 1)).N: Number of observations available.name: Symbolic name of the side, displayed in error messages.
Validation
s > 0whensis anInteger.0 < s < 1whensis anAbstractFloat.
Returns
n::Int: The number of rows the side takes, in1:N.
Related