use crate::{CovarianceType, GreenersError, OLS};
use ndarray::{Array1, Array2};
use std::fmt;
#[derive(Debug)]
pub struct DidResult {
pub att: f64, pub std_error: f64, pub t_stat: f64,
pub p_value: f64,
pub n_obs: usize,
pub r_squared: f64,
pub control_pre_mean: f64, pub control_post_mean: f64, pub treated_pre_mean: f64, pub treated_post_mean: f64, pub cov_type: CovarianceType,
}
impl fmt::Display for DidResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"\n{:=^78}",
" Difference-in-Differences (2x2 Canonical) "
)?;
writeln!(
f,
"{:<20} {:>15.4} || {:<20} {:>15.4}",
"ATT (Effect):", self.att, "R-squared:", self.r_squared
)?;
writeln!(
f,
"{:<20} {:>15.4} || {:<20} {:>15.4}",
"Std. Error:", self.std_error, "P-value:", self.p_value
)?;
writeln!(
f,
"{:<20} {:>15.4} || {:<20} {:>15}",
"t-statistic:", self.t_stat, "Observations:", self.n_obs
)?;
writeln!(f, "\n{:-^78}", " Group Means ")?;
writeln!(
f,
"Control Group (Pre): {:>10.4} | Control Group (Post): {:>10.4}",
self.control_pre_mean, self.control_post_mean
)?;
writeln!(
f,
"Treated Group (Pre): {:>10.4} | Treated Group (Post): {:>10.4}",
self.treated_pre_mean, self.treated_post_mean
)?;
writeln!(
f,
"Parallel Trend Diff: {:>10.4} (If > 0, Control grew more than Treated Pre-trend)",
(self.control_post_mean - self.control_pre_mean)
- (self.treated_post_mean - self.treated_pre_mean - self.att)
)?;
writeln!(f, "{:=^78}", "")
}
}
pub struct DiffInDiff;
impl DiffInDiff {
pub fn fit(
y: &Array1<f64>,
treated: &Array1<f64>,
post: &Array1<f64>,
cov_type: CovarianceType,
) -> Result<DidResult, GreenersError> {
let n = y.len();
if treated.len() != n || post.len() != n {
return Err(GreenersError::ShapeMismatch(
"Input arrays must have same length".into(),
));
}
let mut x_mat = Array2::<f64>::zeros((n, 4));
let mut interaction = Array1::<f64>::zeros(n);
let mut sum_c_pre = 0.0;
let mut n_c_pre = 0.0;
let mut sum_c_post = 0.0;
let mut n_c_post = 0.0;
let mut sum_t_pre = 0.0;
let mut n_t_pre = 0.0;
let mut sum_t_post = 0.0;
let mut n_t_post = 0.0;
for i in 0..n {
let t = treated[i];
let p = post[i];
let inter = t * p;
x_mat[[i, 0]] = 1.0; x_mat[[i, 1]] = t; x_mat[[i, 2]] = p; x_mat[[i, 3]] = inter;
interaction[i] = inter;
let val = y[i];
if t == 0.0 && p == 0.0 {
sum_c_pre += val;
n_c_pre += 1.0;
} else if t == 0.0 && p == 1.0 {
sum_c_post += val;
n_c_post += 1.0;
} else if t == 1.0 && p == 0.0 {
sum_t_pre += val;
n_t_pre += 1.0;
} else if t == 1.0 && p == 1.0 {
sum_t_post += val;
n_t_post += 1.0;
}
}
let ols = OLS::fit(y, &x_mat, cov_type)?;
let att = ols.params[3];
let std_error = ols.std_errors[3];
let t_stat = ols.t_values[3];
let p_value = ols.p_values[3];
Ok(DidResult {
att,
std_error,
t_stat,
p_value,
n_obs: n,
r_squared: ols.r_squared,
control_pre_mean: if n_c_pre > 0.0 {
sum_c_pre / n_c_pre
} else {
0.0
},
control_post_mean: if n_c_post > 0.0 {
sum_c_post / n_c_post
} else {
0.0
},
treated_pre_mean: if n_t_pre > 0.0 {
sum_t_pre / n_t_pre
} else {
0.0
},
treated_post_mean: if n_t_post > 0.0 {
sum_t_post / n_t_post
} else {
0.0
},
cov_type,
})
}
}