///|
/// Deterministic train/validation/test partition.
pub struct DataPartition {
train : Array[Double]
validation : Array[Double]
test_set : Array[Double]
train_indices : Array[Int]
validation_indices : Array[Int]
test_indices : Array[Int]
}
///|
/// One fold in deterministic cross validation.
pub struct DataFold {
fold : Int
train : Array[Double]
holdout : Array[Double]
train_indices : Array[Int]
holdout_indices : Array[Int]
}
///|
/// Sampling configuration.
pub struct SamplingRule {
seed : Int
train_fraction : Double
validation_fraction : Double
folds : Int
}
///|
pub fn sampling_default_rule() -> SamplingRule {
{ seed: 20260819, train_fraction: 0.7, validation_fraction: 0.15, folds: 5 }
}
///|
pub fn sampling_rule(
seed : Int,
train_fraction : Double,
validation_fraction : Double,
folds : Int,
) -> SamplingRule {
let train = if train_fraction < 0.0 {
0.0
} else if train_fraction > 1.0 {
1.0
} else {
train_fraction
}
let validation = if validation_fraction < 0.0 {
0.0
} else if validation_fraction > 1.0 {
1.0
} else {
validation_fraction
}
{
seed,
train_fraction: train,
validation_fraction: validation,
folds: if folds < 2 {
2
} else {
folds
},
}
}
///|
pub fn sampling_indices(length : Int) -> Array[Int] {
let result = []
if length <= 0 {
return result
}
for index = 0; index < length; index = index + 1 {
result.push(index)
}
result
}
///|
pub fn sampling_shuffle_indices(length : Int, seed : Int) -> Array[Int] {
let result = sampling_indices(length)
if result.length() <= 1 {
return result
}
let rng = DeterministicRng::new(seed)
for index = result.length() - 1; index > 0; index = index - 1 {
let swap_index = rng.next_int(index + 1)
let value = result[index]
result[index] = result[swap_index]
result[swap_index] = value
}
result
}
///|
pub fn sampling_shuffle(data : Array[Double], seed : Int) -> Array[Double] {
let indices = sampling_shuffle_indices(data.length(), seed)
let result = []
for index in indices {
result.push(data[index])
}
result
}
///|
pub fn sampling_take_indices(
data : Array[Double],
indices : Array[Int],
) -> Array[Double] {
let result = []
for index in indices {
if index >= 0 && index < data.length() {
result.push(data[index])
}
}
result
}
///|
pub fn sampling_take(
data : Array[Double],
count : Int,
seed : Int,
) -> Array[Double] {
let indices = sampling_shuffle_indices(data.length(), seed)
let limit = if count < 0 {
0
} else if count > indices.length() {
indices.length()
} else {
count
}
let selected = []
for index = 0; index < limit; index = index + 1 {
selected.push(indices[index])
}
sampling_take_indices(data, selected)
}
///|
pub fn sampling_without_replacement(
data : Array[Double],
count : Int,
seed : Int,
) -> Array[Double] {
sampling_take(data, count, seed)
}
///|
pub fn sampling_with_replacement(
data : Array[Double],
count : Int,
seed : Int,
) -> Array[Double] {
let result = []
if data.length() == 0 || count <= 0 {
return result
}
let rng = DeterministicRng::new(seed)
for _ in 0.. Array[Array[Double]] {
let result = []
if replicates <= 0 {
return result
}
for index = 0; index < replicates; index = index + 1 {
result.push(sampling_with_replacement(data, data.length(), seed + index))
}
result
}
///|
pub fn sampling_bootstrap_means(
data : Array[Double],
replicates : Int,
seed : Int,
) -> Array[Double] {
let result = []
for sample in sampling_bootstrap(data, replicates, seed) {
result.push(mean(sample))
}
result
}
///|
pub fn sampling_bootstrap_medians(
data : Array[Double],
replicates : Int,
seed : Int,
) -> Array[Double] {
let result = []
for sample in sampling_bootstrap(data, replicates, seed) {
result.push(median(sample))
}
result
}
///|
pub fn sampling_bootstrap_scales(
data : Array[Double],
replicates : Int,
seed : Int,
) -> Array[Double] {
let result = []
for sample in sampling_bootstrap(data, replicates, seed) {
result.push(mad(sample))
}
result
}
///|
pub fn sampling_bootstrap_interval(
data : Array[Double],
replicates : Int,
confidence : Double,
seed : Int,
) -> BootstrapInterval {
let estimates = sampling_bootstrap_means(data, replicates, seed)
let confidence_value = if confidence <= 0.0 {
0.5
} else if confidence >= 1.0 {
0.999
} else {
confidence
}
let tail = (1.0 - confidence_value) / 2.0
{
estimate: mean(data),
lower: quantile(estimates, tail),
upper: quantile(estimates, 1.0 - tail),
confidence: confidence_value,
replicates: estimates.length(),
}
}
///|
pub fn sampling_jackknife(data : Array[Double]) -> Array[Array[Double]] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
let sample = []
for cursor = 0; cursor < data.length(); cursor = cursor + 1 {
if cursor != index {
sample.push(data[cursor])
}
}
result.push(sample)
}
result
}
///|
pub fn sampling_jackknife_means(data : Array[Double]) -> Array[Double] {
let result = []
for sample in sampling_jackknife(data) {
result.push(mean(sample))
}
result
}
///|
pub fn sampling_jackknife_medians(data : Array[Double]) -> Array[Double] {
let result = []
for sample in sampling_jackknife(data) {
result.push(median(sample))
}
result
}
///|
pub fn sampling_permutation(data : Array[Double], seed : Int) -> Array[Double] {
sampling_shuffle(data, seed)
}
///|
pub fn sampling_permutation_difference(
left : Array[Double],
right : Array[Double],
permutations : Int,
seed : Int,
) -> Array[Double] {
let observed = mean(left) - mean(right)
let combined = []
for value in left {
combined.push(value)
}
for value in right {
combined.push(value)
}
let result = []
let left_count = left.length()
for iteration = 0; iteration < permutations; iteration = iteration + 1 {
let shuffled = sampling_shuffle(combined, seed + iteration)
let first = []
let second = []
for index = 0; index < shuffled.length(); index = index + 1 {
if index < left_count {
first.push(shuffled[index])
} else {
second.push(shuffled[index])
}
}
result.push(mean(first) - mean(second) - observed)
}
result
}
///|
pub fn sampling_permutation_p_value(
left : Array[Double],
right : Array[Double],
permutations : Int,
seed : Int,
) -> Double {
let observed = abs_double(mean(left) - mean(right))
let differences = sampling_permutation_difference(
left, right, permutations, seed,
)
if differences.length() == 0 {
return 0.0
}
let mut extreme = 0
for difference in differences {
if abs_double(difference) >= observed {
extreme += 1
}
}
(extreme + 1).to_double() / (differences.length() + 1).to_double()
}
///|
pub fn sampling_partition_indices(
length : Int,
rule : SamplingRule,
) -> DataPartition {
let shuffled = sampling_shuffle_indices(length, rule.seed)
let train_count = (length.to_double() * rule.train_fraction).to_int()
let validation_count = (length.to_double() * rule.validation_fraction).to_int()
let train_indices = []
let validation_indices = []
let test_indices = []
for position = 0; position < shuffled.length(); position = position + 1 {
if position < train_count {
train_indices.push(shuffled[position])
} else if position < train_count + validation_count {
validation_indices.push(shuffled[position])
} else {
test_indices.push(shuffled[position])
}
}
{
train: [],
validation: [],
test_set: [],
train_indices,
validation_indices,
test_indices,
}
}
///|
pub fn sampling_partition(
data : Array[Double],
rule : SamplingRule,
) -> DataPartition {
let indices = sampling_partition_indices(data.length(), rule)
{
train: sampling_take_indices(data, indices.train_indices),
validation: sampling_take_indices(data, indices.validation_indices),
test_set: sampling_take_indices(data, indices.test_indices),
train_indices: indices.train_indices,
validation_indices: indices.validation_indices,
test_indices: indices.test_indices,
}
}
///|
pub fn sampling_partition_sizes(
data : Array[Double],
rule : SamplingRule,
) -> Array[Int] {
let partition = sampling_partition(data, rule)
[
partition.train.length(),
partition.validation.length(),
partition.test_set.length(),
]
}
///|
pub fn sampling_kfold_indices(
length : Int,
folds : Int,
seed : Int,
) -> Array[Array[Int]] {
let result = []
let count = if folds < 2 { 2 } else { folds }
let shuffled = sampling_shuffle_indices(length, seed)
for fold = 0; fold < count; fold = fold + 1 {
let indices = []
for position = fold
position < shuffled.length()
position = position + count {
indices.push(shuffled[position])
}
result.push(indices)
}
result
}
///|
pub fn sampling_kfold(
data : Array[Double],
folds : Int,
seed : Int,
) -> Array[DataFold] {
let result = []
let groups = sampling_kfold_indices(data.length(), folds, seed)
for fold = 0; fold < groups.length(); fold = fold + 1 {
let holdout_indices = groups[fold]
let train_indices = []
let is_holdout = []
for _ in 0..= 0 && index < is_holdout.length() {
is_holdout[index] = true
}
}
for index = 0; index < data.length(); index = index + 1 {
if !is_holdout[index] {
train_indices.push(index)
}
}
result.push({
fold,
train: sampling_take_indices(data, train_indices),
holdout: sampling_take_indices(data, holdout_indices),
train_indices,
holdout_indices,
})
}
result
}
///|
pub fn sampling_fold_scores(
data : Array[Double],
folds : Int,
seed : Int,
) -> Array[Double] {
let result = []
for fold in sampling_kfold(data, folds, seed) {
result.push(abs_double(mean(fold.train) - mean(fold.holdout)))
}
result
}
///|
pub fn sampling_fold_robust_scores(
data : Array[Double],
folds : Int,
seed : Int,
) -> Array[Double] {
let result = []
for fold in sampling_kfold(data, folds, seed) {
result.push(abs_double(median(fold.train) - median(fold.holdout)))
}
result
}
///|
pub fn sampling_fold_stability(
data : Array[Double],
folds : Int,
seed : Int,
) -> Double {
let scores = sampling_fold_robust_scores(data, folds, seed)
if scores.length() == 0 {
0.0
} else {
1.0 / (1.0 + mean(scores))
}
}
///|
pub fn sampling_stratified_indices(
labels : Array[Int],
fraction : Double,
seed : Int,
) -> Array[Int] {
let result = []
let groups = []
for label in labels {
if !groups.contains(label) {
groups.push(label)
}
}
for group_index = 0
group_index < groups.length()
group_index = group_index + 1 {
let group_indices = []
for index = 0; index < labels.length(); index = index + 1 {
if labels[index] == groups[group_index] {
group_indices.push(index)
}
}
let shuffled = sampling_shuffle_indices(
group_indices.length(),
seed + group_index,
)
let count = (group_indices.length().to_double() * fraction).to_int()
for position = 0
position < count && position < shuffled.length()
position = position + 1 {
result.push(group_indices[shuffled[position]])
}
}
result.sort()
result
}
///|
pub fn sampling_stratified_values(
data : Array[Double],
labels : Array[Int],
fraction : Double,
seed : Int,
) -> Array[Double] {
if data.length() != labels.length() {
return []
}
sampling_take_indices(
data,
sampling_stratified_indices(labels, fraction, seed),
)
}
///|
pub fn sampling_repeated_subsamples(
data : Array[Double],
count : Int,
size : Int,
seed : Int,
) -> Array[Array[Double]] {
let result = []
for index = 0; index < count; index = index + 1 {
result.push(sampling_without_replacement(data, size, seed + index))
}
result
}
///|
pub fn sampling_subsample_means(
data : Array[Double],
count : Int,
size : Int,
seed : Int,
) -> Array[Double] {
let result = []
for sample in sampling_repeated_subsamples(data, count, size, seed) {
result.push(mean(sample))
}
result
}
///|
pub fn sampling_subsample_medians(
data : Array[Double],
count : Int,
size : Int,
seed : Int,
) -> Array[Double] {
let result = []
for sample in sampling_repeated_subsamples(data, count, size, seed) {
result.push(median(sample))
}
result
}
///|
pub fn sampling_stability_score(
data : Array[Double],
count : Int,
size : Int,
seed : Int,
) -> Double {
let estimates = sampling_subsample_medians(data, count, size, seed)
if estimates.length() == 0 {
0.0
} else {
1.0 / (1.0 + mad(estimates))
}
}
///|
pub fn sampling_seed_sequence(seed : Int, length : Int) -> Array[Int] {
let result = []
let mut state = seed
for _ in 0.. Bool {
sampling_with_replacement(data, count, seed) ==
sampling_with_replacement(data, count, seed)
}
///|
pub fn sampling_effective_size(weights : Array[Double]) -> Double {
let total = sum_absolute(weights)
if total == 0.0 {
0.0
} else {
total * total / sum_squared(weights)
}
}
///|
pub fn sampling_weighted_indices(
weights : Array[Double],
count : Int,
seed : Int,
) -> Array[Int] {
let result = []
if weights.length() == 0 || count <= 0 {
return result
}
let total = sum_absolute(weights)
if total == 0.0 {
return result
}
let rng = DeterministicRng::new(seed)
for _ in 0.. Array[Double] {
sampling_take_indices(data, sampling_weighted_indices(weights, count, seed))
}
///|
pub fn sampling_mean_interval(
data : Array[Double],
count : Int,
seed : Int,
) -> Array[Double] {
let samples = sampling_subsample_means(data, count, data.length(), seed)
[quantile(samples, 0.025), quantile(samples, 0.975)]
}
///|
pub fn sampling_median_interval(
data : Array[Double],
count : Int,
seed : Int,
) -> Array[Double] {
let samples = sampling_subsample_medians(data, count, data.length(), seed)
[quantile(samples, 0.025), quantile(samples, 0.975)]
}
///|
pub fn sampling_outlier_resistant_split(
data : Array[Double],
rule : SamplingRule,
) -> DataPartition {
let shuffled = sampling_partition(data, rule)
let train = quality_sanitize(shuffled.train, quality_default_rule())
let validation = quality_sanitize(shuffled.validation, quality_default_rule())
let test_set = quality_sanitize(shuffled.test_set, quality_default_rule())
{
train,
validation,
test_set,
train_indices: shuffled.train_indices,
validation_indices: shuffled.validation_indices,
test_indices: shuffled.test_indices,
}
}
///|
pub fn sampling_partition_quality(partition : DataPartition) -> Array[Double] {
[
robust_signal_quality(partition.train),
robust_signal_quality(partition.validation),
robust_signal_quality(partition.test_set),
duplicate_fraction(partition.train),
duplicate_fraction(partition.validation),
duplicate_fraction(partition.test_set),
]
}