use std::{
fmt::{Debug, Display, Formatter, Result as FmtResult},
ops::Add,
process::{Command, Stdio},
str::FromStr,
};
use approx::assert_relative_eq;
use nalgebra::{DMatrix, Dim, IsContiguous, Matrix, Storage};
use num::{complex::Complex64, Float, ToPrimitive};
#[macro_export]
macro_rules! r_assert_relative_equal {
($lhs:expr, $rhs:expr, $max_relative:expr) => {{
$crate::r::r_assert_relative_equal_($lhs, $rhs, $max_relative)
}};
($lhs:expr, $rhs:expr) => {{
r_assert_relative_equal!($lhs, $rhs, 1e-3)
}};
}
pub fn r_assert_relative_equal_(mut lhs: f64, mut rhs: f64, max_relative: f64) {
if lhs < 1e-30 {
lhs = 0.0;
}
if rhs < 1e-30 {
rhs = 0.0;
}
if lhs.is_finite() && rhs.is_finite() {
if (lhs - rhs).abs() / lhs > max_relative && (lhs - rhs).abs() / rhs > max_relative {
panic!(
"Test Failed: r_assert_relative_equal\n\n\tleft : {}\n\tright : {}\n\tdiff : {:e}\n\tmax_relative : {:e}\n\n",
lhs, rhs, (lhs - rhs).abs(), max_relative
)
}
} else if lhs.is_nan() && rhs.is_nan()
|| lhs.is_nan() && !rhs.is_nan()
{
} else if (lhs.is_finite() && !rhs.is_finite())
|| (!lhs.is_finite() && rhs.is_finite())
|| (lhs.is_sign_negative() && rhs.is_sign_positive())
|| (lhs.is_sign_positive() && rhs.is_sign_negative())
{
panic!(
"Test Failed: r_assert_relative_equal\n\n\tleft : {}\n\tright : {}\n\n",
lhs, rhs
)
}
}
#[macro_export]
macro_rules! r_assert_relative_equal_result {
($lhs:expr, $rhs:expr, $max_relative:expr) => {{
$crate::r::r_assert_relative_equal_result_(
$lhs,
Ok(num_traits::ToPrimitive::to_f64($rhs).unwrap()),
$max_relative,
)
}};
($lhs:expr, $rhs:expr) => {{
r_assert_relative_equal_result!($lhs, $rhs, 1e-3)
}};
}
pub fn r_assert_relative_equal_result_(
lhs: Result<f64, Box<dyn std::error::Error>>,
rhs: Result<f64, Box<dyn std::error::Error>>,
max_relative: f64,
) {
match (lhs, rhs) {
(Ok(o1), Ok(o2)) => {
if !(o1.is_nan() && o2.is_nan()) {
assert_relative_eq!(o1, o2, max_relative = max_relative)
}
}
(Err(e), Ok(o)) => {
if !o.is_nan() {
panic!("Test Failed: r_assert_relative_equal_result\n\n\tleft : Err({})\n\tright : Ok({})\n\n", e, o)
}
}
r => panic!("Unexpected: {:?}", r),
}
}
#[derive(Copy, Clone, Debug)]
pub enum RTesterError {
NotFinished,
}
impl Display for RTesterError {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
RTesterError::NotFinished => write!(f, "R Tester stopped before finishing"),
}
}
}
impl std::error::Error for RTesterError {}
#[derive(Clone, Debug)]
pub struct RTester {
seed: Option<RString>,
rand_type: Option<RString>,
libraries: RString,
script: RString,
display: RString,
done: RString,
}
#[derive(Clone, Debug)]
pub struct RString(String);
impl Display for RString {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl Add for RString {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0.as_str())
}
}
impl FromStr for RString {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string()))
}
}
impl RString {
pub fn from_string(string: String) -> Self {
Self(string)
}
pub fn empty() -> Self {
Self(String::new())
}
pub fn push(self, string: &str) -> Self {
Self(self.0 + string)
}
pub fn as_f64(&self) -> Result<f64, Box<dyn std::error::Error>> {
let string = self.0.as_str();
let string = if string.contains(']') {
string
.trim()
.split("] ")
.last()
.unwrap()
.replace(['<', '>'], "")
} else {
string.replace(['<', '>'], "")
};
match string.as_str() {
"Inf" => Ok(f64::infinity()),
"-Inf" => Ok(f64::NEG_INFINITY),
"NaN" | "NA" => Ok(f64::nan()),
_ => Ok(string.parse()?),
}
}
pub fn as_complex64(&self) -> Result<Complex64, Box<dyn std::error::Error>> {
let string = self.0.as_str();
let string = if string.contains(']') {
string
.trim()
.split("] ")
.last()
.unwrap()
.replace(['<', '>'], "")
} else {
string.replace(['<', '>'], "")
}
.replace('i', "");
let split_index = string
.rfind('+')
.filter(|i| *i != 0)
.unwrap_or(string.rfind('-').filter(|i| *i != 0).unwrap_or(0));
let split_string = string.split_at(split_index);
let real = RString::from_str(split_string.0).unwrap().as_f64()?;
let imaginary = RString::from_str(split_string.1).unwrap().as_f64()?;
Ok(Complex64::new(real, imaginary))
}
pub fn as_complex64_vec(&self) -> Result<Vec<Complex64>, Box<dyn std::error::Error>> {
let string = self.0.as_str();
Ok(string
.split_whitespace()
.filter(|i| !i.contains('['))
.filter_map(|s| RString::from_str(s).unwrap().as_complex64().ok())
.collect())
}
pub fn as_f64_vec(&self) -> Result<Vec<f64>, Box<dyn std::error::Error>> {
let string = self.0.as_str();
Ok(string
.split_whitespace()
.filter_map(|s| RString::from_str(s).unwrap().as_f64().ok())
.collect())
}
pub fn as_f64_matrix(&self) -> Result<DMatrix<f64>, Box<dyn std::error::Error>> {
let string = self.0.as_str();
let mut num_cols = 0;
string.lines().take(1).for_each(|s| {
num_cols = s.split_ascii_whitespace().count();
});
let num_rows = string.lines().skip(1).count();
let mut ret = DMatrix::from_iterator(
num_rows,
num_cols,
vec![0.0; num_rows * num_cols].into_iter(),
);
for (row, line) in string.lines().skip(1).enumerate() {
for (col, val) in line.split_ascii_whitespace().skip(1).enumerate() {
if let Some(x) = ret.get_mut((row, col)) {
*x = RString::from_str(val.trim()).unwrap().as_f64().unwrap();
}
}
}
Ok(ret)
}
pub fn as_bool(&self) -> Result<bool, Box<String>> {
let str = self.0.as_str();
if str.contains("TRUE") {
Ok(true)
} else if str.contains("FALSE") {
Ok(false)
} else {
Err(Box::new(format!("{str} is not a bool")))
}
}
pub fn as_lm_regression(&self) -> LMRegression {
let summary = self.0.as_str();
let mut coefficient_lines = Vec::new();
let mut r_squared_line = String::new();
let mut significance_line = String::new();
for line in summary
.trim()
.split('\n')
.skip_while(|line| !line.contains("Coefficients:"))
.skip(2)
.take_while(|line| !line.contains("---") && !line.is_empty())
{
coefficient_lines.push(line);
}
for line in summary.trim().split('\n') {
if line.starts_with("Multiple R-squared") {
r_squared_line = line.to_string();
}
if line.starts_with("F-statistic") {
significance_line = line.to_string();
}
}
let coefs = lm_summary_coefficients(&coefficient_lines);
let r_squared = r_r_squared(&r_squared_line);
let significance = r_significance(&significance_line);
LMRegression {
coefs: coefs
.into_iter()
.map(|(estimate, std_error, t, p)| LMCoefs {
estimate,
std_error,
t,
p,
})
.collect(),
r_squared,
f: significance.0,
p: significance.1,
}
}
pub fn as_rlm_regression(&self) -> RLMRegression {
let summary = self.0.as_str();
let mut coefficient_lines = Vec::new();
let mut on_coefficients = false;
for line in summary.trim().split('\n') {
if line.contains("Coefficients:") {
on_coefficients = true;
} else if line.contains("---") || line.is_empty() {
on_coefficients = false;
} else if on_coefficients {
coefficient_lines.push(line);
}
}
let coefs = rlm_summary_coefficients(&coefficient_lines);
RLMRegression {
coefs: coefs
.into_iter()
.map(|(estimate, std_error, t)| RLMCoefs {
estimate,
std_error,
t,
})
.collect(),
}
}
pub fn as_glm_regression(&self) -> GLMRegression {
let summary = self.0.as_str();
let mut coefficient_lines = Vec::new();
let mut null_dev_line = String::new();
let mut resid_dev_line = String::new();
let mut aic_line = String::new();
let mut on_coefficients = false;
for line in summary.trim().split('\n') {
if line.contains("Estimate") {
on_coefficients = true;
} else if line.is_empty() {
on_coefficients = false;
} else if on_coefficients {
coefficient_lines.push(line);
}
}
for line in summary.trim().split('\n') {
if line.trim().starts_with("Null deviance") {
null_dev_line = line.to_string();
}
if line.starts_with("Residual deviance") {
resid_dev_line = line.to_string();
}
if line.starts_with("AIC") {
aic_line = line.to_string();
}
}
let coefs = lm_summary_coefficients(&coefficient_lines);
let null_dev = r_null_deviance(&null_dev_line);
let resid_dev = r_residual_deviance(&resid_dev_line);
let aic = r_aic(&aic_line);
GLMRegression {
coefs: coefs
.into_iter()
.map(|(estimate, std_error, z, p)| GLMCoefs {
estimate,
std_error,
z,
p,
})
.collect(),
null_dev,
resid_dev,
aic,
}
}
pub fn as_r_fit(&self) -> RFitRegression {
let summary = self.0.as_str();
let mut coefficient_lines = Vec::new();
let mut r_squared_line = String::new();
let mut significance_line = String::new();
let mut wald_line = String::new();
for line in summary
.trim()
.split('\n')
.skip_while(|line| !line.contains("Coefficients:"))
.skip(2)
.take_while(|line| !line.contains("---") && !line.is_empty())
{
coefficient_lines.push(line);
}
for line in summary.trim().split('\n') {
if line.starts_with("Multiple R-squared (Robust)") {
r_squared_line = line.to_string();
}
if line.starts_with("Reduction in Dispersion") {
significance_line = line.to_string();
}
if line.starts_with("Overall Wald Test") {
wald_line = line.to_string();
}
}
let coefs = lm_summary_coefficients(&coefficient_lines);
let r_squared_robust = r_r_squared(&r_squared_line);
let red_dispersion = r_red_dispersion(&significance_line);
let wald_test = r_wald_test(&wald_line);
RFitRegression {
coefs: coefs
.into_iter()
.map(|(estimate, std_error, t, p)| LMCoefs {
estimate,
std_error,
t,
p,
})
.collect(),
r_squared_robust,
red_disp_test: red_dispersion.0,
disp_p: red_dispersion.1,
wald_test: wald_test.0,
wald_p: wald_test.1,
}
}
pub fn as_one_way_r_fit(&self) -> Vec<OneWayRFit> {
let summary = self.0.as_str();
let mut ret = Vec::new();
for line in summary
.trim()
.split('\n')
.skip_while(|line| !line.contains("Estimate"))
.skip(1)
{
let split = line.split_whitespace().collect::<Vec<_>>();
ret.push(OneWayRFit {
i: split[1].to_string(),
j: split[2].to_string(),
estimate: RString::from_str(split[3]).unwrap().as_f64().unwrap(),
std_err: RString::from_str(split[4]).unwrap().as_f64().unwrap(),
confidence_interval: (
RString::from_str(split[5]).unwrap().as_f64().unwrap(),
RString::from_str(split[6]).unwrap().as_f64().unwrap(),
),
});
}
ret
}
pub fn as_raov(&self) -> Vec<RAOV> {
let summary = self.0.as_str();
let mut ret = Vec::new();
for line in summary
.trim()
.split('\n')
.skip_while(|line| !line.contains("Mean RD"))
.skip(1)
{
let mut line = line.to_string();
for i in 1..=20 {
line = line.replace(&format!("x[, {i}]"), &format!("x[,{i}]"));
}
let split = line.split_whitespace().collect::<Vec<_>>();
ret.push(RAOV {
name: split[0].to_string(),
df: RString::from_str(split[1]).unwrap().as_f64().unwrap(),
rd: RString::from_str(split[2]).unwrap().as_f64().unwrap(),
mean_rd: RString::from_str(split[3]).unwrap().as_f64().unwrap(),
f: RString::from_str(split[4]).unwrap().as_f64().unwrap(),
p: RString::from_str(split[5]).unwrap().as_f64().unwrap(),
});
}
ret
}
pub fn from_f64_slice(v: &[f64]) -> Self {
let length = v.len();
let mut ret = "c(".to_string();
for (i, v_i) in v.iter().take(length).enumerate() {
ret += &v_i.to_string();
if i < length - 1 {
ret += ", "
}
}
ret += ")";
Self(ret)
}
pub fn from_complex64_slice(v: &[Complex64]) -> Self {
let length = v.len();
let mut ret = "c(".to_string();
for (i, v_i) in v.iter().take(length).enumerate() {
ret += &v_i.re.to_string();
ret += "+";
ret += &v_i.re.to_string();
ret += "i";
if i < length - 1 {
ret += ", "
}
}
ret += ")";
Self(ret)
}
pub fn from_f64_matrix<R, C, S>(m: &Matrix<f64, R, C, S>) -> Self
where
R: Dim,
C: Dim,
S: Storage<f64, R, C> + IsContiguous,
{
let (row_size, col_size) = m.shape();
let slice_string = RString::from_f64_slice(m.as_slice());
let mut ret = "matrix(".to_string();
ret += &slice_string.to_string();
ret += &format!(", nrow={}, ncol={})", row_size, col_size);
Self(ret)
}
pub fn from_bool(b: bool) -> Self {
Self(match b {
true => "TRUE".to_string(),
false => "FALSE".to_string(),
})
}
pub fn from_f64<F: ToPrimitive + std::fmt::Debug>(num: F) -> Self {
let num = num.to_f64().unwrap_or(f64::nan());
Self(if num.is_infinite() && num.is_sign_positive() {
"Inf".to_string()
} else if num.is_infinite() && num.is_sign_negative() {
"-Inf".to_string()
} else if num.is_nan() {
"NaN".to_string()
} else {
format!("{}", num)
})
}
pub fn from_library_name(lib_name: &str) -> Self {
Self(String::from("library(\"") + lib_name + "\")")
}
}
pub fn print_mat<R, C, S>(x: &Matrix<f64, R, C, S>)
where
R: Dim,
C: Dim,
S: Storage<f64, R, C>,
{
print!(" ");
for i in 0..x.ncols() {
print!("{:4} ", i);
}
println!();
for i in 0..x.nrows() {
print!("{:4} ", i);
for j in 0..x.ncols() {
print!("{:4} ", x[(i, j)]);
}
println!();
}
}
const RSCRIPT: Option<&str> = option_env!("Rscript");
impl Default for RTester {
fn default() -> Self {
Self {
seed: None,
rand_type: None,
libraries: RString::empty(),
script: RString::empty(),
display: RString::empty(),
done: RString::from_str("R_TESTER_DONE").unwrap(),
}
}
}
impl RTester {
pub fn new() -> Self {
Self::default()
}
pub fn add_library(mut self, library: &RString) -> Self {
self.libraries.0 += library.0.as_str();
self.libraries.0 += ";";
self
}
pub fn set_seed(mut self, seed: u16) -> Self {
self.seed = Some(RString::from_f64(seed));
self
}
pub fn set_rand_type(mut self, rand_type: &str) -> Self {
self.rand_type = Some(RString::from_str(rand_type).unwrap());
self
}
pub fn set_script(mut self, script: &RString) -> Self {
self.script = script.clone();
self
}
pub fn set_display(mut self, display: &RString) -> Self {
self.display = display.clone();
self
}
pub fn run(&self) -> Result<RString, Box<dyn std::error::Error>> {
let seed_piece = match (self.seed.clone(), self.rand_type.clone()) {
(Some(seed), Some(rand_type)) => {
RString::from_string(format!("set.seed({}, '{}');", seed, rand_type))
}
(None, Some(rand_type)) => {
RString::from_string(format!("set.seed(NULL, '{}');", rand_type))
}
(Some(seed), None) => {
RString::from_string(format!("set.seed({}, 'Marsaglia-Multicarry');", seed))
}
(None, None) => RString::from_str("").unwrap(),
};
let script = format!(
"options(warn=-1, show.error.messages=FALSE, digits=14, width=1e4, scipen=1);{}{}{}print({}, digits=14, dig.tst=14);cat('{}');",
self.libraries.0, seed_piece.0, self.script.0, self.display.0, self.done.0,
);
let mut ret_string = String::from_utf8(
Command::new(
RSCRIPT
.ok_or("")
.expect("Environment variable `Rscript` not set"),
)
.args(["-e", &script])
.stdout(Stdio::piped())
.spawn()?
.wait_with_output()?
.stdout,
)?;
let done_message = (0..self.done.0.len())
.filter_map(|_| ret_string.pop())
.collect::<String>()
.chars()
.rev()
.collect::<String>();
if done_message == self.done.0 {
Ok(RString(ret_string))
} else {
let error_script = format!(
"options(digits=14, width=1e4);{}{}{}print({});cat('{}');",
self.libraries.0, seed_piece.0, self.script.0, self.display.0, self.done.0,
);
let ret = Command::new(
RSCRIPT
.ok_or("")
.expect("Environment variable `Rscript` not set"),
)
.args(["-e", &error_script])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait_with_output()?;
let output = String::from_utf8(ret.stdout)?;
let error = String::from_utf8(ret.stderr)?;
println!("Input:\n{}", script);
println!("Output:\n{}", output);
println!("Error:\n{}", error);
Err(Box::new(RTesterError::NotFinished))
}
}
}
fn lm_summary_coefficients(lines: &[&str]) -> Vec<(f64, f64, f64, f64)> {
lines
.iter()
.take_while(|s| !s.trim().is_empty())
.filter_map(|line| {
let nums = line
.split_whitespace()
.filter_map(|s| RString::from_str(s).unwrap().as_f64().ok())
.collect::<Vec<_>>();
if nums.len() == 4 {
Some((nums[0], nums[1], nums[2], nums[3]))
} else {
None
}
})
.collect()
}
fn rlm_summary_coefficients(lines: &[&str]) -> Vec<(f64, f64, f64)> {
lines
.iter()
.skip(1)
.filter(|l| l.split_ascii_whitespace().count() == 4)
.map(|line| {
let nums = line
.split_whitespace()
.filter_map(|s| RString::from_str(s).unwrap().as_f64().ok())
.collect::<Vec<_>>();
(nums[0], nums[1], nums[2])
})
.collect()
}
fn r_r_squared(line: &str) -> f64 {
RString::from_str(line.split_whitespace().last().unwrap_or("0.0"))
.unwrap()
.as_f64()
.expect("R^2 error")
}
fn r_significance(line: &str) -> (f64, f64) {
let split_line = line.split_whitespace().collect::<Vec<_>>();
(
RString::from_str(split_line[1])
.unwrap()
.as_f64()
.expect("F-statistic error"),
RString::from_str(split_line.last().unwrap())
.unwrap()
.as_f64()
.expect("P-value error"),
)
}
fn r_null_deviance(line: &str) -> f64 {
let split_line = line.split_whitespace().collect::<Vec<_>>();
RString::from_str(split_line[2])
.unwrap()
.as_f64()
.expect("Null deviance error")
}
fn r_residual_deviance(line: &str) -> f64 {
let split_line = line.split_whitespace().collect::<Vec<_>>();
RString::from_str(split_line[2])
.unwrap()
.as_f64()
.expect("Residual deviance error")
}
fn r_aic(line: &str) -> f64 {
let split_line = line.split_whitespace().collect::<Vec<_>>();
RString::from_str(split_line[1])
.unwrap()
.as_f64()
.expect("AIC deviance error")
}
fn r_red_dispersion(line: &str) -> (f64, f64) {
let split_line = line.split_whitespace().collect::<Vec<_>>();
(
RString::from_str(split_line.get(4).unwrap_or(&"0.0"))
.unwrap()
.as_f64()
.expect("Reduction in Dispersion Test"),
RString::from_str(split_line.last().unwrap_or(&"0.0"))
.unwrap()
.as_f64()
.expect("P Value"),
)
}
fn r_wald_test(line: &str) -> (f64, f64) {
let split_line = line.split_whitespace().collect::<Vec<_>>();
(
RString::from_str(split_line.get(3).unwrap_or(&"0.0"))
.unwrap()
.as_f64()
.expect("Overall Wald Test"),
RString::from_str(split_line.last().unwrap_or(&"0.0"))
.unwrap()
.as_f64()
.expect("P Value"),
)
}
#[derive(Copy, Clone, Debug, Default)]
pub struct RGenerator {
seed: u16,
}
impl RGenerator {
pub fn new() -> Self {
Self::default()
}
pub fn set_seed(self, seed: u16) -> Self {
Self { seed, ..self }
}
pub fn generate_uniform<T1: ToPrimitive + Debug, T2: ToPrimitive + Debug>(
&self,
len: T1,
(lower_bound, upper_bound): (T2, T2),
) -> Vec<f64> {
RTester::new()
.set_seed(self.seed)
.set_script(&RString::from_string(format!(
"ret = runif({}, {}, {});",
RString::from_f64(len),
RString::from_f64(lower_bound),
RString::from_f64(upper_bound),
)))
.set_display(&RString::from_str("ret").unwrap())
.run()
.expect("R running error")
.as_f64_vec()
.expect("R running error")
}
pub fn generate_uniform_matrix<
T1: ToPrimitive + Debug,
T2: ToPrimitive + Debug,
T3: ToPrimitive + Debug + Clone,
>(
&self,
num_rows: T1,
num_columns: T2,
(lower_bound, upper_bound): (T3, T3),
) -> DMatrix<f64> {
let mut x = RTester::new()
.set_seed(self.seed)
.set_display(&RString::from_string(format!(
"matrix(runif({0}*{1}, {2}, {3}), nrow={0}, ncol={1})",
RString::from_f64(num_rows),
RString::from_f64(num_columns),
RString::from_f64(lower_bound.clone()),
RString::from_f64(upper_bound.clone()),
)))
.run()
.expect("R error")
.as_f64_matrix()
.expect("R array error");
x.iter_mut().for_each(|x_i| {
if x_i.is_nan() {
*x_i = lower_bound.to_f64().unwrap()
+ ((upper_bound.clone().to_f64().unwrap()
- lower_bound.clone().to_f64().unwrap())
/ 2.0)
}
});
x
}
pub fn generate_errors<
T1: ToPrimitive + Debug,
T2: ToPrimitive + Debug + Clone,
T3: ToPrimitive + Debug,
>(
&self,
len: T1,
(lower_bound, upper_bound): (T2, T2),
fuzz_amount: T3,
) -> Vec<f64> {
let mut errors = RTester::new()
.set_seed(self.seed)
.set_display(&RString::from_string(format!(
"rnorm({0}, {1}, {2}) + runif({0}, -{3}, {3})",
RString::from_f64(len),
RString::from_f64(lower_bound.clone()),
RString::from_f64(upper_bound.clone()),
RString::from_f64(fuzz_amount)
)))
.run()
.expect("R error")
.as_f64_vec()
.expect("R array error");
errors.iter_mut().for_each(|e| {
if e.is_nan() {
*e = lower_bound.clone().to_f64().unwrap()
+ ((upper_bound.to_f64().unwrap() - lower_bound.clone().to_f64().unwrap())
/ 2.0)
}
});
errors
}
}
#[derive(Clone, Debug)]
pub struct LMCoefs {
pub estimate: f64,
pub std_error: f64,
pub t: f64,
pub p: f64,
}
#[derive(Clone, Debug)]
pub struct RLMCoefs {
pub estimate: f64,
pub std_error: f64,
pub t: f64,
}
#[derive(Clone, Debug)]
pub struct GLMCoefs {
pub estimate: f64,
pub std_error: f64,
pub z: f64,
pub p: f64,
}
#[derive(Clone, Debug)]
pub struct LMRegression {
pub coefs: Vec<LMCoefs>,
pub r_squared: f64,
pub f: f64,
pub p: f64,
}
#[derive(Clone, Debug)]
pub struct RLMRegression {
pub coefs: Vec<RLMCoefs>,
}
#[derive(Clone, Debug)]
pub struct GLMRegression {
pub coefs: Vec<GLMCoefs>,
pub null_dev: f64,
pub resid_dev: f64,
pub aic: f64,
}
#[derive(Clone, Debug)]
pub struct RFitRegression {
pub coefs: Vec<LMCoefs>,
pub r_squared_robust: f64,
pub red_disp_test: f64,
pub disp_p: f64,
pub wald_test: f64,
pub wald_p: f64,
}
#[derive(Clone, Debug)]
pub struct OneWayRFit {
pub i: String,
pub j: String,
pub estimate: f64,
pub std_err: f64,
pub confidence_interval: (f64, f64),
}
#[derive(Clone, Debug)]
pub struct RAOV {
pub name: String,
pub df: f64,
pub rd: f64,
pub mean_rd: f64,
pub f: f64,
pub p: f64,
}