///|
/// Returns the population covariance of two equally sized signals.
///
/// Covariance is useful for comparing sensor channels without requiring a
/// frequency-domain representation. The divisor is `N`, matching the other
/// descriptive statistics in this package.
pub fn covariance(
signal : ArrayView[Double],
other : ArrayView[Double],
) -> Double raise SpectrumError {
if signal.length() == 0 {
raise EmptySignal
}
if signal.length() != other.length() {
raise LengthMismatch
}
let mut mean_signal = 0.0
let mut mean_other = 0.0
for i in 0.. Double raise SpectrumError {
if signal.length() == 0 {
raise EmptySignal
}
if lag < 0 || lag >= signal.length() {
raise InvalidArgument(message="lag must be in [0, signal length)")
}
let mean = signal_stats(signal).mean
let count = signal.length() - lag
let mut total = 0.0
for i in 0.. Double raise SpectrumError {
if signal.length() == 0 {
raise EmptySignal
}
let mean = signal_stats(signal).mean
let mut total = 0.0
for value in signal {
total = total + (value - mean).abs()
}
total / Double::from_int(signal.length())
}
///|
/// Returns the interquartile range using the package's linear quantile rule.
pub fn interquartile_range(
signal : ArrayView[Double],
) -> Double raise SpectrumError {
quantile(signal, 0.75) - quantile(signal, 0.25)
}
///|
/// Clamps every sample to the inclusive interval `[lower, upper]`.
pub fn clip_signal(
signal : ArrayView[Double],
lower : Double,
upper : Double,
) -> Array[Double] raise SpectrumError {
if signal.length() == 0 {
raise EmptySignal
}
if lower > upper {
raise InvalidArgument(message="lower must not exceed upper")
}
let out : Array[Double] = []
for value in signal {
out.push(
if value < lower {
lower
} else if value > upper {
upper
} else {
value
},
)
}
out
}
///|
/// Returns sample indices where a signal crosses a threshold in either direction.
pub fn threshold_crossings(
signal : ArrayView[Double],
threshold : Double,
) -> Array[Int] raise SpectrumError {
if signal.length() < 2 {
raise InvalidArgument(message="threshold crossings need two samples")
}
let crossings : Array[Int] = []
for i in 1.. Array[Int] raise SpectrumError {
if signal.length() < 2 {
raise InvalidArgument(message="zero crossing indices need two samples")
}
let crossings : Array[Int] = []
for i in 1.. 0.0) ||
(signal[i - 1] > 0.0 && signal[i] < 0.0) {
crossings.push(i)
}
}
crossings
}