use crate::error::GreenersError;
use crate::glm::{Family, GLM};
use crate::ols::PredictionResult;
use crate::{CovarianceType, DataFrame, Formula, InferenceType};
use ndarray::{Array1, Array2};
use statrs::distribution::{ContinuousCDF, Normal};
use std::fmt;
#[derive(Debug, Clone)]
pub struct PoissonResult {
pub params: Array1<f64>,
pub std_errors: Array1<f64>,
pub z_values: Array1<f64>,
pub p_values: Array1<f64>,
pub conf_lower: Array1<f64>,
pub conf_upper: Array1<f64>,
pub log_likelihood: f64,
pub deviance: f64,
pub null_deviance: f64,
pub aic: f64,
pub bic: f64,
pub pseudo_r2: f64,
pub pearson_chi2: f64,
pub n_obs: usize,
pub df_resid: usize,
pub df_model: usize,
pub n_iter: usize,
pub converged: bool,
pub inference_type: InferenceType,
pub variable_names: Option<Vec<String>>,
pub omitted_vars: Vec<(usize, String)>,
_x_data: Array2<f64>,
_y_data: Array1<f64>,
}
impl fmt::Display for PoissonResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", " Poisson Regression Results ")?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Dep. Variable:", "y", "Log-Likelihood:", self.log_likelihood
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Model:", "Poisson", "Pseudo R-sq:", self.pseudo_r2
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Method:", "IRLS", "Deviance:", self.deviance
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"No. Observations:", self.n_obs, "Pearson chi2:", self.pearson_chi2
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Df Residuals:", self.df_resid, "AIC:", self.aic
)?;
writeln!(
f,
"{:<20} {:>15} || {:<20} {:>15.4}",
"Df Model:", self.df_model, "BIC:", self.bic
)?;
writeln!(f, "\n{:-^78}", "")?;
writeln!(
f,
"{:<12} {:>10} {:>10} {:>8} {:>8} {:>10} {:>10}",
"", "coef", "std err", "z", "P>|z|", "[0.025", "0.975]"
)?;
writeln!(f, "{:-^78}", "")?;
let total = self.params.len() + self.omitted_vars.len();
let mut fit_idx = 0usize;
for pos in 0..total {
if let Some((_, name)) = self.omitted_vars.iter().find(|(p, _)| *p == pos) {
writeln!(f, "{:<12} (omitted)", name)?;
} else {
let name = self
.variable_names
.as_ref()
.and_then(|n| n.get(fit_idx).cloned())
.unwrap_or_else(|| format!("x{}", fit_idx));
writeln!(
f,
"{:<12} {:>10.4} {:>10.4} {:>8.3} {:>8.3} {:>10.3} {:>10.3}",
name,
self.params[fit_idx],
self.std_errors[fit_idx],
self.z_values[fit_idx],
self.p_values[fit_idx],
self.conf_lower[fit_idx],
self.conf_upper[fit_idx]
)?;
fit_idx += 1;
}
}
writeln!(f, "{:=^78}", "")?;
for (_, name) in &self.omitted_vars {
writeln!(f, "note: {} omitted because of collinearity", name)?;
}
Ok(())
}
}
impl PoissonResult {
pub fn x_data(&self) -> &Array2<f64> { &self._x_data }
pub fn predict_count(&self, x_new: &Array2<f64>) -> Array1<f64> {
let eta = x_new.dot(&self.params);
eta.mapv(f64::exp)
}
pub fn fitted_values(&self) -> Array1<f64> {
self.predict_count(&self._x_data)
}
pub fn residuals(&self) -> Array1<f64> {
let mu = self.fitted_values();
let n = self._y_data.len();
let mut resid = Array1::<f64>::zeros(n);
for i in 0..n {
let y = self._y_data[i];
let m = mu[i].max(1e-10);
let d = if y > 1e-10 {
2.0 * (y * (y / m).ln() - (y - m))
} else {
2.0 * m
};
resid[i] = d.max(0.0).sqrt() * (y - m).signum();
}
resid
}
pub fn pearson_residuals(&self) -> Array1<f64> {
let mu = self.fitted_values();
(&self._y_data - &mu) / mu.mapv(|m| m.max(1e-10).sqrt())
}
pub fn marginal_effects(&self, x: &Array2<f64>) -> Array1<f64> {
let mu = self.predict_count(x);
let mean_mu = mu.mean().unwrap_or(1.0);
&self.params * mean_mu
}
pub fn conf_int(&self, alpha: f64) -> Vec<(f64, f64)> {
let normal = Normal::new(0.0, 1.0).unwrap();
let z = normal.inverse_cdf(1.0 - alpha / 2.0);
(0..self.params.len())
.map(|i| {
(
self.params[i] - z * self.std_errors[i],
self.params[i] + z * self.std_errors[i],
)
})
.collect()
}
pub fn get_prediction(&self, x_new: &Array2<f64>, alpha: f64) -> PredictionResult {
let eta = x_new.dot(&self.params);
let mu = eta.mapv(f64::exp);
let normal = Normal::new(0.0, 1.0).unwrap();
let z = normal.inverse_cdf(1.0 - alpha / 2.0);
let n = x_new.nrows();
let mut se = Array1::<f64>::zeros(n);
for i in 0..n {
let x_i = x_new.row(i);
let var_eta: f64 = x_i
.iter()
.zip(self.std_errors.iter())
.map(|(xi, sei)| xi * xi * sei * sei)
.sum();
se[i] = mu[i] * var_eta.sqrt();
}
PredictionResult {
mean: mu.clone(),
se: se.clone(),
ci_lower: &mu - z * &se,
ci_upper: &mu + z * &se,
}
}
pub fn model_stats(&self) -> (f64, f64, f64, f64) {
(self.aic, self.bic, self.log_likelihood, self.pseudo_r2)
}
pub fn overdispersion_test(&self) -> Result<(f64, f64), GreenersError> {
let mu = self.fitted_values();
let n = self._y_data.len();
let mut dep = Array1::<f64>::zeros(n);
let mut reg = Array1::<f64>::zeros(n);
for i in 0..n {
let m = mu[i].max(1e-10);
dep[i] = ((self._y_data[i] - m).powi(2) - self._y_data[i]) / m;
reg[i] = m;
}
let reg_mean = reg.mean().unwrap_or(0.0);
let dep_mean = dep.mean().unwrap_or(0.0);
let mut num = 0.0;
let mut den = 0.0;
for i in 0..n {
num += (reg[i] - reg_mean) * (dep[i] - dep_mean);
den += (reg[i] - reg_mean).powi(2);
}
if den < 1e-15 {
return Err(GreenersError::SingularMatrix);
}
let alpha_hat = num / den;
let residuals: Array1<f64> = (0..n).map(|i| dep[i] - alpha_hat * reg[i]).collect();
let sigma2 = residuals.mapv(|r| r.powi(2)).sum() / (n - 1) as f64;
let se_alpha = (sigma2 / den).sqrt();
if se_alpha < 1e-15 {
return Err(GreenersError::SingularMatrix);
}
let t_stat = alpha_hat / se_alpha;
let normal = Normal::new(0.0, 1.0).unwrap();
let p_value = 2.0 * (1.0 - normal.cdf(t_stat.abs()));
Ok((t_stat, p_value))
}
pub fn goodness_of_fit(&self) -> (f64, f64) {
let chi2_p = 1.0
- statrs::distribution::ChiSquared::new(self.df_resid as f64)
.map(|d| {
use statrs::distribution::ContinuousCDF;
d.cdf(self.deviance)
})
.unwrap_or(0.0);
(self.deviance, chi2_p)
}
}
pub struct Poisson;
impl Poisson {
pub fn from_formula(
formula: &Formula,
data: &DataFrame,
cov_type: CovarianceType,
) -> Result<PoissonResult, GreenersError> {
let (y, x) = data.to_design_matrix(formula)?;
let var_names = data.formula_var_names(formula)?;
Self::fit_with_names(&y, &x, cov_type, Some(var_names))
}
pub fn fit(
y: &Array1<f64>,
x: &Array2<f64>,
cov_type: CovarianceType,
) -> Result<PoissonResult, GreenersError> {
Self::fit_with_names(y, x, cov_type, None)
}
pub fn fit_with_names(
y: &Array1<f64>,
x: &Array2<f64>,
cov_type: CovarianceType,
variable_names: Option<Vec<String>>,
) -> Result<PoissonResult, GreenersError> {
let glm_result = GLM::fit_with_names(y, x, Family::Poisson, cov_type, variable_names)?;
Ok(PoissonResult {
params: glm_result.params,
std_errors: glm_result.std_errors,
z_values: glm_result.z_values,
p_values: glm_result.p_values,
conf_lower: glm_result.conf_lower,
conf_upper: glm_result.conf_upper,
log_likelihood: glm_result.log_likelihood,
deviance: glm_result.deviance,
null_deviance: glm_result.null_deviance,
aic: glm_result.aic,
bic: glm_result.bic,
pseudo_r2: glm_result.pseudo_r2,
pearson_chi2: glm_result.pearson_chi2,
n_obs: glm_result.n_obs,
df_resid: glm_result.df_resid,
df_model: glm_result.df_model,
n_iter: glm_result.n_iter,
converged: glm_result.converged,
inference_type: glm_result.inference_type,
variable_names: glm_result.variable_names,
omitted_vars: glm_result.omitted_vars,
_x_data: glm_result._x_data.clone(),
_y_data: glm_result._y_data.clone(),
})
}
pub fn fit_with_exposure(
y: &Array1<f64>,
x: &Array2<f64>,
exposure: &Array1<f64>,
cov_type: CovarianceType,
) -> Result<PoissonResult, GreenersError> {
let n = x.nrows();
let k = x.ncols();
let mut x_with_offset = Array2::<f64>::zeros((n, k + 1));
x_with_offset.slice_mut(ndarray::s![.., ..k]).assign(x);
for i in 0..n {
x_with_offset[[i, k]] = exposure[i].max(1e-10).ln();
}
Self::fit(y, &x_with_offset, cov_type)
}
}