ClassSeparationScore.java
package org.hammer.audio.experimental.acoustic.feature.evaluation;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* Class-separation statistics for a single feature.
*
* <p>Captures per-class descriptive statistics and derived separation measures that estimate how
* well the feature discriminates between class labels.
*
* @param featureName human-readable feature name; must not be blank
* @param classMeans mean feature value per class label
* @param classStdDevs population standard deviation of the feature per class label
* @param classCounts number of samples per class label
* @param betweenClassVariance variance of class means weighted by class size around the global mean
* @param withinClassVariance mean within-class variance weighted by class size
* @param fisherRatio Fisher discriminant ratio ({@code betweenClassVariance /
* withinClassVariance}); {@code 0} when {@code withinClassVariance} is zero
*/
public record ClassSeparationScore(
String featureName,
Map<String, Double> classMeans,
Map<String, Double> classStdDevs,
Map<String, Integer> classCounts,
double betweenClassVariance,
double withinClassVariance,
double fisherRatio) {
/** Validate and defensively copy fields. */
public ClassSeparationScore {
Objects.requireNonNull(featureName, "featureName");
Objects.requireNonNull(classMeans, "classMeans");
Objects.requireNonNull(classStdDevs, "classStdDevs");
Objects.requireNonNull(classCounts, "classCounts");
if (featureName.isBlank()) {
throw new IllegalArgumentException("featureName must not be blank");
}
if (!Double.isFinite(betweenClassVariance) || betweenClassVariance < 0.0) {
throw new IllegalArgumentException("betweenClassVariance must be finite and >= 0");
}
if (!Double.isFinite(withinClassVariance) || withinClassVariance < 0.0) {
throw new IllegalArgumentException("withinClassVariance must be finite and >= 0");
}
if (!Double.isFinite(fisherRatio) || fisherRatio < 0.0) {
throw new IllegalArgumentException("fisherRatio must be finite and >= 0");
}
classMeans = Collections.unmodifiableMap(new LinkedHashMap<>(classMeans));
classStdDevs = Collections.unmodifiableMap(new LinkedHashMap<>(classStdDevs));
classCounts = Collections.unmodifiableMap(new LinkedHashMap<>(classCounts));
}
}