use crate::error::{StatsError, StatsResult};
use scirs2_core::ndarray::{ArrayBase, Data, Dimension, Ix2};
use scirs2_core::numeric::{Float, NumCast};
#[allow(dead_code)]
pub fn icc<F, D>(
data: &ArrayBase<D, Ix2>,
model: u8,
conf_level: Option<F>,
) -> StatsResult<(F, [F; 2])>
where
F: Float + std::fmt::Debug + NumCast + std::iter::Sum<F> + std::fmt::Display,
D: Data<Elem = F>,
Ix2: Dimension,
{
let (n, k) = data.dim();
if n < 2 {
return Err(StatsError::InvalidArgument(
"At least 2 subjects/items are required".to_string(),
));
}
if k < 2 {
return Err(StatsError::InvalidArgument(
"At least 2 raters/measurements are required".to_string(),
));
}
if !(1..=3).contains(&model) {
return Err(StatsError::InvalidArgument(
"Model must be 1, 2, or 3".to_string(),
));
}
let alpha = F::one()
- conf_level.unwrap_or_else(|| F::from(0.95).expect("Failed to convert constant to float"));
let mut subject_means = vec![F::zero(); n];
let mut rater_means = vec![F::zero(); k];
let mut grand_mean = F::zero();
for i in 0..n {
let mut sum = F::zero();
for j in 0..k {
sum = sum + data[[i, j]];
}
subject_means[i] = sum / F::from(k).expect("Failed to convert to float");
grand_mean = grand_mean + sum;
}
for j in 0..k {
let mut sum = F::zero();
for i in 0..n {
sum = sum + data[[i, j]];
}
rater_means[j] = sum / F::from(n).expect("Failed to convert to float");
}
grand_mean = grand_mean / F::from(n * k).expect("Failed to convert to float");
let mut ss_total = F::zero();
let mut ss_subjects = F::zero();
let mut ss_raters = F::zero();
for i in 0..n {
for j in 0..k {
ss_total = ss_total + (data[[i, j]] - grand_mean).powi(2);
}
}
for &mean in subject_means.iter().take(n) {
ss_subjects = ss_subjects
+ F::from(k).expect("Failed to convert to float") * (mean - grand_mean).powi(2);
}
for &mean in rater_means.iter().take(k) {
ss_raters = ss_raters
+ F::from(n).expect("Failed to convert to float") * (mean - grand_mean).powi(2);
}
let ss_residual = ss_total - ss_subjects - ss_raters;
let ms_subjects = ss_subjects / F::from(n - 1).expect("Failed to convert to float");
let ms_raters = ss_raters / F::from(k - 1).expect("Failed to convert to float");
let ms_residual = ss_residual / F::from((n - 1) * (k - 1)).expect("Operation failed");
let icc_val = match model {
1 => {
let ms_within =
(ss_raters + ss_residual) / F::from(n * (k - 1)).expect("Operation failed");
let ms_between = ms_subjects;
if ms_between <= ms_within {
F::zero() } else {
(ms_between - ms_within)
/ (ms_between + F::from(k - 1).expect("Failed to convert to float") * ms_within)
}
}
2 => {
if ms_subjects <= ms_residual {
F::zero() } else {
let numerator = ms_subjects - ms_residual;
let denominator = ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual
+ (F::from(k).expect("Failed to convert to float") * (ms_raters - ms_residual))
/ F::from(n).expect("Failed to convert to float");
numerator / denominator
}
}
3 => {
if ms_subjects <= ms_residual {
F::zero() } else {
(ms_subjects - ms_residual)
/ (ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual)
}
}
_ => unreachable!(), };
let f_value_lower = f_distribution_quantile(
F::one() - alpha / F::from(2.0).expect("Failed to convert constant to float"),
F::from(n - 1).expect("Failed to convert to float"),
F::from((n - 1) * (k - 1)).expect("Operation failed"),
);
let f_value_upper = f_distribution_quantile(
alpha / F::from(2.0).expect("Failed to convert constant to float"),
F::from(n - 1).expect("Failed to convert to float"),
F::from((n - 1) * (k - 1)).expect("Operation failed"),
);
let (lower_bound, upper_bound) = match model {
1 => {
let f_l = F::one() / f_value_lower;
let f_u = f_value_upper;
let lower = (f_l * ms_subjects - ms_residual)
/ (f_l * ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual);
let upper = (f_u * ms_subjects - ms_residual)
/ (f_u * ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual);
(lower.max(F::zero()), upper.min(F::one()))
}
2 => {
let f_l = F::one() / f_value_lower;
let f_u = f_value_upper;
let lower = (f_l * ms_subjects - ms_residual)
/ (f_l * ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual
+ F::from(k).expect("Failed to convert to float") * (ms_raters - ms_residual)
/ F::from(n).expect("Failed to convert to float"));
let upper = (f_u * ms_subjects - ms_residual)
/ (f_u * ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual
+ F::from(k).expect("Failed to convert to float") * (ms_raters - ms_residual)
/ F::from(n).expect("Failed to convert to float"));
(lower.max(F::zero()), upper.min(F::one()))
}
3 => {
let f_l = F::one() / f_value_lower;
let f_u = f_value_upper;
let lower = (f_l * ms_subjects - ms_residual)
/ (f_l * ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual);
let upper = (f_u * ms_subjects - ms_residual)
/ (f_u * ms_subjects
+ F::from(k - 1).expect("Failed to convert to float") * ms_residual);
(lower.max(F::zero()), upper.min(F::one()))
}
_ => unreachable!(), };
Ok((icc_val, [lower_bound, upper_bound]))
}
#[allow(dead_code)]
fn f_distribution_quantile<F: Float + NumCast>(p: F, df1: F, df2: F) -> F {
let p_f64 = <f64 as NumCast>::from(p).expect("Operation failed");
let df1_f64 = <f64 as NumCast>::from(df1).expect("Operation failed");
let df2_f64 = <f64 as NumCast>::from(df2).expect("Operation failed");
if p_f64 <= 0.0 {
return F::from(0.0).expect("Failed to convert constant to float");
}
if p_f64 >= 1.0 {
return F::from(1e10).expect("Failed to convert constant to float"); }
let z = normal_quantile(p_f64);
let a = 2.0 / (9.0 * df1_f64);
let b = 2.0 / (9.0 * df2_f64);
let c = z * (a + b).sqrt();
let res = ((1.0 - b) * ((1.0 - c).powi(3) / (1.0 - a))).powi(-1);
F::from(res).expect("Failed to convert to float")
}
#[allow(dead_code)]
fn normal_quantile(p: f64) -> f64 {
if p <= 0.0 {
return -38.5; }
if p >= 1.0 {
return 38.5; }
let q = if p < 0.5 { p } else { 1.0 - p };
let t = (-2.0 * q.ln()).sqrt();
let c0 = 2.515517;
let c1 = 0.802853;
let c2 = 0.010328;
let d1 = 1.432788;
let d2 = 0.189269;
let d3 = 0.001308;
let x = t - (c0 + c1 * t + c2 * t * t) / (1.0 + d1 * t + d2 * t * t + d3 * t * t * t);
if p < 0.5 {
-x
} else {
x
}
}