///|
pub(all) struct AggregateBin {
index : Int
lower : Double
upper : Double
count : Int
mean : Double
minimum : Double
maximum : Double
standard_deviation : Double
} derive(Debug, Eq, ToJson)
///|
pub(all) struct GroupSummary {
key : String
count : Int
mean : Double
weighted_mean : Double
minimum : Double
maximum : Double
total : Double
} derive(Debug, Eq, ToJson)
///|
pub(all) struct Histogram {
bins : Array[AggregateBin]
total_count : Int
underflow : Int
overflow : Int
} derive(Debug, Eq, ToJson)
///|
pub fn bin_index(
value : Double,
lower : Double,
upper : Double,
bins : Int,
) -> Int {
if bins < 1 || upper <= lower {
return -1
}
if value < lower {
-1
} else if value >= upper {
bins
} else {
((value - lower) / (upper - lower) * bins.to_double())
.to_int()
.min(bins - 1)
}
}
///|
pub fn histogram(
values : ArrayView[Double],
lower : Double,
upper : Double,
bins : Int,
) -> Histogram raise @domain.ExtractionError {
if values.length() == 0 || bins < 1 || upper <= lower {
raise @domain.ExtractionError::InvalidArgument(
message="histogram inputs are invalid",
)
}
let counts = Array::make(bins, 0)
let totals = Array::make(bins, 0.0)
let squares = Array::make(bins, 0.0)
let minimums = Array::make(bins, 1.0e30)
let maximums = Array::make(bins, -1.0e30)
let mut underflow = 0
let mut overflow = 0
for value in values {
let index = bin_index(value, lower, upper, bins)
if index < 0 {
underflow += 1
} else if index >= bins {
overflow += 1
} else {
counts[index] += 1
totals[index] += value
squares[index] += value * value
minimums[index] = minimums[index].min(value)
maximums[index] = maximums[index].max(value)
}
}
let result = []
let width = (upper - lower) / bins.to_double()
for index in 0.. Array[Double] {
let densities = []
for bin in histogram.bins {
let width = bin.upper - bin.lower
densities.push(
if width == 0.0 {
0.0
} else {
bin.count.to_double() / histogram.total_count.to_double() / width
},
)
}
densities
}
///|
pub fn histogram_quantile(
histogram : Histogram,
probability : Double,
) -> Double raise @domain.ExtractionError {
if histogram.total_count == 0 || probability < 0.0 || probability > 1.0 {
raise @domain.ExtractionError::InvalidArgument(
message="histogram quantile is invalid",
)
}
let target = probability * histogram.total_count.to_double()
let mut cumulative = histogram.underflow.to_double()
for bin in histogram.bins {
cumulative += bin.count.to_double()
if cumulative >= target {
return (bin.lower + bin.upper) / 2.0
}
}
histogram.bins[histogram.bins.length() - 1].upper
}
///|
pub fn cumulative_counts(histogram : Histogram) -> Array[Int] {
let result = []
let mut cumulative = histogram.underflow
for bin in histogram.bins {
cumulative += bin.count
result.push(cumulative)
}
result
}
///|
pub fn group_by_label(
labels : ArrayView[String],
values : ArrayView[Double],
) -> Array[GroupSummary] raise @domain.ExtractionError {
if labels.length() != values.length() || labels.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="group arrays are invalid",
)
}
let keys = []
let totals = []
let counts = []
let minimums = []
let maximums = []
for index in 0.. Array[GroupSummary] raise @domain.ExtractionError {
if labels.length() != values.length() ||
labels.length() != weights.length() ||
labels.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="weighted group arrays are invalid",
)
}
let keys = []
let totals = []
let weighted_totals = []
let weights_total = []
let counts = []
let minimums = []
let maximums = []
for index in 0.. String {
let lines = ["key,count,mean,weighted_mean,minimum,maximum,total"]
for group in groups {
lines.push(
group.key +
"," +
group.count.to_string() +
"," +
group.mean.to_string() +
"," +
group.weighted_mean.to_string() +
"," +
group.minimum.to_string() +
"," +
group.maximum.to_string() +
"," +
group.total.to_string(),
)
}
lines.join("\n")
}
///|
pub fn group_summary_markdown(groups : ArrayView[GroupSummary]) -> String {
let lines = [
"| Group | Count | Mean | Weighted mean | Minimum | Maximum | Total |", "|---|---:|---:|---:|---:|---:|---:|",
]
for group in groups {
lines.push(
"| " +
group.key +
" | " +
group.count.to_string() +
" | " +
group.mean.to_string() +
" | " +
group.weighted_mean.to_string() +
" | " +
group.minimum.to_string() +
" | " +
group.maximum.to_string() +
" | " +
group.total.to_string() +
" |",
)
}
lines.join("\n")
}
///|
pub fn aggregate_mean(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
values.fold(init=0.0, (sum, value) => sum + value) /
values.length().to_double()
}
}
///|
pub fn aggregate_minimum(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
let mut result = values[0]
for value in values {
result = result.min(value)
}
result
}
}
///|
pub fn aggregate_maximum(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
let mut result = values[0]
for value in values {
result = result.max(value)
}
result
}
}
///|
pub fn aggregate_range(values : ArrayView[Double]) -> (Double, Double) {
(aggregate_minimum(values), aggregate_maximum(values))
}
///|
pub fn aggregate_variance(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
let mean = aggregate_mean(values)
let mut total = 0.0
for value in values {
let difference = value - mean
total += difference * difference
}
total / values.length().to_double()
}
}
///|
pub fn aggregate_standard_deviation(values : ArrayView[Double]) -> Double {
aggregate_variance(values).sqrt()
}
///|
pub fn aggregate_median(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
let ordered = values.to_owned()
ordered.sort()
let middle = ordered.length() / 2
if ordered.length() % 2 == 0 {
(ordered[middle - 1] + ordered[middle]) / 2.0
} else {
ordered[middle]
}
}
}
///|
pub fn aggregate_mad(values : ArrayView[Double]) -> Double {
let median = aggregate_median(values)
let deviations = [ for value in values => (value - median).abs() ]
aggregate_median(deviations)
}
///|
pub fn aggregate_cv(values : ArrayView[Double]) -> Double {
let mean = aggregate_mean(values)
if mean == 0.0 {
0.0
} else {
aggregate_standard_deviation(values) / mean.abs()
}
}
///|
pub fn aggregate_z_scores(values : ArrayView[Double]) -> Array[Double] {
let mean = aggregate_mean(values)
let deviation = aggregate_standard_deviation(values)
if deviation == 0.0 {
Array::make(values.length(), 0.0)
} else {
[
for value in values => (value - mean) / deviation
]
}
}
///|
pub fn aggregate_trimmed_mean(
values : ArrayView[Double],
trim_fraction : Double,
) -> Double raise @domain.ExtractionError {
if trim_fraction < 0.0 || trim_fraction >= 0.5 {
raise @domain.ExtractionError::InvalidFraction(
name="trim fraction",
value=trim_fraction,
)
}
let ordered = values.to_owned()
ordered.sort()
let trim = (ordered.length().to_double() * trim_fraction).to_int()
if ordered.length() <= 2 * trim {
0.0
} else {
let mut total = 0.0
for index in trim..<(ordered.length() - trim) {
total += ordered[index]
}
total / (ordered.length() - 2 * trim).to_double()
}
}
///|
pub fn aggregate_winsorized_mean(
values : ArrayView[Double],
fraction : Double,
) -> Double raise @domain.ExtractionError {
if fraction < 0.0 || fraction >= 0.5 || values.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="winsorization inputs are invalid",
)
}
let ordered = values.to_owned()
ordered.sort()
let count = (ordered.length().to_double() * fraction).to_int()
for index in 0.. Double raise @domain.ExtractionError {
if first.length() != second.length() || first.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="correlation arrays are invalid",
)
}
let first_mean = aggregate_mean(first)
let second_mean = aggregate_mean(second)
let mut numerator = 0.0
let mut first_variance = 0.0
let mut second_variance = 0.0
for index in 0.. Double raise @domain.ExtractionError {
if first.length() != second.length() || first.length() == 0 {
raise @domain.ExtractionError::InvalidArgument(
message="covariance arrays are invalid",
)
}
let first_mean = aggregate_mean(first)
let second_mean = aggregate_mean(second)
let mut total = 0.0
for index in 0.. Array[Int] {
let result = Array::make(values.length(), 1)
for index in 0.. values[index] {
result[index] += 1
}
}
}
result
}
///|
pub fn aggregate_sort_indices(values : ArrayView[Double]) -> Array[Int] {
let indices = [ for index in 0.. index ]
for first in 0..