101 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
102 return std::log(mu / (1.0 - mu));
104 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
107 return std::log(std::max(1e-10, mu));
109 return 1.0 / std::max(1e-10, mu);
111 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
112 return std::log(-std::log(1.0 - mu));
133 return 1.0 / (1.0 + std::exp(-eta));
137 return std::min(std::exp(eta), 1e300);
139 if (std::abs(eta) < 1e-10)
return 1e10;
140 double result = 1.0 / eta;
141 return std::max(result, 1e-10);
144 return 1.0 - std::exp(-std::exp(eta));
166 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
167 return 1.0 / (mu * (1.0 - mu));
169 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
172 return 1.0 / std::max(1e-10, mu);
174 mu = std::max(1e-10, mu);
175 return -1.0 / (mu * mu);
177 mu = std::max(1e-8, std::min(1.0 - 1e-8, mu));
178 double neg_log_term = -std::log(1.0 - mu);
180 if (neg_log_term < 1e-10) {
181 throw std::runtime_error(
"statcpp::link_derivative: cloglog derivative undefined near mu=0");
184 return 1.0 / ((1.0 - mu) * neg_log_term);
206 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
207 return mu * (1.0 - mu);
209 return std::max(1e-10, mu);
211 mu = std::max(1e-10, mu);
232 return (y - mu) * (y - mu);
235 mu = std::max(1e-10, std::min(1.0 - 1e-10, mu));
238 d += y * std::log(y / mu);
241 d += (1.0 - y) * std::log((1.0 - y) / (1.0 - mu));
247 mu = std::max(1e-10, mu);
249 return 2.0 * (y * std::log(y / mu) - (y - mu));
256 mu = std::max(1e-10, mu);
257 return 2.0 * ((y - mu) / mu - std::log(y / mu));
260 return (y - mu) * (y - mu);
277 const std::vector<std::vector<double>>& X,
278 const std::vector<double>& z,
279 const std::vector<double>& w,
280 std::vector<std::vector<double>>& XtWX_inv)
282 std::size_t n = X.size();
283 std::size_t p = X[0].size();
286 std::vector<std::vector<double>> XtWX(p, std::vector<double>(p, 0.0));
287 for (std::size_t j = 0; j < p; ++j) {
288 for (std::size_t k = 0; k < p; ++k) {
289 for (std::size_t i = 0; i < n; ++i) {
290 XtWX[j][k] += X[i][j] * w[i] * X[i][k];
296 std::vector<double> XtWz(p, 0.0);
297 for (std::size_t j = 0; j < p; ++j) {
298 for (std::size_t i = 0; i < n; ++i) {
299 XtWz[j] += X[i][j] * w[i] * z[i];
304 std::vector<std::vector<double>> L(p, std::vector<double>(p, 0.0));
305 for (std::size_t i = 0; i < p; ++i) {
306 for (std::size_t j = 0; j <= i; ++j) {
308 for (std::size_t k = 0; k < j; ++k) {
309 sum += L[i][k] * L[j][k];
312 double val = XtWX[i][i] -
sum;
314 throw std::runtime_error(
"statcpp::glm: matrix is not positive definite");
316 L[i][j] = std::sqrt(val);
318 L[i][j] = (XtWX[i][j] -
sum) / L[j][j];
324 std::vector<double> y(p);
325 for (std::size_t i = 0; i < p; ++i) {
327 for (std::size_t j = 0; j < i; ++j) {
328 sum += L[i][j] * y[j];
330 y[i] = (XtWz[i] -
sum) / L[i][i];
334 std::vector<double>
beta(p);
335 for (std::size_t i = p; i > 0; --i) {
336 std::size_t idx = i - 1;
338 for (std::size_t j = idx + 1; j < p; ++j) {
341 beta[idx] = (y[idx] -
sum) / L[idx][idx];
345 XtWX_inv.assign(p, std::vector<double>(p, 0.0));
346 for (std::size_t col = 0; col < p; ++col) {
347 std::vector<double> e(p, 0.0);
351 std::vector<double> y_inv(p);
352 for (std::size_t i = 0; i < p; ++i) {
354 for (std::size_t j = 0; j < i; ++j) {
355 sum += L[i][j] * y_inv[j];
357 y_inv[i] = (e[i] -
sum) / L[i][i];
361 for (std::size_t i = p; i > 0; --i) {
362 std::size_t idx = i - 1;
364 for (std::size_t j = idx + 1; j < p; ++j) {
365 sum += L[j][idx] * XtWX_inv[j][col];
367 XtWX_inv[idx][col] = (y_inv[idx] -
sum) / L[idx][idx];
402 const std::vector<std::vector<double>>& X,
403 const std::vector<double>& y,
406 std::size_t max_iter = 100,
409 std::size_t n = X.size();
411 throw std::invalid_argument(
"statcpp::glm_fit: empty data");
414 throw std::invalid_argument(
"statcpp::glm_fit: X and y must have same number of observations");
417 std::size_t p = X[0].size();
418 for (
const auto& row : X) {
419 if (row.size() != p) {
420 throw std::invalid_argument(
"statcpp::glm_fit: inconsistent number of predictors");
424 std::size_t p_full = p + 1;
426 throw std::invalid_argument(
"statcpp::glm_fit: need more observations than predictors");
430 std::vector<std::vector<double>> X_design(n, std::vector<double>(p_full));
431 for (std::size_t i = 0; i < n; ++i) {
432 X_design[i][0] = 1.0;
433 for (std::size_t j = 0; j < p; ++j) {
434 X_design[i][j + 1] = X[i][j];
439 std::vector<double> mu(n);
440 std::vector<double> eta(n);
444 double y_mean_original = y_mean;
449 y_mean = std::max(0.01, std::min(0.99, y_mean));
453 y_mean = std::max(0.1, y_mean);
461 for (std::size_t i = 0; i < n; ++i) {
467 std::vector<double>
beta(p_full, 0.0);
470 std::vector<std::vector<double>> XtWX_inv;
471 bool converged =
false;
472 std::size_t iter = 0;
475 for (iter = 0; iter < max_iter; ++iter) {
477 std::vector<double> w(n);
478 std::vector<double> z(n);
480 for (std::size_t i = 0; i < n; ++i) {
485 w[i] = 1.0 / (var_i * g_prime * g_prime);
488 z[i] = eta[i] + (y[i] - mu[i]) * g_prime;
492 std::vector<double> beta_new;
495 }
catch (
const std::exception&) {
500 double max_change = 0.0;
501 for (std::size_t j = 0; j < p_full; ++j) {
502 double change = std::abs(beta_new[j] -
beta[j]);
503 if (std::abs(
beta[j]) > 1.0) {
504 change /= std::abs(
beta[j]);
506 max_change = std::max(max_change, change);
513 for (std::size_t i = 0; i < n; ++i) {
517 if (max_change < tol) {
525 std::vector<double> coefficient_se(p_full, std::numeric_limits<double>::quiet_NaN());
526 std::vector<double> z_statistics(p_full, std::numeric_limits<double>::quiet_NaN());
527 std::vector<double> p_values(p_full, std::numeric_limits<double>::quiet_NaN());
532 double dispersion = 1.0;
534 double pearson_chi2 = 0.0;
535 for (std::size_t i = 0; i < n; ++i) {
536 double resid = y[i] - mu[i];
539 double df_resid =
static_cast<double>(n) -
static_cast<double>(p_full);
540 dispersion = (df_resid > 0.0) ? pearson_chi2 / df_resid
541 : std::numeric_limits<double>::quiet_NaN();
544 if (!XtWX_inv.empty()) {
545 for (std::size_t j = 0; j < p_full; ++j) {
546 coefficient_se[j] = std::sqrt(dispersion * XtWX_inv[j][j]);
550 for (std::size_t j = 0; j < p_full; ++j) {
551 z_statistics[j] =
beta[j] / coefficient_se[j];
552 p_values[j] = 2.0 * (1.0 -
norm_cdf(std::abs(z_statistics[j])));
557 double residual_deviance = 0.0;
558 for (std::size_t i = 0; i < n; ++i) {
563 double null_deviance = 0.0;
564 for (std::size_t i = 0; i < n; ++i) {
569 double null_log_likelihood = 0.0;
572 for (std::size_t i = 0; i < n; ++i) {
573 double resid = y[i] - y_mean_original;
574 null_log_likelihood += -0.5 * resid * resid;
578 for (std::size_t i = 0; i < n; ++i) {
579 double p_null = std::max(1e-10, std::min(1.0 - 1e-10, y_mean_original));
580 null_log_likelihood += y[i] * std::log(p_null) + (1.0 - y[i]) * std::log(1.0 - p_null);
584 for (std::size_t i = 0; i < n; ++i) {
585 double mu_null = std::max(1e-10, y_mean_original);
586 null_log_likelihood += y[i] * std::log(mu_null) - mu_null - std::lgamma(y[i] + 1.0);
590 null_log_likelihood = -0.5 * null_deviance;
595 double log_likelihood = 0.0;
599 double sigma2 = residual_deviance /
static_cast<double>(n);
600 log_likelihood = -0.5 *
static_cast<double>(n) *
601 (std::log(2.0 *
pi) + std::log(sigma2) + 1.0);
605 for (std::size_t i = 0; i < n; ++i) {
606 double mu_i = std::max(1e-10, std::min(1.0 - 1e-10, mu[i]));
608 log_likelihood += y[i] * std::log(mu_i);
611 log_likelihood += (1.0 - y[i]) * std::log(1.0 - mu_i);
616 for (std::size_t i = 0; i < n; ++i) {
617 log_likelihood += y[i] * std::log(std::max(1e-10, mu[i])) - mu[i]
618 - std::lgamma(y[i] + 1.0);
625 double phi = residual_deviance / std::max(1.0,
static_cast<double>(n - p_full));
626 for (std::size_t i = 0; i < n; ++i) {
627 if (mu[i] > 0.0 && y[i] > 0.0) {
628 double nu = 1.0 / phi;
629 log_likelihood += nu * std::log(nu / mu[i]) - std::lgamma(nu)
630 + (nu - 1.0) * std::log(y[i]) - nu * y[i] / mu[i];
636 log_likelihood = -0.5 * residual_deviance;
641 double n_d =
static_cast<double>(n);
642 double k =
static_cast<double>(p_full);
646 double aic = -2.0 * log_likelihood + 2.0 * k;
647 double bic = -2.0 * log_likelihood + k * std::log(n_d);
650 beta, coefficient_se, z_statistics, p_values,
651 null_deviance, residual_deviance,
652 static_cast<double>(n - 1),
static_cast<double>(n - p_full),
653 aic,
bic, log_likelihood, null_log_likelihood,
676 const std::vector<std::vector<double>>& X,
677 const std::vector<double>& y,
678 std::size_t max_iter = 100,
685 for (
double yi : y) {
686 if (yi < 0.0 || yi > 1.0) {
687 throw std::invalid_argument(
"statcpp::logistic_regression: y must be in [0, 1]");
708 throw std::invalid_argument(
"statcpp::predict_probability: model must be binomial");
711 throw std::invalid_argument(
"statcpp::predict_probability: x dimension mismatch");
715 for (std::size_t i = 0; i < x.size(); ++i) {
734 throw std::invalid_argument(
"statcpp::odds_ratios: requires logistic regression model");
737 std::vector<double> or_values(model.
coefficients.size() - 1);
738 for (std::size_t i = 1; i < model.
coefficients.size(); ++i) {
755 const glm_result& model,
double confidence = 0.95)
758 throw std::invalid_argument(
"statcpp::odds_ratios_ci: requires logistic regression model");
760 if (confidence <= 0.0 || confidence >= 1.0) {
761 throw std::invalid_argument(
"statcpp::odds_ratios_ci: confidence must be in (0, 1)");
766 std::vector<std::pair<double, double>> ci(model.
coefficients.size() - 1);
767 for (std::size_t i = 1; i < model.
coefficients.size(); ++i) {
770 double lower = std::exp(
beta - z * se);
771 double upper = std::exp(
beta + z * se);
772 ci[i - 1] = {lower, upper};
795 const std::vector<std::vector<double>>& X,
796 const std::vector<double>& y,
797 std::size_t max_iter = 100,
804 for (
double yi : y) {
806 throw std::invalid_argument(
"statcpp::poisson_regression: y must be non-negative");
827 throw std::invalid_argument(
"statcpp::predict_count: model must be Poisson");
830 throw std::invalid_argument(
"statcpp::predict_count: x dimension mismatch");
834 for (std::size_t i = 0; i < x.size(); ++i) {
853 throw std::invalid_argument(
"statcpp::incidence_rate_ratios: requires Poisson regression model");
857 for (std::size_t i = 1; i < model.
coefficients.size(); ++i) {
892 const std::vector<std::vector<double>>& X,
893 const std::vector<double>& y)
895 std::size_t n = X.size();
897 throw std::invalid_argument(
"statcpp::compute_glm_residuals: X and y must have same length");
900 std::vector<double> response(n);
901 std::vector<double> pearson(n);
902 std::vector<double> deviance_res(n);
903 std::vector<double> working(n);
905 for (std::size_t i = 0; i < n; ++i) {
908 for (std::size_t j = 0; j < X[i].size(); ++j) {
915 response[i] = y[i] - mu;
919 pearson[i] = response[i] / std::sqrt(
var);
923 int sign = (y[i] >= mu) ? 1 : -1;
924 deviance_res[i] = sign * std::sqrt(d);
928 working[i] = response[i] * g_prime;
931 return {response, pearson, deviance_res, working};
947 const std::vector<std::vector<double>>& X,
948 const std::vector<double>& y)
951 throw std::invalid_argument(
"statcpp::overdispersion_test: requires Poisson model");
957 double pearson_chi2 = 0.0;
958 for (
double r : residuals.pearson) {
959 pearson_chi2 += r * r;
963 double dispersion = pearson_chi2 / model.
df_residual;
1000 const std::vector<double>& y,
1003 double n_d =
static_cast<double>(n);
1007 double ll_saturated = 0.0;
1021 for (std::size_t i = 0; i < n; ++i) {
1023 ll_saturated += y[i] * std::log(y[i]) - y[i] - std::lgamma(y[i] + 1.0);
1040 double r2_cox_snell = 1.0 - std::exp(2.0 * (ll_null - ll_model) / n_d);
1041 double r2_max = 1.0 - std::exp(2.0 * ll_null / n_d);
1043 if (r2_max == 0.0)
return 0.0;
1045 return r2_cox_snell / r2_max;
Basic statistical computation functions.
Continuous distribution functions.
Linear regression analysis.
double link_derivative(double mu, link_function link)
Derivative of link function d(eta)/d(mu) = g'(mu)
double variance_function(double mu, distribution_family family)
Variance function V(mu)
double link_transform(double mu, link_function link)
Link function g(mu) -> eta.
double deviance_residual(double y, double mu, distribution_family family)
Calculate deviance (for a single observation)
double inverse_link(double eta, link_function link)
Inverse link function g^{-1}(eta) -> mu.
std::vector< double > solve_weighted_least_squares(const std::vector< std::vector< double > > &X, const std::vector< double > &z, const std::vector< double > &w, std::vector< std::vector< double > > &XtWX_inv)
Solve weighted least squares.
std::vector< double > matrix_vector_multiply(const std::vector< std::vector< double > > &A, const std::vector< double > &v)
Calculate matrix-vector product.
void validate_no_intercept_column(const std::vector< std::vector< double > > &X, const char *func_name)
Check that X data does not contain an intercept column.
glm_residuals compute_glm_residuals(const glm_result &model, const std::vector< std::vector< double > > &X, const std::vector< double > &y)
Calculate GLM residuals.
double predict_probability(const glm_result &model, const std::vector< double > &x)
Probability prediction with logistic regression.
constexpr double pi
Pi constant.
double normal_pdf(double x, double mu=0.0, double sigma=1.0)
Normal distribution probability density function (PDF)
auto sum(Iterator first, Iterator last)
Sum.
double var(Iterator first, Iterator last, std::size_t ddof=0)
Variance (ddof = Delta Degrees of Freedom)
double norm_cdf(double x)
Standard normal CDF.
double norm_quantile(double p)
Standard normal quantile function.
glm_result poisson_regression(const std::vector< std::vector< double > > &X, const std::vector< double > &y, std::size_t max_iter=100, double tol=1e-8)
Poisson regression.
double beta(double a, double b)
Beta function.
std::vector< double > odds_ratios(const glm_result &model)
Calculate odds ratios.
glm_result glm_fit(const std::vector< std::vector< double > > &X, const std::vector< double > &y, distribution_family family=distribution_family::gaussian, link_function link=link_function::identity, std::size_t max_iter=100, double tol=1e-8)
Fit a generalized linear model.
double mean(Iterator first, Iterator last)
Arithmetic mean.
double bic(double log_likelihood, std::size_t n, std::size_t k)
Calculate BIC (Bayesian Information Criterion)
double predict_count(const glm_result &model, const std::vector< double > &x)
Expected count prediction with Poisson regression.
double overdispersion_test(const glm_result &model, const std::vector< std::vector< double > > &X, const std::vector< double > &y)
Overdispersion test (for Poisson regression)
double aic(double log_likelihood, std::size_t k)
Calculate AIC (Akaike Information Criterion)
std::vector< double > incidence_rate_ratios(const glm_result &model)
Calculate Incidence Rate Ratios.
glm_result logistic_regression(const std::vector< std::vector< double > > &X, const std::vector< double > &y, std::size_t max_iter=100, double tol=1e-8)
Logistic regression.
distribution_family
Distribution family.
@ poisson
Poisson distribution.
@ gaussian
Gaussian (normal) distribution.
@ gamma_family
Gamma distribution (gamma_family because gamma is a reserved word)
@ binomial
Binomial distribution.
double pseudo_r_squared_nagelkerke(const glm_result &model, const std::vector< double > &y, std::size_t n)
Nagelkerke's pseudo R-squared.
std::vector< std::pair< double, double > > odds_ratios_ci(const glm_result &model, double confidence=0.95)
Confidence intervals for odds ratios.
link_function
Link function types.
@ logit
Logit link (logistic regression)
@ inverse
Inverse link (Gamma regression)
@ cloglog
Complementary log-log link.
@ log
Log link (Poisson regression)
@ identity
Identity link (linear regression)
double pseudo_r_squared_mcfadden(const glm_result &model)
McFadden's pseudo R-squared.
std::vector< double > deviance
Deviance residuals.
std::vector< double > working
Working residuals.
std::vector< double > response
Response residuals (y - mu)
std::vector< double > pearson
Pearson residuals.
std::vector< double > z_statistics
z-statistics (or Wald statistics)
std::size_t iterations
Number of iterations until convergence.
link_function link
Link function used.
double df_residual
Residual degrees of freedom.
distribution_family family
Distribution family used.
std::vector< double > coefficient_se
Standard errors of coefficients.
std::vector< double > coefficients
Regression coefficients.
bool converged
Whether convergence was achieved.
double null_deviance
Null deviance.
std::vector< double > p_values
p-values
double df_null
Null model degrees of freedom.
double residual_deviance
Residual deviance.
double null_log_likelihood
Null model log-likelihood.
double log_likelihood
Log-likelihood.