///|
pub fn z_scores(data : Array[Double]) -> Array[Double] {
let center = mean(data)
let scale = population_stddev(data)
let result = []
for value in data {
result.push(if scale == 0.0 { 0.0 } else { (value - center) / scale })
}
result
}
///|
pub fn standardize(data : Array[Double]) -> Array[Double] {
z_scores(data)
}
///|
pub fn robust_standardize(data : Array[Double]) -> Array[Double] {
let center = median(data)
let scale = mad(data)
let result = []
for value in data {
result.push(robust_z_score(value, center, scale))
}
result
}
///|
pub fn min_max_scale(
data : Array[Double],
lower? : Double = 0.0,
upper? : Double = 1.0,
) -> Array[Double] {
if lower >= upper {
abort("lower bound must be less than upper bound")
}
if data.length() == 0 {
return []
}
let minimum = min_value(data)
let maximum = max_value(data)
let result = []
for value in data {
if maximum == minimum {
result.push((lower + upper) / 2.0)
} else {
result.push(
lower + (value - minimum) / (maximum - minimum) * (upper - lower),
)
}
}
result
}
///|
pub fn robust_min_max_scale(
data : Array[Double],
lower? : Double = 0.0,
upper? : Double = 1.0,
) -> Array[Double] {
if lower >= upper {
abort("lower bound must be less than upper bound")
}
if data.length() == 0 {
return []
}
let low = quantile(data, 0.05)
let high = quantile(data, 0.95)
let clipped = []
for value in data {
clipped.push(clamp_double(value, low, high))
}
min_max_scale(clipped, lower~, upper~)
}
///|
pub fn l1_normalize(data : Array[Double]) -> Array[Double] {
let scale = sum_absolute(data)
let result = []
for value in data {
result.push(if scale == 0.0 { 0.0 } else { value / scale })
}
result
}
///|
pub fn l2_normalize(data : Array[Double]) -> Array[Double] {
let scale = sum_squared(data).sqrt()
let result = []
for value in data {
result.push(if scale == 0.0 { 0.0 } else { value / scale })
}
result
}
///|
pub fn center_by_median(data : Array[Double]) -> Array[Double] {
let center = median(data)
let result = []
for value in data {
result.push(value - center)
}
result
}
///|
pub fn center_by_mean(data : Array[Double]) -> Array[Double] {
let center = mean(data)
let result = []
for value in data {
result.push(value - center)
}
result
}
///|
pub fn scale_by_mad(data : Array[Double]) -> Array[Double] {
let scale = mad(data)
let result = []
for value in data {
result.push(if scale == 0.0 { 0.0 } else { value / scale })
}
result
}
///|
pub fn robust_center_scale(data : Array[Double]) -> Array[Double] {
[median(data), mad(data)]
}
///|
pub fn quantile_transform(
data : Array[Double],
output_lower? : Double = 0.0,
output_upper? : Double = 1.0,
) -> Array[Double] {
if output_lower >= output_upper {
abort("output_lower must be less than output_upper")
}
let result = []
for value in data {
let probability = empirical_cdf(data, value)
result.push(output_lower + probability * (output_upper - output_lower))
}
result
}
///|
pub fn rank_normalize(data : Array[Double]) -> Array[Double] {
if data.length() == 0 {
return []
}
let denominator = if data.length() == 1 {
1.0
} else {
(data.length() - 1).to_double()
}
let result = []
for rank in ranks(data) {
result.push((rank - 1.0) / denominator)
}
result
}
///|
pub fn clip_range(
data : Array[Double],
lower : Double,
upper : Double,
) -> Array[Double] {
if lower > upper {
abort("lower must not exceed upper")
}
let result = []
for value in data {
result.push(clamp_double(value, lower, upper))
}
result
}
///|
pub fn soft_clip(
data : Array[Double],
center : Double,
scale : Double,
threshold : Double,
) -> Array[Double] {
if scale <= 0.0 || threshold <= 0.0 {
abort("scale and threshold must be positive")
}
let limit = scale * threshold
let result = []
for value in data {
result.push(center + clamp_double(value - center, -limit, limit))
}
result
}
///|
pub fn normalize_against_reference(
data : Array[Double],
reference : Array[Double],
) -> Array[Double] {
let center = median(reference)
let scale = mad(reference)
let result = []
for value in data {
result.push(robust_z_score(value, center, scale))
}
result
}
///|
pub fn quantile_map(
data : Array[Double],
reference : Array[Double],
) -> Array[Double] {
if reference.length() == 0 {
return []
}
let result = []
for value in data {
result.push(quantile(reference, empirical_cdf(data, value)))
}
result
}
///|
pub fn difference_from_baseline(
data : Array[Double],
baseline : Double,
) -> Array[Double] {
let result = []
for value in data {
result.push(value - baseline)
}
result
}
///|
pub fn percent_change(data : Array[Double], baseline : Double) -> Array[Double] {
if baseline == 0.0 {
abort("baseline must not be zero")
}
let result = []
for value in data {
result.push((value - baseline) / abs_double(baseline))
}
result
}
///|
pub fn robust_percent_change(
data : Array[Double],
reference : Array[Double],
) -> Array[Double] {
let baseline = median(reference)
percent_change(data, if baseline == 0.0 { 1.0 } else { baseline })
}
///|
pub fn winsorized_standardize(
data : Array[Double],
trim_percent : Double,
) -> Array[Double] {
z_scores(winsorize(data, trim_percent))
}
///|
pub fn robust_clip_and_center(
data : Array[Double],
lower_probability : Double,
upper_probability : Double,
) -> Array[Double] {
center_by_median(
clip_by_quantiles(data, lower_probability, upper_probability),
)
}
///|
pub fn signed_log_scale(data : Array[Double]) -> Array[Double] {
let result = []
for value in data {
let magnitude = abs_double(value)
let transformed = if magnitude == 0.0 { 0.0 } else { magnitude.sqrt() }
result.push(sign_double(value) * transformed)
}
result
}
///|
pub fn rank_centered(data : Array[Double]) -> Array[Double] {
let result = []
let center = (data.length().to_double() + 1.0) / 2.0
for rank in ranks(data) {
result.push(rank - center)
}
result
}
///|
pub fn monotone_scale(data : Array[Double], factor : Double) -> Array[Double] {
if factor <= 0.0 {
abort("factor must be positive")
}
let result = []
for value in data {
result.push(value * factor)
}
result
}
///|
pub fn affine_transform(
data : Array[Double],
offset : Double,
factor : Double,
) -> Array[Double] {
let result = []
for value in data {
result.push(offset + factor * value)
}
result
}