180
crates/domain/tests/correlation/adjustment_test.rs
Normal file
180
crates/domain/tests/correlation/adjustment_test.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use domain::correlation::{Adjustment, PValue};
|
||||
|
||||
fn p_values(values: &[f64]) -> Vec<PValue> {
|
||||
values
|
||||
.iter()
|
||||
.map(|value| PValue::new(*value).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_step_up_procedure_keeps_everything_below_the_largest_passing_rank() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let held = adjustment.holds_up(&p_values(&[0.001, 0.008, 0.039, 0.041, 0.42]));
|
||||
|
||||
assert_eq!(held, [true, true, true, true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_result_below_its_own_threshold_is_carried_by_a_stronger_one() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let held = adjustment.holds_up(&p_values(&[0.001, 0.079]));
|
||||
|
||||
assert_eq!(
|
||||
held,
|
||||
[true, true],
|
||||
"0.079 exceeds rank 1's threshold but rank 2 passes, so both hold"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn order_of_the_set_does_not_change_who_holds_up() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let held = adjustment.holds_up(&p_values(&[0.42, 0.041, 0.001, 0.039, 0.008]));
|
||||
|
||||
assert_eq!(held, [false, true, true, true, true]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nothing_holds_up_when_nothing_is_small_enough() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let held = adjustment.holds_up(&p_values(&[0.4, 0.5, 0.6]));
|
||||
|
||||
assert_eq!(held, [false, false, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_larger_set_makes_each_result_work_harder() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let alone = adjustment.holds_up(&p_values(&[0.04]));
|
||||
let among_ten = adjustment.holds_up(&p_values(&[
|
||||
0.04, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
|
||||
]));
|
||||
|
||||
assert_eq!(alone, [true]);
|
||||
assert!(!among_ten[0], "0.04 needs to beat 0.01 once ten are tested");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_threshold_is_whatever_it_is_configured_to_be() {
|
||||
let strict = Adjustment::controlling_false_discovery_at(0.01);
|
||||
let loose = Adjustment::controlling_false_discovery_at(0.20);
|
||||
|
||||
let borderline = p_values(&[0.03, 0.5]);
|
||||
|
||||
assert_eq!(strict.holds_up(&borderline), [false, false]);
|
||||
assert_eq!(loose.holds_up(&borderline), [true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_set_holds_nothing_up_and_does_not_panic() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
assert!(adjustment.holds_up(&[]).is_empty());
|
||||
}
|
||||
|
||||
use domain::correlation::{CorrelationStrategy, Family, Tested};
|
||||
|
||||
fn tested(family: Family, strategy: CorrelationStrategy, p: f64) -> Tested {
|
||||
Tested {
|
||||
family,
|
||||
strategy,
|
||||
p_value: PValue::new(p).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn measurement(p: f64) -> Tested {
|
||||
tested(Family::Measurements, CorrelationStrategy::Spearman, p)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_result_is_corrected_against_the_others_in_its_own_group() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let held = adjustment.holds_up_across(&[measurement(0.04), measurement(0.5)]);
|
||||
|
||||
assert_eq!(held, [true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn how_many_activities_are_kept_does_not_change_a_measurement_result() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let mut entries = vec![measurement(0.04), measurement(0.5)];
|
||||
for _ in 0..20 {
|
||||
entries.push(tested(
|
||||
Family::Activities,
|
||||
CorrelationStrategy::MeanDifference,
|
||||
0.5,
|
||||
));
|
||||
}
|
||||
|
||||
let held = adjustment.holds_up_across(&entries);
|
||||
|
||||
assert!(
|
||||
held[0],
|
||||
"a measurement must not be corrected against activities"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn measuring_the_same_thing_several_ways_is_not_several_hypotheses() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let mut entries = vec![measurement(0.04), measurement(0.5)];
|
||||
for other in [CorrelationStrategy::Pearson, CorrelationStrategy::Kendall] {
|
||||
for _ in 0..10 {
|
||||
entries.push(tested(Family::Measurements, other, 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
let held = adjustment.holds_up_across(&entries);
|
||||
|
||||
assert!(
|
||||
held[0],
|
||||
"correcting across strategies would penalise measuring carefully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_of_the_same_question_does_make_a_result_work_harder() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let mut entries = vec![measurement(0.04)];
|
||||
for _ in 0..20 {
|
||||
entries.push(measurement(0.5));
|
||||
}
|
||||
|
||||
let held = adjustment.holds_up_across(&entries);
|
||||
|
||||
assert!(
|
||||
!held[0],
|
||||
"twenty more measurements under the same strategy is twenty more comparisons"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_families_are_corrected_apart_even_under_one_strategy() {
|
||||
let adjustment = Adjustment::controlling_false_discovery_at(0.10);
|
||||
|
||||
let mut entries = vec![measurement(0.04), measurement(0.5)];
|
||||
for _ in 0..20 {
|
||||
entries.push(tested(
|
||||
Family::Activities,
|
||||
CorrelationStrategy::Spearman,
|
||||
0.5,
|
||||
));
|
||||
}
|
||||
|
||||
let held = adjustment.holds_up_across(&entries);
|
||||
|
||||
assert!(
|
||||
held[0],
|
||||
"a Family is a question: activities never dilute a measurement, whichever strategy scored them"
|
||||
);
|
||||
}
|
||||
58
crates/domain/tests/correlation/moon_phase_test.rs
Normal file
58
crates/domain/tests/correlation/moon_phase_test.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use domain::entry::Date;
|
||||
use domain::moon::MoonPhase;
|
||||
|
||||
fn on(day: &str) -> MoonPhase {
|
||||
MoonPhase::on(&Date::from_persistence(day.parse().unwrap()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_total_solar_eclipse_happens_at_a_new_moon() {
|
||||
for day in ["2017-08-21", "2024-04-08"] {
|
||||
let illumination = on(day).illumination();
|
||||
|
||||
assert!(
|
||||
illumination < 0.02,
|
||||
"{day} should be dark, got {illumination}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_total_lunar_eclipse_happens_at_a_full_moon() {
|
||||
for day in ["2000-01-21", "2018-01-31", "2019-01-21", "2022-05-16"] {
|
||||
let illumination = on(day).illumination();
|
||||
|
||||
assert!(
|
||||
illumination > 0.98,
|
||||
"{day} should be full, got {illumination}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn illumination_never_leaves_its_bounds() {
|
||||
let mut day = Date::from_persistence("2024-01-01".parse().unwrap());
|
||||
|
||||
for _ in 0..800 {
|
||||
let illumination = MoonPhase::on(&day).illumination();
|
||||
assert!((0.0..=1.0).contains(&illumination), "got {illumination}");
|
||||
day = day.next();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_same_illumination_is_told_apart_by_waxing_and_waning() {
|
||||
let waxing = on("2024-04-15");
|
||||
let waning = on("2024-04-30");
|
||||
|
||||
assert!((waxing.illumination() - 0.5).abs() < 0.15);
|
||||
assert!((waning.illumination() - 0.5).abs() < 0.15);
|
||||
assert_eq!(waxing.name(), "First Quarter");
|
||||
assert_eq!(waning.name(), "Last Quarter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_extremes_are_named_for_what_they_are() {
|
||||
assert_eq!(on("2024-04-08").name(), "New");
|
||||
assert_eq!(on("2022-05-16").name(), "Full");
|
||||
}
|
||||
107
crates/domain/tests/correlation/significance_test.rs
Normal file
107
crates/domain/tests/correlation/significance_test.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use domain::correlation::{CorrelationStrategy, Observation, PValue};
|
||||
use domain::entry::{DayMood, Mood};
|
||||
|
||||
fn day(mood: Mood) -> DayMood {
|
||||
DayMood::of(&[mood]).unwrap()
|
||||
}
|
||||
|
||||
fn mood_of(value: u8) -> Mood {
|
||||
Mood::try_from(value).unwrap()
|
||||
}
|
||||
|
||||
fn loosely_rising() -> Vec<Observation> {
|
||||
let moods = [3, 1, 4, 2, 3, 5, 2, 4, 3, 5, 4, 3];
|
||||
|
||||
moods
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, mood)| Observation::new(index as f64 + 1.0, day(mood_of(*mood))))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn significance(strategy: CorrelationStrategy, observations: &[Observation]) -> f64 {
|
||||
strategy
|
||||
.significance(observations)
|
||||
.expect("a scored series has a p-value")
|
||||
.value()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_familiar_thresholds_come_out_of_the_normal_tail() {
|
||||
assert!((PValue::from_standard_score(1.96).value() - 0.05).abs() < 1e-4);
|
||||
assert!((PValue::from_standard_score(1.645).value() - 0.10).abs() < 1e-4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_standard_score_of_nothing_is_certain_to_be_nothing() {
|
||||
assert!((PValue::from_standard_score(0.0).value() - 1.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direction_does_not_change_how_surprising_a_score_is() {
|
||||
let positive = PValue::from_standard_score(2.5).value();
|
||||
let negative = PValue::from_standard_score(-2.5).value();
|
||||
|
||||
assert!((positive - negative).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_strategy_turns_its_own_statistic_into_a_p_value() {
|
||||
let observations = loosely_rising();
|
||||
|
||||
let pearson = significance(CorrelationStrategy::Pearson, &observations);
|
||||
let spearman = significance(CorrelationStrategy::Spearman, &observations);
|
||||
let kendall = significance(CorrelationStrategy::Kendall, &observations);
|
||||
|
||||
assert!((pearson - 0.198_050_956_8).abs() < 1e-4, "got {pearson}");
|
||||
assert!((spearman - 0.241_732_162_6).abs() < 1e-4, "got {spearman}");
|
||||
assert!((kendall - 0.201_603_352_7).abs() < 1e-4, "got {kendall}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_relationship_that_holds_all_the_way_is_hard_to_put_down_to_chance() {
|
||||
let tight: Vec<Observation> = (1..=12)
|
||||
.map(|day_number| {
|
||||
let mood = mood_of(((day_number - 1) / 3 + 1) as u8);
|
||||
Observation::new(day_number as f64, day(mood))
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(significance(CorrelationStrategy::Pearson, &tight) < 0.001);
|
||||
assert!(significance(CorrelationStrategy::Spearman, &tight) < 0.001);
|
||||
assert!(significance(CorrelationStrategy::Kendall, &tight) < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mean_difference_is_judged_by_the_spread_within_each_group() {
|
||||
let observations: Vec<Observation> = [
|
||||
(1.0, Mood::Good),
|
||||
(1.0, Mood::Rad),
|
||||
(1.0, Mood::Good),
|
||||
(1.0, Mood::Rad),
|
||||
(0.0, Mood::Bad),
|
||||
(0.0, Mood::Meh),
|
||||
(0.0, Mood::Bad),
|
||||
(0.0, Mood::Awful),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(value, mood)| Observation::new(value, day(mood)))
|
||||
.collect();
|
||||
|
||||
let p = significance(CorrelationStrategy::MeanDifference, &observations);
|
||||
|
||||
assert!((p - 0.000_000_573_3).abs() < 1e-9, "got {p}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_series_that_cannot_be_scored_has_nothing_to_report() {
|
||||
let unvarying: Vec<Observation> = (1..=10)
|
||||
.map(|day_number| Observation::new(day_number as f64, day(Mood::Meh)))
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
CorrelationStrategy::Spearman
|
||||
.significance(&unvarying)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
117
crates/domain/tests/correlation/spearman_test.rs
Normal file
117
crates/domain/tests/correlation/spearman_test.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use domain::correlation::{CorrelationStrategy, Observation};
|
||||
use domain::entry::{DayMood, Mood};
|
||||
|
||||
fn day(mood: Mood) -> DayMood {
|
||||
DayMood::of(&[mood]).unwrap()
|
||||
}
|
||||
|
||||
fn observations(pairs: &[(f64, Mood)]) -> Vec<Observation> {
|
||||
pairs
|
||||
.iter()
|
||||
.map(|(value, mood)| Observation::new(*value, day(*mood)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn spearman(pairs: &[(f64, Mood)]) -> Option<f64> {
|
||||
CorrelationStrategy::Spearman
|
||||
.score(&observations(pairs))
|
||||
.map(|coefficient| coefficient.value())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_perfectly_ordered_pair_of_series_scores_one() {
|
||||
let score = spearman(&[
|
||||
(1.0, Mood::Awful),
|
||||
(2.0, Mood::Bad),
|
||||
(3.0, Mood::Meh),
|
||||
(4.0, Mood::Good),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert!((score - 1.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_perfectly_inverted_pair_of_series_scores_minus_one() {
|
||||
let score = spearman(&[
|
||||
(1.0, Mood::Rad),
|
||||
(2.0, Mood::Good),
|
||||
(3.0, Mood::Meh),
|
||||
(4.0, Mood::Bad),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert!((score + 1.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_order_matters_not_the_distance() {
|
||||
let gentle = spearman(&[
|
||||
(1.0, Mood::Awful),
|
||||
(2.0, Mood::Bad),
|
||||
(3.0, Mood::Meh),
|
||||
(4.0, Mood::Good),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let one_enormous_day = spearman(&[
|
||||
(1.0, Mood::Awful),
|
||||
(2.0, Mood::Bad),
|
||||
(3.0, Mood::Meh),
|
||||
(100_000.0, Mood::Good),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert!((gentle - one_enormous_day).abs() < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tied_moods_share_their_average_rank_rather_than_taking_the_shortcut() {
|
||||
let score = spearman(&[
|
||||
(1_000.0, Mood::Meh),
|
||||
(2_000.0, Mood::Meh),
|
||||
(3_000.0, Mood::Meh),
|
||||
(4_000.0, Mood::Good),
|
||||
(5_000.0, Mood::Good),
|
||||
(6_000.0, Mood::Rad),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let tie_corrected = 0.925_820_099_8;
|
||||
let textbook_shortcut = 0.928_571_428_6;
|
||||
|
||||
assert!(
|
||||
(score - tie_corrected).abs() < 1e-9,
|
||||
"expected the tie-corrected coefficient, got {score}"
|
||||
);
|
||||
assert!(
|
||||
(score - textbook_shortcut).abs() > 1e-9,
|
||||
"this is the 6*d^2 shortcut, which is wrong when ranks are tied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_day_count_below_two_scores_nothing() {
|
||||
assert!(spearman(&[(1.0, Mood::Good)]).is_none());
|
||||
assert!(spearman(&[]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unvarying_series_scores_nothing() {
|
||||
let same_mood_every_day = spearman(&[
|
||||
(1.0, Mood::Meh),
|
||||
(2.0, Mood::Meh),
|
||||
(3.0, Mood::Meh),
|
||||
(4.0, Mood::Meh),
|
||||
]);
|
||||
|
||||
let same_value_every_day = spearman(&[
|
||||
(5_000.0, Mood::Awful),
|
||||
(5_000.0, Mood::Bad),
|
||||
(5_000.0, Mood::Good),
|
||||
(5_000.0, Mood::Rad),
|
||||
]);
|
||||
|
||||
assert!(same_mood_every_day.is_none());
|
||||
assert!(same_value_every_day.is_none());
|
||||
}
|
||||
180
crates/domain/tests/correlation/strategy_test.rs
Normal file
180
crates/domain/tests/correlation/strategy_test.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use domain::activity::ActivityId;
|
||||
use domain::correlation::{CorrelationInput, CorrelationStrategy, Observation, SeriesShape};
|
||||
use domain::entry::{DayMood, Mood};
|
||||
use domain::metric::MetricKind;
|
||||
|
||||
fn day(mood: Mood) -> DayMood {
|
||||
DayMood::of(&[mood]).unwrap()
|
||||
}
|
||||
|
||||
fn observations(pairs: &[(f64, Mood)]) -> Vec<Observation> {
|
||||
pairs
|
||||
.iter()
|
||||
.map(|(value, mood)| Observation::new(*value, day(*mood)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn score(strategy: CorrelationStrategy, pairs: &[(f64, Mood)]) -> Option<f64> {
|
||||
strategy
|
||||
.score(&observations(pairs))
|
||||
.map(|coefficient| coefficient.value())
|
||||
}
|
||||
|
||||
const TIED: [(f64, Mood); 6] = [
|
||||
(1_000.0, Mood::Meh),
|
||||
(2_000.0, Mood::Meh),
|
||||
(3_000.0, Mood::Meh),
|
||||
(4_000.0, Mood::Good),
|
||||
(5_000.0, Mood::Good),
|
||||
(6_000.0, Mood::Rad),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn kendall_corrects_for_ties_rather_than_counting_pairs_alone() {
|
||||
let score = score(CorrelationStrategy::Kendall, &TIED).unwrap();
|
||||
|
||||
let tau_b = 0.856_348_838_6;
|
||||
let tau_a = 0.733_333_333_3;
|
||||
|
||||
assert!((score - tau_b).abs() < 1e-9, "expected tau-b, got {score}");
|
||||
assert!(
|
||||
(score - tau_a).abs() > 1e-9,
|
||||
"this is tau-a, which overstates disagreement when ranks are tied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kendall_reaches_both_extremes() {
|
||||
let rising = score(
|
||||
CorrelationStrategy::Kendall,
|
||||
&[
|
||||
(1.0, Mood::Awful),
|
||||
(2.0, Mood::Bad),
|
||||
(3.0, Mood::Meh),
|
||||
(4.0, Mood::Good),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let falling = score(
|
||||
CorrelationStrategy::Kendall,
|
||||
&[
|
||||
(1.0, Mood::Good),
|
||||
(2.0, Mood::Meh),
|
||||
(3.0, Mood::Bad),
|
||||
(4.0, Mood::Awful),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!((rising - 1.0).abs() < 1e-12);
|
||||
assert!((falling + 1.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pearson_measures_the_line_where_the_rank_methods_measure_the_order() {
|
||||
let curved = [
|
||||
(1.0, Mood::Awful),
|
||||
(2.0, Mood::Bad),
|
||||
(3.0, Mood::Meh),
|
||||
(100.0, Mood::Good),
|
||||
];
|
||||
|
||||
let pearson = score(CorrelationStrategy::Pearson, &curved).unwrap();
|
||||
let spearman = score(CorrelationStrategy::Spearman, &curved).unwrap();
|
||||
|
||||
assert!((spearman - 1.0).abs() < 1e-12, "the order is perfect");
|
||||
assert!(
|
||||
(pearson - 0.785_026_421).abs() < 1e-9,
|
||||
"the line is not, got {pearson}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mean_difference_is_told_as_a_share_of_the_mood_scale() {
|
||||
let score = score(
|
||||
CorrelationStrategy::MeanDifference,
|
||||
&[
|
||||
(1.0, Mood::Good),
|
||||
(1.0, Mood::Rad),
|
||||
(1.0, Mood::Good),
|
||||
(1.0, Mood::Rad),
|
||||
(0.0, Mood::Bad),
|
||||
(0.0, Mood::Meh),
|
||||
(0.0, Mood::Bad),
|
||||
(0.0, Mood::Awful),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
(score - 0.625).abs() < 1e-12,
|
||||
"two and a half mood points out of four, got {score}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mean_difference_needs_days_on_both_sides() {
|
||||
let only_present = score(
|
||||
CorrelationStrategy::MeanDifference,
|
||||
&[(1.0, Mood::Good), (1.0, Mood::Rad)],
|
||||
);
|
||||
|
||||
let only_absent = score(
|
||||
CorrelationStrategy::MeanDifference,
|
||||
&[(0.0, Mood::Good), (0.0, Mood::Rad)],
|
||||
);
|
||||
|
||||
assert!(only_present.is_none());
|
||||
assert!(only_absent.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_strategy_scores_one_shape_of_series_and_an_input_has_one() {
|
||||
assert_eq!(
|
||||
CorrelationStrategy::MeanDifference.scores(),
|
||||
SeriesShape::Presence
|
||||
);
|
||||
assert_eq!(
|
||||
CorrelationInput::Activity(ActivityId::generate()).series(),
|
||||
SeriesShape::Presence
|
||||
);
|
||||
|
||||
for continuous in [
|
||||
CorrelationStrategy::Pearson,
|
||||
CorrelationStrategy::Spearman,
|
||||
CorrelationStrategy::Kendall,
|
||||
] {
|
||||
assert_eq!(continuous.scores(), SeriesShape::Continuous);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
CorrelationInput::Metric(MetricKind::Steps).series(),
|
||||
SeriesShape::Continuous
|
||||
);
|
||||
assert_eq!(
|
||||
CorrelationInput::MoonPhase.series(),
|
||||
SeriesShape::Continuous
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_matching_strategies_are_offered_for_an_input() {
|
||||
let steps = CorrelationInput::Metric(MetricKind::Steps);
|
||||
let exercise = CorrelationInput::Activity(ActivityId::generate());
|
||||
|
||||
let for_steps: Vec<&str> = CorrelationStrategy::ALL
|
||||
.into_iter()
|
||||
.filter(|strategy| strategy.can_score(&steps))
|
||||
.map(|strategy| strategy.name())
|
||||
.collect();
|
||||
|
||||
let for_exercise: Vec<&str> = CorrelationStrategy::ALL
|
||||
.into_iter()
|
||||
.filter(|strategy| strategy.can_score(&exercise))
|
||||
.map(|strategy| strategy.name())
|
||||
.collect();
|
||||
|
||||
assert_eq!(for_steps, ["pearson", "spearman", "kendall"]);
|
||||
assert_eq!(for_exercise, ["meanDifference"]);
|
||||
}
|
||||
Reference in New Issue
Block a user