///|
/// Robust aggregate for one window or segment.
pub struct AggregateSummary {
count : Int
mean_value : Double
median_value : Double
mad_value : Double
minimum : Double
maximum : Double
trimmed_value : Double
outlier_count : Int
quality : Double
}
///|
/// A collection of aligned segment aggregates.
pub struct AggregateTable {
starts : Array[Int]
ends : Array[Int]
summaries : Array[AggregateSummary]
}
///|
pub fn aggregate_summary(
data : Array[Double],
trim_percent : Double,
threshold : Double,
) -> AggregateSummary {
{
count: data.length(),
mean_value: mean(data),
median_value: median(data),
mad_value: mad(data),
minimum: min_value(data),
maximum: max_value(data),
trimmed_value: trimmed_mean(data, trim_percent),
outlier_count: outlier_indices_z(data, threshold~).length(),
quality: robust_signal_quality(data),
}
}
///|
pub fn aggregate_summary_vector(summary : AggregateSummary) -> Array[Double] {
[
summary.count.to_double(),
summary.mean_value,
summary.median_value,
summary.mad_value,
summary.minimum,
summary.maximum,
summary.trimmed_value,
summary.outlier_count.to_double(),
summary.quality,
]
}
///|
pub fn aggregate_summary_lines(summary : AggregateSummary) -> Array[String] {
[
"count=" + summary.count.to_string(),
"mean=" + summary.mean_value.to_string(),
"median=" + summary.median_value.to_string(),
"mad=" + summary.mad_value.to_string(),
"minimum=" + summary.minimum.to_string(),
"maximum=" + summary.maximum.to_string(),
"trimmed=" + summary.trimmed_value.to_string(),
"outliers=" + summary.outlier_count.to_string(),
"quality=" + summary.quality.to_string(),
]
}
///|
pub fn aggregate_summary_string(summary : AggregateSummary) -> String {
aggregate_summary_lines(summary).join("\n")
}
///|
pub fn aggregate_blocks(
data : Array[Double],
block_size : Int,
trim_percent : Double,
threshold : Double,
) -> AggregateTable {
let starts = []
let ends = []
let summaries = []
let width = if block_size < 1 { 1 } else { block_size }
let mut start = 0
while start < data.length() {
let end = if start + width > data.length() {
data.length()
} else {
start + width
}
let values = []
for index = start; index < end; index = index + 1 {
values.push(data[index])
}
starts.push(start)
ends.push(end)
summaries.push(aggregate_summary(values, trim_percent, threshold))
start = end
}
{ starts, ends, summaries }
}
///|
pub fn aggregate_table_means(table : AggregateTable) -> Array[Double] {
let result = []
for summary in table.summaries {
result.push(summary.mean_value)
}
result
}
///|
pub fn aggregate_table_medians(table : AggregateTable) -> Array[Double] {
let result = []
for summary in table.summaries {
result.push(summary.median_value)
}
result
}
///|
pub fn aggregate_table_scales(table : AggregateTable) -> Array[Double] {
let result = []
for summary in table.summaries {
result.push(summary.mad_value)
}
result
}
///|
pub fn aggregate_table_quality(table : AggregateTable) -> Double {
let values = []
for summary in table.summaries {
values.push(summary.quality)
}
if values.length() == 0 {
0.0
} else {
mean(values)
}
}
///|
pub fn aggregate_table_outlier_counts(table : AggregateTable) -> Array[Int] {
let result = []
for summary in table.summaries {
result.push(summary.outlier_count)
}
result
}
///|
pub fn aggregate_block_mean(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
aggregate_table_means(aggregate_blocks(data, block_size, 0.1, 3.5))
}
///|
pub fn aggregate_block_median(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
aggregate_table_medians(aggregate_blocks(data, block_size, 0.1, 3.5))
}
///|
pub fn aggregate_block_mad(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
aggregate_table_scales(aggregate_blocks(data, block_size, 0.1, 3.5))
}
///|
pub fn aggregate_block_trimmed(
data : Array[Double],
block_size : Int,
trim_percent : Double,
) -> Array[Double] {
let table = aggregate_blocks(data, block_size, trim_percent, 3.5)
let result = []
for summary in table.summaries {
result.push(summary.trimmed_value)
}
result
}
///|
pub fn aggregate_block_quality(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
let table = aggregate_blocks(data, block_size, 0.1, 3.5)
let result = []
for summary in table.summaries {
result.push(summary.quality)
}
result
}
///|
pub fn aggregate_block_ranges(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
let table = aggregate_blocks(data, block_size, 0.1, 3.5)
let result = []
for summary in table.summaries {
result.push(summary.maximum - summary.minimum)
}
result
}
///|
pub fn aggregate_block_shift(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
let values = aggregate_block_median(data, block_size)
let result = []
for index = 1; index < values.length(); index = index + 1 {
result.push(values[index] - values[index - 1])
}
result
}
///|
pub fn aggregate_block_outlier_rate(
data : Array[Double],
block_size : Int,
) -> Array[Double] {
let table = aggregate_blocks(data, block_size, 0.1, 3.5)
let result = []
for summary in table.summaries {
result.push(
if summary.count == 0 {
0.0
} else {
summary.outlier_count.to_double() / summary.count.to_double()
},
)
}
result
}
///|
pub fn aggregate_equal_width(
data : Array[Double],
bins : Int,
) -> Array[Array[Double]] {
let edges = histogram_edges(data, if bins < 1 { 1 } else { bins })
let result = []
for _ in 0..<(edges.length() - 1) {
result.push([])
}
for value in data {
if result.length() > 0 {
let index = drift_bin_index(value, edges)
result[index].push(value)
}
}
result
}
///|
pub fn aggregate_bin_means(data : Array[Double], bins : Int) -> Array[Double] {
let result = []
for group in aggregate_equal_width(data, bins) {
result.push(mean(group))
}
result
}
///|
pub fn aggregate_bin_medians(data : Array[Double], bins : Int) -> Array[Double] {
let result = []
for group in aggregate_equal_width(data, bins) {
result.push(median(group))
}
result
}
///|
pub fn aggregate_bin_scales(data : Array[Double], bins : Int) -> Array[Double] {
let result = []
for group in aggregate_equal_width(data, bins) {
result.push(mad(group))
}
result
}
///|
pub fn aggregate_bin_counts(data : Array[Double], bins : Int) -> Array[Int] {
let result = []
for group in aggregate_equal_width(data, bins) {
result.push(group.length())
}
result
}
///|
pub fn aggregate_quantile_bins(
data : Array[Double],
bins : Int,
) -> Array[Array[Double]] {
aggregate_by_quantile(data, if bins < 1 { 1 } else { bins })
}
///|
pub fn aggregate_quantile_means(
data : Array[Double],
bins : Int,
) -> Array[Double] {
quantile_group_means(data, if bins < 1 { 1 } else { bins })
}
///|
pub fn aggregate_quantile_scales(
data : Array[Double],
bins : Int,
) -> Array[Double] {
quantile_group_mads(data, if bins < 1 { 1 } else { bins })
}
///|
pub fn aggregate_weighted_summary(
data : Array[Double],
weights : Array[Double],
trim_percent : Double,
) -> Array[Double] {
[
weighted_mean(data, weights),
weighted_median(data, weights),
weighted_variance(data, weights),
weighted_iqr(data, weights),
trimmed_mean(data, trim_percent),
]
}
///|
pub fn aggregate_cumulative_summary(
data : Array[Double],
) -> Array[Array[Double]] {
[
cumulative_mean(data),
cumulative_median(data),
cumulative_mad(data),
cumulative_outlier_rate(data),
]
}
///|
pub fn aggregate_exponential_summary(
data : Array[Double],
alpha : Double,
) -> Array[Array[Double]] {
[
exponentially_weighted_mean(data, alpha),
robust_exponentially_weighted_mean(data, alpha, 3.5),
]
}
///|
pub fn aggregate_running_summary(data : Array[Double]) -> Array[Array[Double]] {
[
running_minimum(data),
running_maximum(data),
running_range(data),
cumulative_sum(data),
]
}
///|
pub fn aggregate_downsample_mean(
data : Array[Double],
target_size : Int,
) -> Array[Double] {
let count = if target_size < 1 { 1 } else { target_size }
if data.length() <= count {
return data.copy()
}
let result = []
for bucket = 0; bucket < count; bucket = bucket + 1 {
let start = bucket * data.length() / count
let end = (bucket + 1) * data.length() / count
let values = []
for index = start; index < end; index = index + 1 {
values.push(data[index])
}
result.push(mean(values))
}
result
}
///|
pub fn aggregate_downsample_median(
data : Array[Double],
target_size : Int,
) -> Array[Double] {
let count = if target_size < 1 { 1 } else { target_size }
if data.length() <= count {
return data.copy()
}
let result = []
for bucket = 0; bucket < count; bucket = bucket + 1 {
let start = bucket * data.length() / count
let end = (bucket + 1) * data.length() / count
let values = []
for index = start; index < end; index = index + 1 {
values.push(data[index])
}
result.push(median(values))
}
result
}
///|
pub fn aggregate_downsample_trimmed(
data : Array[Double],
target_size : Int,
trim_percent : Double,
) -> Array[Double] {
let count = if target_size < 1 { 1 } else { target_size }
if data.length() <= count {
return data.copy()
}
let result = []
for bucket = 0; bucket < count; bucket = bucket + 1 {
let start = bucket * data.length() / count
let end = (bucket + 1) * data.length() / count
let values = []
for index = start; index < end; index = index + 1 {
values.push(data[index])
}
result.push(trimmed_mean(values, trim_percent))
}
result
}
///|
pub fn aggregate_downsample_quality(
data : Array[Double],
target_size : Int,
) -> Array[Double] {
let count = if target_size < 1 { 1 } else { target_size }
let result = []
for bucket = 0; bucket < count && bucket < data.length(); bucket = bucket + 1 {
let start = bucket * data.length() / count
let end = (bucket + 1) * data.length() / count
let values = []
for index = start; index < end; index = index + 1 {
values.push(data[index])
}
result.push(robust_signal_quality(values))
}
result
}
///|
pub fn aggregate_window_summary(
data : Array[Double],
window : Int,
trim_percent : Double,
threshold : Double,
) -> Array[AggregateSummary] {
let result = []
let width = if window < 1 { 1 } else { window }
for end = 1; end <= data.length(); end = end + 1 {
let start = if end < width { 0 } else { end - width }
let values = []
for index = start; index < end; index = index + 1 {
values.push(data[index])
}
result.push(aggregate_summary(values, trim_percent, threshold))
}
result
}
///|
pub fn aggregate_window_quality(
data : Array[Double],
window : Int,
) -> Array[Double] {
let result = []
for summary in aggregate_window_summary(data, window, 0.1, 3.5) {
result.push(summary.quality)
}
result
}
///|
pub fn aggregate_window_outliers(
data : Array[Double],
window : Int,
) -> Array[Int] {
let result = []
for summary in aggregate_window_summary(data, window, 0.1, 3.5) {
result.push(summary.outlier_count)
}
result
}
///|
pub fn aggregate_window_center_shift(
data : Array[Double],
window : Int,
) -> Array[Double] {
let result = []
let summaries = aggregate_window_summary(data, window, 0.1, 3.5)
for index = 1; index < summaries.length(); index = index + 1 {
result.push(
summaries[index].median_value - summaries[index - 1].median_value,
)
}
result
}
///|
pub fn aggregate_window_scale_shift(
data : Array[Double],
window : Int,
) -> Array[Double] {
let result = []
let summaries = aggregate_window_summary(data, window, 0.1, 3.5)
for index = 1; index < summaries.length(); index = index + 1 {
result.push(summaries[index].mad_value - summaries[index - 1].mad_value)
}
result
}
///|
pub fn aggregate_window_alerts(
data : Array[Double],
window : Int,
threshold : Double,
) -> Array[Bool] {
let result = []
for summary in aggregate_window_summary(data, window, 0.1, threshold) {
result.push(summary.outlier_count > 0)
}
result
}
///|
pub fn aggregate_compare_blocks(
data : Array[Double],
block_size : Int,
) -> Array[Array[Double]] {
let table = aggregate_blocks(data, block_size, 0.1, 3.5)
let result = []
for summary in table.summaries {
result.push([
summary.mean_value,
summary.median_value,
summary.mad_value,
summary.trimmed_value,
summary.quality,
])
}
result
}