///|
fn window_bounds(length : Int, index : Int, window : Int) -> (Int, Int) {
let radius = window / 2
let start = if index - radius < 0 { 0 } else { index - radius }
let end = if start + window > length { length } else { start + window }
let corrected_start = if end - start < window && end - window >= 0 {
end - window
} else {
start
}
(corrected_start, end)
}
///|
fn extract_window(
data : Array[Double],
index : Int,
window : Int,
) -> Array[Double] {
if window <= 0 {
abort("window must be positive")
}
let bounds = window_bounds(data.length(), index, window)
let result = []
for position = bounds.0; position < bounds.1; position = position + 1 {
result.push(data[position])
}
result
}
///|
pub fn rolling_mean(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(mean(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_median(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(median(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_mad(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(mad(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_quantile(
data : Array[Double],
window : Int,
probability : Double,
) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(quantile(extract_window(data, index, window), probability))
}
result
}
///|
pub fn rolling_trimmed_mean(
data : Array[Double],
window : Int,
trim_percent : Double,
) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(trimmed_mean(extract_window(data, index, window), trim_percent))
}
result
}
///|
pub fn rolling_winsorized_mean(
data : Array[Double],
window : Int,
trim_percent : Double,
) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(
winsorized_mean(extract_window(data, index, window), trim_percent),
)
}
result
}
///|
pub fn rolling_z_scores(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
let neighborhood = extract_window(data, index, window)
result.push(
robust_z_score(data[index], median(neighborhood), mad(neighborhood)),
)
}
result
}
///|
pub fn rolling_iqr(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(interquartile_range(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_min(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(min_value(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_max(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(max_value(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_range(data : Array[Double], window : Int) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(range(extract_window(data, index, window)))
}
result
}
///|
pub fn exponentially_weighted_mean(
data : Array[Double],
alpha : Double,
) -> Array[Double] {
if alpha <= 0.0 || alpha > 1.0 {
abort("alpha must be in (0, 1]")
}
let result = []
if data.length() == 0 {
return result
}
let mut state = data[0]
result.push(state)
for index = 1; index < data.length(); index = index + 1 {
state = alpha * data[index] + (1.0 - alpha) * state
result.push(state)
}
result
}
///|
pub fn robust_exponentially_weighted_mean(
data : Array[Double],
alpha : Double,
clip : Double,
) -> Array[Double] {
if clip <= 0.0 {
abort("clip must be positive")
}
if alpha <= 0.0 || alpha > 1.0 {
abort("alpha must be in (0, 1]")
}
let result = []
if data.length() == 0 {
return result
}
let mut state = data[0]
result.push(state)
for index = 1; index < data.length(); index = index + 1 {
let innovation = clamp_double(data[index] - state, -clip, clip)
state = state + alpha * innovation
result.push(state)
}
result
}
///|
pub fn rolling_huber_location(
data : Array[Double],
window : Int,
) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(huber_location(extract_window(data, index, window)))
}
result
}
///|
pub fn rolling_outlier_flags(
data : Array[Double],
window : Int,
threshold? : Double = 3.5,
) -> Array[Bool] {
if threshold <= 0.0 {
abort("threshold must be positive")
}
let scores = rolling_z_scores(data, window)
let result = []
for score in scores {
result.push(abs_double(score) > threshold)
}
result
}
///|
pub fn rolling_mad_residuals(
data : Array[Double],
window : Int,
) -> Array[Double] {
let centers = rolling_median(data, window)
let scales = rolling_mad(data, window)
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(robust_z_score(data[index], centers[index], scales[index]))
}
result
}
///|
pub fn centered_difference(data : Array[Double]) -> Array[Double] {
let result = []
for index = 0; index < data.length(); index = index + 1 {
if data.length() <= 1 {
result.push(0.0)
} else if index == 0 {
result.push(data[1] - data[0])
} else if index == data.length() - 1 {
result.push(data[index] - data[index - 1])
} else {
result.push((data[index + 1] - data[index - 1]) / 2.0)
}
}
result
}
///|
pub fn robust_detrend(data : Array[Double], window : Int) -> Array[Double] {
let baseline = rolling_huber_location(data, window)
let result = []
for index = 0; index < data.length(); index = index + 1 {
result.push(data[index] - baseline[index])
}
result
}
///|
pub fn rolling_covariance(
x : Array[Double],
y : Array[Double],
window : Int,
) -> Array[Double] {
if x.length() != y.length() {
return []
}
let result = []
for index = 0; index < x.length(); index = index + 1 {
result.push(
covariance(
extract_window(x, index, window),
extract_window(y, index, window),
),
)
}
result
}
///|
pub fn rolling_correlation(
x : Array[Double],
y : Array[Double],
window : Int,
) -> Array[Double] {
if x.length() != y.length() {
return []
}
let result = []
for index = 0; index < x.length(); index = index + 1 {
result.push(
pearson_correlation(
extract_window(x, index, window),
extract_window(y, index, window),
),
)
}
result
}
///|
pub fn rolling_mean_absolute_error(
actual : Array[Double],
predicted : Array[Double],
window : Int,
) -> Array[Double] {
if actual.length() != predicted.length() {
return []
}
let result = []
for index = 0; index < actual.length(); index = index + 1 {
result.push(
mean_absolute_error(
extract_window(actual, index, window),
extract_window(predicted, index, window),
),
)
}
result
}