use super::features::{compute_mean, compute_std, polynomial_feature_names, polynomial_features};
use super::types::{PolynomialFit, PolynomialOptions};
use crate::core::ols_regression;
use crate::error::{Error, Result};
pub fn polynomial_regression(
y: &[f64],
x: &[f64],
options: &PolynomialOptions,
) -> Result<PolynomialFit> {
if y.len() != x.len() {
return Err(Error::DimensionMismatch(format!(
"Length of y ({}) must match length of x ({})",
y.len(),
x.len()
)));
}
if y.len() < 2 {
return Err(Error::InsufficientData {
required: 2,
available: y.len(),
});
}
if options.degree < 1 {
return Err(Error::InvalidInput(
"Polynomial degree must be at least 1".into(),
));
}
let x_mean = if options.center {
compute_mean(x)
} else {
0.0
};
let x_centered: Vec<f64> = if options.center {
x.iter().map(|&xi| xi - x_mean).collect()
} else {
x.to_vec()
};
let poly_features_raw = polynomial_features(
&x_centered,
options.degree,
false, 0.0,
)?;
let mut feature_means: Vec<f64> = Vec::new();
let mut feature_stds: Vec<f64> = Vec::new();
let x_feature: Vec<f64> = if options.standardize {
let mean = compute_mean(&x_centered);
let std_val = compute_std(&x_centered, mean);
if std_val < 1e-10 {
return Err(Error::InvalidInput(
"Cannot standardize x: standard deviation is too small or zero".into(),
));
}
feature_means.push(mean);
feature_stds.push(std_val);
x_centered.iter().map(|&v| (v - mean) / std_val).collect()
} else {
x_centered.clone()
};
let mut poly_features_processed: Vec<Vec<f64>> =
Vec::with_capacity(poly_features_raw.len());
for feature in &poly_features_raw {
if options.standardize {
let mean = compute_mean(feature);
let std_val = compute_std(feature, mean);
if std_val < 1e-10 {
feature_means.push(mean);
feature_stds.push(1.0);
poly_features_processed
.push(feature.iter().map(|_| 0.0).collect());
} else {
feature_means.push(mean);
feature_stds.push(std_val);
poly_features_processed
.push(feature.iter().map(|&v| (v - mean) / std_val).collect());
}
} else {
poly_features_processed.push(feature.clone());
}
}
let mut all_predictor_vecs: Vec<Vec<f64>> = Vec::with_capacity(options.degree);
all_predictor_vecs.push(x_feature);
all_predictor_vecs.extend(poly_features_processed);
let feature_names = polynomial_feature_names(options.degree, options.center);
let mut all_names = vec!["Intercept".to_string()];
all_names.extend(feature_names.iter().cloned());
let ols_output = ols_regression(y, &all_predictor_vecs, &all_names)?;
let x_std = feature_stds.first().copied().unwrap_or(1.0);
Ok(PolynomialFit {
ols_output,
degree: options.degree,
centered: options.center,
x_mean,
x_std,
standardized: options.standardize,
n_features: options.degree,
feature_names: all_names,
feature_means,
feature_stds,
})
}