///|
/// Values and cumulative probabilities returned by `ecdf`.
pub struct Ecdf {
values : Array[Double]
probabilities : Array[Double]
}
///|
pub fn Ecdf::values(self : Ecdf) -> Array[Double] {
self.values.copy()
}
///|
pub fn Ecdf::probabilities(self : Ecdf) -> Array[Double] {
self.probabilities.copy()
}
///|
fn validate_ecdf_values(values : Array[Double]) -> Unit raise @core.DataError {
if values.length() == 0 {
raise @core.DataError::InvalidStatistics(
message="ecdf requires at least one value",
)
}
for value in values {
if value.is_nan() || value.is_inf() {
raise @core.DataError::InvalidStatistics(
message="ecdf values must be finite",
)
}
}
}
///|
fn ecdf_pairs(
values : Array[Double],
weights : Array[Double],
) -> Array[(Double, Double)] raise @core.DataError {
validate_ecdf_values(values)
if weights.length() != 0 && weights.length() != values.length() {
raise @core.DataError::LengthMismatch(
expected=values.length(),
actual=weights.length(),
)
}
let pairs = Array(capacity=values.length())
for index in 0.. {
let (first_value, _) = first
let (second_value, _) = second
if first_value < second_value {
-1
} else if first_value > second_value {
1
} else {
0
}
})
let mut total = 0.0
for pair in pairs {
let (_, weight) = pair
total += weight
}
if total <= 0.0 {
raise @core.DataError::InvalidStatistics(
message="ecdf weights must have a positive sum",
)
}
pairs
}
///|
fn ecdf_result(
values : Array[Double],
weights : Array[Double],
complementary : Bool,
horizontal : Bool,
compress : Bool,
color : String,
label : String,
axes : Axes,
) -> Ecdf raise @core.DataError {
let pairs = ecdf_pairs(values, weights)
let total = {
let mut result = 0.0
for pair in pairs {
let (_, weight) = pair
result += weight
}
result
}
let output_values = Array(capacity=pairs.length())
let output_probabilities = Array(capacity=pairs.length())
let points : Array[@core.Point] = []
let mut cumulative = 0.0
let mut index = 0
while index < pairs.length() {
let (value, first_weight) = pairs[index]
let mut group_weight = first_weight
let mut next_index = index + 1
if compress {
while next_index < pairs.length() {
let (next_value, next_weight) = pairs[next_index]
if next_value != value {
break
}
group_weight += next_weight
next_index += 1
}
}
let before = if complementary {
1.0 - cumulative / total
} else {
cumulative / total
}
cumulative += group_weight
let after = if complementary {
1.0 - cumulative / total
} else {
cumulative / total
}
output_values.push(value)
output_probabilities.push(after)
let first_point = if horizontal {
@core.point(before, value)
} else {
@core.point(value, before)
}
let second_point = if horizontal {
@core.point(after, value)
} else {
@core.point(value, after)
}
points.push(first_point)
points.push(second_point)
index = if compress { next_index } else { index + 1 }
}
let resolved_color = axes.resolve_color(color)
axes.push_artist(Artist::Line(points, @core.style(resolved_color)))
axes.add_auto_legend(label, resolved_color, LegendKind::Line)
{ values: output_values, probabilities: output_probabilities, }
}
///|
/// Plot and return an empirical cumulative distribution function.
pub fn Figure::ecdf(
self : Figure,
values : Array[Double],
weights~ : Array[Double] = [],
complementary~ : Bool = false,
horizontal~ : Bool = false,
compress~ : Bool = false,
color~ : String = "",
label~ : String = "",
) -> Ecdf raise @core.DataError {
self
.axes()
.ecdf(
values,
weights~,
complementary~,
horizontal~,
compress~,
color~,
label~,
)
}
///|
/// Plot and return an empirical cumulative distribution function.
pub fn Axes::ecdf(
self : Axes,
values : Array[Double],
weights~ : Array[Double] = [],
complementary~ : Bool = false,
horizontal~ : Bool = false,
compress~ : Bool = false,
color~ : String = "",
label~ : String = "",
) -> Ecdf raise @core.DataError {
ecdf_result(
values, weights, complementary, horizontal, compress, color, label, self,
)
}