1use crate::strategy::{CITest, CITestDataType, TestResult};
2use crate::utils::power_divergence::power_divergence;
3use ndarray::{Array1, Array2};
4
5const FREEMAN_TUKEY_LAMBDA: f64 = -1.0 / 2.0;
6
7#[derive(Debug, Clone, PartialEq)]
12pub struct FreemanTukey {
13 pub boolean: bool,
14 pub significance_level: f64,
15}
16
17impl FreemanTukey {
18 #[must_use]
19 pub fn new(boolean: bool, significance_level: f64) -> Self {
20 Self {
21 boolean,
22 significance_level,
23 }
24 }
25}
26
27impl CITest for FreemanTukey {
28 fn run_test(
29 &self,
30 x_values: Array1<f64>,
31 y_values: Array1<f64>,
32 z: Array2<f64>,
33 ) -> anyhow::Result<TestResult> {
34 power_divergence(
35 &x_values,
36 &y_values,
37 &z,
38 self.boolean,
39 self.significance_level,
40 FREEMAN_TUKEY_LAMBDA,
41 )
42 }
43
44 fn data_types(&self) -> &'static [CITestDataType] {
45 &[CITestDataType::Discrete]
46 }
47}
48
49#[cfg(test)]
50#[allow(clippy::many_single_char_names)]
51mod tests {
52 use super::*;
53 use crate::utils::EPS;
54 use ndarray::{array, Array2};
55
56 fn unwrap_correlated(r: &TestResult) -> (f64, f64, usize) {
57 match r {
58 TestResult::Statistic(a, b, c) => (*a, *b, *c),
59 _ => panic!("expected Correlated2"),
60 }
61 }
62
63 #[test]
64 fn uncond_independent_data_not_rejected() {
65 let t = FreemanTukey {
66 boolean: false,
67 significance_level: 0.05,
68 };
69 let x = array![1., 1., 2., 2., 1., 1., 2., 2.];
70 let y = array![1., 2., 1., 2., 1., 2., 1., 2.];
71 let empty = Array2::<f64>::zeros((0, 0));
72
73 let (p, stat, dof) = unwrap_correlated(&t.run_test(x, y, empty).unwrap());
74 assert!(stat.abs() < EPS);
75 assert!(p > 0.99);
76 assert_eq!(dof, 1);
77 }
78
79 #[test]
80 fn cond_independent_not_rejected() {
81 let t = FreemanTukey {
82 boolean: false,
83 significance_level: 0.05,
84 };
85 let x = array![1., 1., 2., 2., 1., 1., 2., 2.];
86 let y = array![1., 2., 1., 2., 1., 2., 1., 2.];
87 let z = array![[1.], [1.], [1.], [1.], [2.], [2.], [2.], [2.],];
88
89 let (p, stat, dof) = unwrap_correlated(&t.run_test(x, y, z).unwrap());
90 assert!(stat.abs() < EPS);
91 assert!(p > 0.99);
92 assert_eq!(dof, 2);
93 }
94
95 #[test]
96 fn uncond_dependent_rejected() {
97 let t = FreemanTukey {
98 boolean: false,
99 significance_level: 0.05,
100 };
101 let x = array![1., 1., 1., 1., 1., 1., 2., 2., 2., 2., 2., 2.];
102 let y = array![1., 1., 1., 1., 1., 2., 1., 2., 2., 2., 2., 2.];
103 let empty = Array2::<f64>::zeros((0, 0));
104
105 let (p, stat, dof) = unwrap_correlated(&t.run_test(x, y, empty).unwrap());
106 assert!((stat - 6.319_453_539_579_289).abs() < EPS, "got {stat}");
107 assert!((p - 0.011_942_042_564_347_121).abs() < EPS, "got {p}");
108 assert_eq!(dof, 1);
109 }
110
111 #[test]
112 fn cond_dependent_rejected() {
113 let t = FreemanTukey {
114 boolean: false,
115 significance_level: 0.05,
116 };
117 let x = array![1., 1., 2., 2., 1., 2., 1., 1., 2., 2., 1., 2.];
118 let y = array![1., 2., 1., 2., 2., 1., 1., 2., 1., 2., 2., 1.];
119 let z = array![
120 [1.],
121 [1.],
122 [1.],
123 [1.],
124 [1.],
125 [1.],
126 [2.],
127 [2.],
128 [2.],
129 [2.],
130 [2.],
131 [2.]
132 ];
133
134 let (p, stat, dof) = unwrap_correlated(&t.run_test(x, y, z).unwrap());
135 assert!(
136 (stat - 1.382_538_273_265_069_5).abs() < EPS,
137 "got stat {stat}"
138 );
139 assert!((p - 0.500_939_904_278_208_8).abs() < EPS, "got p value {p}");
140 assert_eq!(dof, 2);
141 }
142
143 #[test]
144 fn uncond_boolean_accepts_independent() {
145 let t = FreemanTukey {
146 boolean: true,
147 significance_level: 0.05,
148 };
149 let x = array![1., 1., 2., 2., 1., 1., 2., 2.];
150 let y = array![1., 2., 1., 2., 1., 2., 1., 2.];
151 let empty = Array2::<f64>::zeros((0, 0));
152 let r = t.run_test(x, y, empty).unwrap();
153 assert!(matches!(r, TestResult::Boolean(true)));
154 }
155
156 #[test]
157 fn cond_boolean_rejects_dependent() {
158 let t = FreemanTukey {
159 boolean: true,
160 significance_level: 0.05,
161 };
162 let x = array![1., 1., 1., 2., 2., 2., 1., 1., 1., 2., 2., 2.];
163 let y = array![1., 1., 2., 2., 2., 2., 1., 1., 2., 2., 2., 2.];
164 let z = array![
165 [1.],
166 [1.],
167 [1.],
168 [1.],
169 [1.],
170 [1.],
171 [2.],
172 [2.],
173 [2.],
174 [2.],
175 [2.],
176 [2.]
177 ];
178
179 let r = t.run_test(x, y, z).unwrap();
180 assert!(matches!(r, TestResult::Boolean(false)));
181 }
182}