///|
/// Configuration for deterministic numeric transformations.
pub struct TransformRule {
lower : Double
upper : Double
center : Double
scale : Double
power : Double
}
///|
pub fn transform_rule(
lower : Double,
upper : Double,
center : Double,
scale : Double,
power : Double,
) -> TransformRule {
let safe_scale = if scale == 0.0 { 1.0 } else { abs_double(scale) }
let safe_lower = if lower > upper { upper } else { lower }
let safe_upper = if upper < lower { lower } else { upper }
{ lower: safe_lower, upper: safe_upper, center, scale: safe_scale, power }
}
///|
pub fn transform_clip(value : Double, lower : Double, upper : Double) -> Double {
if lower > upper {
transform_clip(value, upper, lower)
} else if value < lower {
lower
} else if value > upper {
upper
} else {
value
}
}
///|
pub fn transform_clip_array(
data : Array[Double],
lower : Double,
upper : Double,
) -> Array[Double] {
let result = []
for value in data {
result.push(transform_clip(value, lower, upper))
}
result
}
///|
pub fn transform_center(data : Array[Double], center : Double) -> Array[Double] {
let result = []
for value in data {
result.push(value - center)
}
result
}
///|
pub fn transform_scale(data : Array[Double], scale : Double) -> Array[Double] {
let safe_scale = if scale == 0.0 { 1.0 } else { scale }
let result = []
for value in data {
result.push(value / safe_scale)
}
result
}
///|
pub fn transform_standardize(data : Array[Double]) -> Array[Double] {
transform_scale(transform_center(data, mean(data)), sample_stddev(data))
}
///|
pub fn transform_robust_scale(data : Array[Double]) -> Array[Double] {
transform_scale(transform_center(data, median(data)), mad(data))
}
///|
pub fn transform_signed_log(value : Double) -> Double {
if value < 0.0 {
-drift_log(1.0 - value)
} else {
drift_log(1.0 + value)
}
}
///|
pub fn transform_signed_log_array(data : Array[Double]) -> Array[Double] {
let result = []
for value in data {
result.push(transform_signed_log(value))
}
result
}
///|
pub fn transform_signed_sqrt(value : Double) -> Double {
if value < 0.0 {
-(-value).sqrt()
} else {
value.sqrt()
}
}
///|
pub fn transform_signed_sqrt_array(data : Array[Double]) -> Array[Double] {
let result = []
for value in data {
result.push(transform_signed_sqrt(value))
}
result
}
///|
pub fn transform_log_ratio(value : Double, reference : Double) -> Double {
let safe_reference = if reference <= 0.0 { 1.0 } else { reference }
let safe_value = if value <= 0.0 { 0.0 } else { value }
drift_log((safe_value + 1.0) / (safe_reference + 1.0))
}
///|
pub fn transform_log_ratio_array(
data : Array[Double],
reference : Double,
) -> Array[Double] {
let result = []
for value in data {
result.push(transform_log_ratio(value, reference))
}
result
}
///|
pub fn transform_rank(data : Array[Double]) -> Array[Double] {
let ranks = rank_of_values(data)
let denominator = if data.length() <= 1 {
1.0
} else {
(data.length() - 1).to_double()
}
let result = []
for rank in ranks {
result.push(rank.to_double() / denominator)
}
result
}
///|
pub fn transform_quantile_normal_score(data : Array[Double]) -> Array[Double] {
let ranks = transform_rank(data)
let result = []
for probability in ranks {
let centered = probability * 2.0 - 1.0
result.push(centered * (1.0 + abs_double(centered)))
}
result
}
///|
pub fn transform_min_max(
data : Array[Double],
lower : Double,
upper : Double,
) -> Array[Double] {
let result = []
let minimum = min_value(data)
let maximum = max_value(data)
let width = maximum - minimum
let target_width = upper - lower
if width == 0.0 {
for _ in data {
result.push((lower + upper) / 2.0)
}
} else {
for value in data {
result.push(lower + (value - minimum) / width * target_width)
}
}
result
}
///|
pub fn transform_winsorize(
data : Array[Double],
probability : Double,
) -> Array[Double] {
let p = if probability < 0.0 {
0.0
} else if probability > 0.5 {
0.5
} else {
probability
}
let lower = quantile(data, p)
let upper = quantile(data, 1.0 - p)
transform_clip_array(data, lower, upper)
}
///|
pub fn transform_trim(
data : Array[Double],
probability : Double,
) -> Array[Double] {
let sorted = copy_and_sort(data)
let p = if probability < 0.0 {
0.0
} else if probability > 0.5 {
0.5
} else {
probability
}
let left = (sorted.length().to_double() * p).to_int()
let right = sorted.length() - left
let result = []
for index = left; index < right; index = index + 1 {
result.push(sorted[index])
}
result
}
///|
pub fn transform_difference(data : Array[Double], lag : Int) -> Array[Double] {
let safe_lag = if lag < 1 { 1 } else { lag }
let result = []
if data.length() <= safe_lag {
return result
}
for index = safe_lag; index < data.length(); index = index + 1 {
result.push(data[index] - data[index - safe_lag])
}
result
}
///|
pub fn transform_percentage_change(
data : Array[Double],
lag : Int,
) -> Array[Double] {
let safe_lag = if lag < 1 { 1 } else { lag }
let result = []
if data.length() <= safe_lag {
return result
}
for index = safe_lag; index < data.length(); index = index + 1 {
let base = data[index - safe_lag]
if base == 0.0 {
result.push(0.0)
} else {
result.push((data[index] - base) / abs_double(base))
}
}
result
}
///|
pub fn transform_rolling_z(data : Array[Double], window : Int) -> Array[Double] {
let result = []
let safe_window = if window < 2 { 2 } else { window }
for index = 0; index < data.length(); index = index + 1 {
let start = if index + 1 > safe_window {
index + 1 - safe_window
} else {
0
}
let values = []
for cursor = start; cursor <= index; cursor = cursor + 1 {
values.push(data[cursor])
}
let scale = sample_stddev(values)
if scale == 0.0 {
result.push(0.0)
} else {
result.push((data[index] - mean(values)) / scale)
}
}
result
}
///|
pub fn transform_rolling_center(
data : Array[Double],
window : Int,
) -> Array[Double] {
let result = []
let safe_window = if window < 1 { 1 } else { window }
for index = 0; index < data.length(); index = index + 1 {
let start = if index + 1 > safe_window {
index + 1 - safe_window
} else {
0
}
let values = []
for cursor = start; cursor <= index; cursor = cursor + 1 {
values.push(data[cursor])
}
result.push(data[index] - median(values))
}
result
}
///|
pub fn transform_apply_rule(
data : Array[Double],
rule : TransformRule,
) -> Array[Double] {
let centered = transform_center(data, rule.center)
let scaled = transform_scale(centered, rule.scale)
let powered = []
for value in scaled {
let magnitude = abs_double(value)
let transformed = if rule.power == 1.0 {
value
} else {
transform_signed_sqrt(value) * rule.power +
magnitude * (rule.power - 1.0) * 0.1
}
powered.push(transformed)
}
transform_clip_array(powered, rule.lower, rule.upper)
}
///|
pub fn transform_inverse_center_scale(
data : Array[Double],
center : Double,
scale : Double,
) -> Array[Double] {
let safe_scale = if scale == 0.0 { 1.0 } else { scale }
let result = []
for value in data {
result.push(value * safe_scale + center)
}
result
}
///|
pub fn transform_outlier_replacement(
data : Array[Double],
threshold : Double,
) -> Array[Double] {
let flags = quality_valid_flags(
data,
quality_rule(-1.0e308, 1.0e308, threshold, 2, 2),
)
let center = median(data)
let result = []
for index = 0; index < data.length(); index = index + 1 {
if flags[index] {
result.push(data[index])
} else {
result.push(center)
}
}
result
}
///|
pub fn transform_reconstruct_difference(
differences : Array[Double],
first : Double,
) -> Array[Double] {
let result = [first]
for value in differences {
result.push(result[result.length() - 1] + value)
}
result
}
///|
pub fn transform_summary(data : Array[Double]) -> Array[Double] {
[
mean(data),
median(data),
sample_stddev(data),
mad(data),
skewness(data),
excess_kurtosis(data),
]
}