1use crate::strategy::{CITest, CITestDataType, TestResult};
2use crate::utils::power_divergence::power_divergence;
3use ndarray::{Array1, Array2};
4
5const CRESSIE_READ_LAMBDA: f64 = 2.0 / 3.0;
6
7#[derive(Debug, Clone, PartialEq)]
12pub struct CressieRead {
13 pub boolean: bool,
14 pub significance_level: f64,
15}
16
17impl CressieRead {
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 CressieRead {
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 CRESSIE_READ_LAMBDA,
41 )
42 }
43 fn data_types(&self) -> &'static [CITestDataType] {
44 &[CITestDataType::Discrete]
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::utils::EPS;
52 use ndarray::array;
53
54 fn unwrap_correlated(result: &TestResult) -> (f64, f64, usize) {
55 match result {
56 TestResult::Statistic(a, b, c) => (*a, *b, *c),
57 _ => panic!("expected Correlated2"),
58 }
59 }
60
61 fn unwrap_boolean(result: &TestResult) -> bool {
62 match result {
63 TestResult::Boolean(b) => *b,
64 _ => panic!("expected Boolean"),
65 }
66 }
67
68 #[test]
69 fn unconditional_independent_data_is_not_rejected() {
70 let test = CressieRead {
71 boolean: false,
72 significance_level: 0.05,
73 };
74 let x = array![1., 1., 2., 2., 1., 1., 2., 2.];
75 let y = array![1., 2., 1., 2., 1., 2., 1., 2.];
76 let empty_z = Array2::<f64>::zeros((0, 0));
77
78 let (p_value, statistic, dof) = unwrap_correlated(
79 &test
80 .run_test(x.clone(), y.clone(), empty_z.clone())
81 .unwrap(),
82 );
83 assert!(
84 statistic.abs() < EPS,
85 "expected statistic ~0, got {statistic}"
86 );
87 assert!(p_value > 0.99, "expected p ~1, got {p_value}");
88 assert_eq!(dof, 1);
89
90 let independent = unwrap_boolean(
91 &CressieRead {
92 boolean: true,
93 significance_level: 0.05,
94 }
95 .run_test(x, y, empty_z)
96 .unwrap(),
97 );
98 assert!(independent, "expected fail-to-reject (independent=true)");
99 }
100
101 #[test]
102 fn unconditional_dependent_data_is_rejected() {
103 let test = CressieRead {
104 boolean: false,
105 significance_level: 0.05,
106 };
107 let x = array![1., 1., 1., 1., 2., 2., 2., 2.];
108 let y = array![1., 1., 1., 1., 2., 2., 2., 2.];
109 let empty_z = Array2::<f64>::zeros((0, 0));
110
111 let (p_value, statistic, _dof) = unwrap_correlated(
112 &test
113 .run_test(x.clone(), y.clone(), empty_z.clone())
114 .unwrap(),
115 );
116 assert!(statistic > 5.0, "expected large statistic, got {statistic}");
117 assert!(
118 p_value < test.significance_level,
119 "expected p < {}, got {p_value}",
120 test.significance_level
121 );
122
123 let independent = unwrap_boolean(
124 &CressieRead {
125 boolean: true,
126 significance_level: 0.05,
127 }
128 .run_test(x, y, empty_z)
129 .unwrap(),
130 );
131 assert!(!independent, "expected reject (independent=false)");
132 }
133
134 #[test]
135 fn conditional_independent_per_group() {
136 let test = CressieRead {
137 boolean: false,
138 significance_level: 0.05,
139 };
140 let x = array![1., 1., 2., 2., 1., 1., 2., 2.];
141 let y = array![1., 2., 1., 2., 1., 2., 1., 2.];
142 let z = Array2::from_shape_vec((8, 1), vec![0., 0., 0., 0., 1., 1., 1., 1.]).unwrap();
143
144 let (p_value, statistic, dof) =
145 unwrap_correlated(&test.run_test(x.clone(), y.clone(), z.clone()).unwrap());
146 assert!(
147 statistic.abs() < EPS,
148 "expected statistic ~0, got {statistic}"
149 );
150 assert!(p_value > 0.99, "expected p ~1, got {p_value}");
151 assert_eq!(dof, 2);
152
153 let independent = unwrap_boolean(
154 &CressieRead {
155 boolean: true,
156 significance_level: 0.05,
157 }
158 .run_test(x, y, z)
159 .unwrap(),
160 );
161 assert!(independent);
162 }
163
164 #[test]
165 fn conditional_dependent_per_group() {
166 let test = CressieRead {
167 boolean: false,
168 significance_level: 0.05,
169 };
170 let x = array![1., 1., 2., 2., 1., 1., 2., 2.];
171 let y = array![1., 1., 2., 2., 1., 1., 2., 2.];
172 let z = Array2::from_shape_vec((8, 1), vec![0., 0., 0., 0., 1., 1., 1., 1.]).unwrap();
173
174 let (p_value, statistic, _dof) = unwrap_correlated(&test.run_test(x, y, z).unwrap());
175 assert!(statistic > 5.0, "expected large statistic, got {statistic}");
176 assert!(
177 p_value < test.significance_level,
178 "expected p < {}, got {p_value}",
179 test.significance_level
180 );
181 }
182}