statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
model_selection.hpp
Go to the documentation of this file.
1
9#pragma once
10
15
16#include <algorithm>
17#include <cmath>
18#include <cstddef>
19#include <iterator>
20#include <limits>
21#include <numeric>
22#include <stdexcept>
23#include <utility>
24#include <vector>
25
26namespace statcpp {
27
28// ============================================================================
29// Model Selection Criteria
30// ============================================================================
31
41inline double aic(double log_likelihood, std::size_t k)
42{
43 return -2.0 * log_likelihood + 2.0 * static_cast<double>(k);
44}
45
55inline double aic_linear(const simple_regression_result& model, std::size_t n)
56{
57 // sigma^2 = SS_res / n (MLE version)
58 double sigma2 = model.ss_residual / static_cast<double>(n);
59 double n_d = static_cast<double>(n);
60
61 // Log-likelihood: -n/2 * (log(2*pi) + log(sigma^2) + 1)
62 double ll = -0.5 * n_d * (std::log(2.0 * pi) + std::log(sigma2) + 1.0);
63
64 return aic(ll, 3); // k = 2 (coefficients) + 1 (sigma^2)
65}
66
76inline double aic_linear(const multiple_regression_result& model, std::size_t n)
77{
78 double sigma2 = model.ss_residual / static_cast<double>(n);
79 double n_d = static_cast<double>(n);
80
81 double ll = -0.5 * n_d * (std::log(2.0 * pi) + std::log(sigma2) + 1.0);
82
83 std::size_t k = model.coefficients.size() + 1; // coefficients + sigma^2
84 return aic(ll, k);
85}
86
99inline double aicc(double log_likelihood, std::size_t n, std::size_t k)
100{
101 double n_d = static_cast<double>(n);
102 double k_d = static_cast<double>(k);
103
104 if (n_d <= k_d + 1.0) {
105 throw std::invalid_argument("statcpp::aicc: n must be greater than k + 1");
106 }
107
108 return aic(log_likelihood, k) + (2.0 * k_d * (k_d + 1.0)) / (n_d - k_d - 1.0);
109}
110
122inline double bic(double log_likelihood, std::size_t n, std::size_t k)
123{
124 return -2.0 * log_likelihood + static_cast<double>(k) * std::log(static_cast<double>(n));
125}
126
136inline double bic_linear(const simple_regression_result& model, std::size_t n)
137{
138 double sigma2 = model.ss_residual / static_cast<double>(n);
139 double n_d = static_cast<double>(n);
140
141 double ll = -0.5 * n_d * (std::log(2.0 * pi) + std::log(sigma2) + 1.0);
142
143 return bic(ll, n, 3);
144}
145
155inline double bic_linear(const multiple_regression_result& model, std::size_t n)
156{
157 double sigma2 = model.ss_residual / static_cast<double>(n);
158 double n_d = static_cast<double>(n);
159
160 double ll = -0.5 * n_d * (std::log(2.0 * pi) + std::log(sigma2) + 1.0);
161
162 std::size_t k = model.coefficients.size() + 1;
163 return bic(ll, n, k);
164}
165
182template <typename IteratorX, typename IteratorY>
183double press_statistic(IteratorX x_first, IteratorX x_last,
184 IteratorY y_first, IteratorY y_last,
185 const simple_regression_result& model)
186{
187 auto n = statcpp::count(x_first, x_last);
188 auto n_y = statcpp::count(y_first, y_last);
189 if (n != n_y) {
190 throw std::invalid_argument("statcpp::press_statistic: x and y must have same length");
191 }
192
193 double n_d = static_cast<double>(n);
194 double mean_x = statcpp::mean(x_first, x_last);
195
196 // Calculate Sxx
197 double sxx = 0.0;
198 for (auto it = x_first; it != x_last; ++it) {
199 double dx = static_cast<double>(*it) - mean_x;
200 sxx += dx * dx;
201 }
202
203 double press = 0.0;
204 auto it_x = x_first;
205 auto it_y = y_first;
206 for (; it_x != x_last; ++it_x, ++it_y) {
207 double x_i = static_cast<double>(*it_x);
208 double y_i = static_cast<double>(*it_y);
209
210 double y_hat = predict(model, x_i);
211 double residual = y_i - y_hat;
212
213 // Leverage h_ii
214 double dx = x_i - mean_x;
215 double h_ii = 1.0 / n_d + dx * dx / sxx;
216
217 // PRESS residual = residual / (1 - h_ii)
218 double press_residual = residual / (1.0 - h_ii);
219 press += press_residual * press_residual;
220 }
221
222 return press;
223}
224
225// ============================================================================
226// Cross-Validation
227// ============================================================================
228
232struct cv_result {
233 double mean_error;
234 double se_error;
235 std::vector<double> fold_errors;
236 std::size_t n_folds;
237};
238
250inline std::vector<std::vector<std::size_t>> create_cv_folds(
251 std::size_t n, std::size_t k, bool shuffle = true)
252{
253 if (k < 2) {
254 throw std::invalid_argument("statcpp::create_cv_folds: k must be at least 2");
255 }
256 if (k > n) {
257 throw std::invalid_argument("statcpp::create_cv_folds: k cannot exceed n");
258 }
259
260 std::vector<std::size_t> indices(n);
261 std::iota(indices.begin(), indices.end(), 0);
262
263 if (shuffle) {
264 std::shuffle(indices.begin(), indices.end(), get_random_engine());
265 }
266
267 std::vector<std::vector<std::size_t>> folds(k);
268 std::size_t fold_size = n / k;
269 std::size_t remainder = n % k;
270
271 std::size_t current = 0;
272 for (std::size_t i = 0; i < k; ++i) {
273 std::size_t this_fold_size = fold_size + (i < remainder ? 1 : 0);
274 for (std::size_t j = 0; j < this_fold_size; ++j) {
275 folds[i].push_back(indices[current++]);
276 }
277 }
278
279 return folds;
280}
281
295 const std::vector<std::vector<double>>& X,
296 const std::vector<double>& y,
297 std::size_t k = 5)
298{
299 std::size_t n = X.size();
300 if (n != y.size()) {
301 throw std::invalid_argument("statcpp::cross_validate_linear: X and y must have same size");
302 }
303
304 auto folds = create_cv_folds(n, k, true);
305 std::vector<double> fold_errors(k);
306
307 for (std::size_t fold = 0; fold < k; ++fold) {
308 // Separate test and training sets
309 std::vector<std::size_t> test_idx = folds[fold];
310 std::vector<std::size_t> train_idx;
311 for (std::size_t f = 0; f < k; ++f) {
312 if (f != fold) {
313 train_idx.insert(train_idx.end(), folds[f].begin(), folds[f].end());
314 }
315 }
316
317 // Training data
318 std::vector<std::vector<double>> X_train(train_idx.size());
319 std::vector<double> y_train(train_idx.size());
320 for (std::size_t i = 0; i < train_idx.size(); ++i) {
321 X_train[i] = X[train_idx[i]];
322 y_train[i] = y[train_idx[i]];
323 }
324
325 // Test data
326 std::vector<std::vector<double>> X_test(test_idx.size());
327 std::vector<double> y_test(test_idx.size());
328 for (std::size_t i = 0; i < test_idx.size(); ++i) {
329 X_test[i] = X[test_idx[i]];
330 y_test[i] = y[test_idx[i]];
331 }
332
333 // Fit model
334 try {
335 auto model = multiple_linear_regression(X_train, y_train);
336
337 // Calculate test error
338 double mse = 0.0;
339 for (std::size_t i = 0; i < test_idx.size(); ++i) {
340 double pred = predict(model, X_test[i]);
341 double err = y_test[i] - pred;
342 mse += err * err;
343 }
344 fold_errors[fold] = mse / static_cast<double>(test_idx.size());
345 } catch (...) {
346 fold_errors[fold] = std::numeric_limits<double>::infinity();
347 }
348 }
349
350 double mean_error = statcpp::mean(fold_errors.begin(), fold_errors.end());
351 double se_error = statcpp::sample_stddev(fold_errors.begin(), fold_errors.end())
352 / std::sqrt(static_cast<double>(k));
353
354 return {mean_error, se_error, fold_errors, k};
355}
356
367 const std::vector<std::vector<double>>& X,
368 const std::vector<double>& y)
369{
370 return cross_validate_linear(X, y, X.size());
371}
372
373// ============================================================================
374// Regularized Regression
375// ============================================================================
376
381 std::vector<double> coefficients;
382 double lambda;
383 double mse;
384 std::size_t iterations;
386};
387
388namespace detail {
389
394 const std::vector<std::vector<double>>& X,
395 std::vector<std::vector<double>>& X_scaled,
396 std::vector<double>& X_mean,
397 std::vector<double>& X_std,
398 std::size_t n, std::size_t p)
399{
400 for (std::size_t j = 0; j < p; ++j) {
401 double sum = 0.0;
402 for (std::size_t i = 0; i < n; ++i) {
403 sum += X[i][j];
404 }
405 X_mean[j] = sum / static_cast<double>(n);
406
407 double ss = 0.0;
408 for (std::size_t i = 0; i < n; ++i) {
409 double d = X[i][j] - X_mean[j];
410 ss += d * d;
411 }
412 X_std[j] = std::sqrt(ss / static_cast<double>(n));
413 if (X_std[j] < 1e-10) X_std[j] = 1.0;
414
415 for (std::size_t i = 0; i < n; ++i) {
416 X_scaled[i][j] = (X[i][j] - X_mean[j]) / X_std[j];
417 }
418 }
419}
420
424inline std::vector<double> rescale_coefficients(
425 const std::vector<double>& beta,
426 const std::vector<double>& X_mean,
427 const std::vector<double>& X_std,
428 double y_mean, std::size_t p, bool standardize)
429{
430 std::vector<double> coefficients(p + 1);
431 if (standardize) {
432 coefficients[0] = y_mean;
433 for (std::size_t j = 0; j < p; ++j) {
434 coefficients[j + 1] = beta[j] / X_std[j];
435 coefficients[0] -= coefficients[j + 1] * X_mean[j];
436 }
437 } else {
438 coefficients[0] = y_mean;
439 for (std::size_t j = 0; j < p; ++j) {
440 coefficients[j + 1] = beta[j];
441 }
442 }
443 return coefficients;
444}
445
446} // namespace detail
447
464 const std::vector<std::vector<double>>& X,
465 const std::vector<double>& y,
466 double lambda,
467 bool standardize = true,
468 std::size_t max_iter = 1000,
469 double tol = 1e-6)
470{
471 // Verify that X does not contain an intercept column (from linear_regression.hpp)
473
474 if (lambda < 0.0) {
475 throw std::invalid_argument("statcpp::ridge_regression: lambda must be non-negative");
476 }
477
478 std::size_t n = X.size();
479 if (n == 0) {
480 throw std::invalid_argument("statcpp::ridge_regression: empty data");
481 }
482 if (n != y.size()) {
483 throw std::invalid_argument("statcpp::ridge_regression: X and y must have same size");
484 }
485
486 std::size_t p = X[0].size();
487
488 // Data standardization
489 std::vector<double> X_mean(p, 0.0);
490 std::vector<double> X_std(p, 1.0);
491 double y_mean = statcpp::mean(y.begin(), y.end());
492
493 std::vector<std::vector<double>> X_scaled = X;
494 std::vector<double> y_centered(n);
495
496 if (standardize) {
497 detail::standardize_features(X, X_scaled, X_mean, X_std, n, p);
498 }
499
500 for (std::size_t i = 0; i < n; ++i) {
501 y_centered[i] = y[i] - y_mean;
502 }
503
504 // Ridge closed-form solution: beta = (X'X + lambda*I)^{-1} X'y
505 // Solve with coordinate descent
506 std::vector<double> beta(p, 0.0);
507 std::vector<double> residuals = y_centered;
508
509 std::size_t iter = 0;
510 bool converged = false;
511
512 for (iter = 0; iter < max_iter; ++iter) {
513 double max_change = 0.0;
514
515 for (std::size_t j = 0; j < p; ++j) {
516 // Add back the contribution of this variable to residuals
517 for (std::size_t i = 0; i < n; ++i) {
518 residuals[i] += X_scaled[i][j] * beta[j];
519 }
520
521 // Calculate X_j'r
522 double xr = 0.0;
523 double xx = 0.0;
524 for (std::size_t i = 0; i < n; ++i) {
525 xr += X_scaled[i][j] * residuals[i];
526 xx += X_scaled[i][j] * X_scaled[i][j];
527 }
528
529 // Ridge update
530 double beta_new = xr / (xx + lambda);
531 double change = std::abs(beta_new - beta[j]);
532 max_change = std::max(max_change, change);
533
534 beta[j] = beta_new;
535
536 // Update residuals
537 for (std::size_t i = 0; i < n; ++i) {
538 residuals[i] -= X_scaled[i][j] * beta[j];
539 }
540 }
541
542 if (max_change < tol) {
543 converged = true;
544 ++iter;
545 break;
546 }
547 }
548
549 // Transform coefficients back to original scale
550 auto coefficients = detail::rescale_coefficients(beta, X_mean, X_std, y_mean, p, standardize);
551
552 // Calculate MSE
553 double mse = 0.0;
554 for (std::size_t i = 0; i < n; ++i) {
555 double pred = coefficients[0];
556 for (std::size_t j = 0; j < p; ++j) {
557 pred += coefficients[j + 1] * X[i][j];
558 }
559 double err = y[i] - pred;
560 mse += err * err;
561 }
562 mse /= static_cast<double>(n);
563
564 return {coefficients, lambda, mse, iter, converged};
565}
566
583 const std::vector<std::vector<double>>& X,
584 const std::vector<double>& y,
585 double lambda,
586 bool standardize = true,
587 std::size_t max_iter = 1000,
588 double tol = 1e-6)
589{
590 // Verify that X does not contain an intercept column
592
593 if (lambda < 0.0) {
594 throw std::invalid_argument("statcpp::lasso_regression: lambda must be non-negative");
595 }
596
597 std::size_t n = X.size();
598 if (n == 0) {
599 throw std::invalid_argument("statcpp::lasso_regression: empty data");
600 }
601 if (n != y.size()) {
602 throw std::invalid_argument("statcpp::lasso_regression: X and y must have same size");
603 }
604
605 std::size_t p = X[0].size();
606
607 // Data standardization
608 std::vector<double> X_mean(p, 0.0);
609 std::vector<double> X_std(p, 1.0);
610 double y_mean = statcpp::mean(y.begin(), y.end());
611
612 std::vector<std::vector<double>> X_scaled = X;
613 std::vector<double> y_centered(n);
614
615 if (standardize) {
616 detail::standardize_features(X, X_scaled, X_mean, X_std, n, p);
617 }
618
619 for (std::size_t i = 0; i < n; ++i) {
620 y_centered[i] = y[i] - y_mean;
621 }
622
623 // Coordinate descent
624 std::vector<double> beta(p, 0.0);
625 std::vector<double> residuals = y_centered;
626
627 // Soft thresholding function
628 auto soft_threshold = [](double x, double t) -> double {
629 if (x > t) return x - t;
630 if (x < -t) return x + t;
631 return 0.0;
632 };
633
634 std::size_t iter = 0;
635 bool converged = false;
636
637 for (iter = 0; iter < max_iter; ++iter) {
638 double max_change = 0.0;
639
640 for (std::size_t j = 0; j < p; ++j) {
641 // Add back the contribution of this variable to residuals
642 for (std::size_t i = 0; i < n; ++i) {
643 residuals[i] += X_scaled[i][j] * beta[j];
644 }
645
646 // Calculate X_j'r
647 double xr = 0.0;
648 double xx = 0.0;
649 for (std::size_t i = 0; i < n; ++i) {
650 xr += X_scaled[i][j] * residuals[i];
651 xx += X_scaled[i][j] * X_scaled[i][j];
652 }
653
654 // Lasso update (soft thresholding)
655 double beta_new = soft_threshold(xr, lambda) / xx;
656 double change = std::abs(beta_new - beta[j]);
657 max_change = std::max(max_change, change);
658
659 beta[j] = beta_new;
660
661 // Update residuals
662 for (std::size_t i = 0; i < n; ++i) {
663 residuals[i] -= X_scaled[i][j] * beta[j];
664 }
665 }
666
667 if (max_change < tol) {
668 converged = true;
669 ++iter;
670 break;
671 }
672 }
673
674 // Transform coefficients back to original scale
675 auto coefficients = detail::rescale_coefficients(beta, X_mean, X_std, y_mean, p, standardize);
676
677 // Calculate MSE
678 double mse = 0.0;
679 for (std::size_t i = 0; i < n; ++i) {
680 double pred = coefficients[0];
681 for (std::size_t j = 0; j < p; ++j) {
682 pred += coefficients[j + 1] * X[i][j];
683 }
684 double err = y[i] - pred;
685 mse += err * err;
686 }
687 mse /= static_cast<double>(n);
688
689 return {coefficients, lambda, mse, iter, converged};
690}
691
709 const std::vector<std::vector<double>>& X,
710 const std::vector<double>& y,
711 double lambda,
712 double alpha = 0.5, // L1 ratio (0 = Ridge, 1 = Lasso)
713 bool standardize = true,
714 std::size_t max_iter = 1000,
715 double tol = 1e-6)
716{
717 // Verify that X does not contain an intercept column
718 statcpp::detail::validate_no_intercept_column(X, "elastic_net_regression");
719
720 if (lambda < 0.0) {
721 throw std::invalid_argument("statcpp::elastic_net_regression: lambda must be non-negative");
722 }
723 if (alpha < 0.0 || alpha > 1.0) {
724 throw std::invalid_argument("statcpp::elastic_net_regression: alpha must be in [0, 1]");
725 }
726
727 std::size_t n = X.size();
728 if (n == 0) {
729 throw std::invalid_argument("statcpp::elastic_net_regression: empty data");
730 }
731 if (n != y.size()) {
732 throw std::invalid_argument("statcpp::elastic_net_regression: X and y must have same size");
733 }
734
735 std::size_t p = X[0].size();
736
737 // Data standardization
738 std::vector<double> X_mean(p, 0.0);
739 std::vector<double> X_std(p, 1.0);
740 double y_mean = statcpp::mean(y.begin(), y.end());
741
742 std::vector<std::vector<double>> X_scaled = X;
743 std::vector<double> y_centered(n);
744
745 if (standardize) {
746 detail::standardize_features(X, X_scaled, X_mean, X_std, n, p);
747 }
748
749 for (std::size_t i = 0; i < n; ++i) {
750 y_centered[i] = y[i] - y_mean;
751 }
752
753 // Coordinate descent
754 std::vector<double> beta(p, 0.0);
755 std::vector<double> residuals = y_centered;
756
757 auto soft_threshold = [](double x, double t) -> double {
758 if (x > t) return x - t;
759 if (x < -t) return x + t;
760 return 0.0;
761 };
762
763 double lambda1 = alpha * lambda; // L1 penalty
764 double lambda2 = (1.0 - alpha) * lambda; // L2 penalty
765
766 std::size_t iter = 0;
767 bool converged = false;
768
769 for (iter = 0; iter < max_iter; ++iter) {
770 double max_change = 0.0;
771
772 for (std::size_t j = 0; j < p; ++j) {
773 for (std::size_t i = 0; i < n; ++i) {
774 residuals[i] += X_scaled[i][j] * beta[j];
775 }
776
777 double xr = 0.0;
778 double xx = 0.0;
779 for (std::size_t i = 0; i < n; ++i) {
780 xr += X_scaled[i][j] * residuals[i];
781 xx += X_scaled[i][j] * X_scaled[i][j];
782 }
783
784 // Elastic Net update
785 double beta_new = soft_threshold(xr, lambda1) / (xx + lambda2);
786 double change = std::abs(beta_new - beta[j]);
787 max_change = std::max(max_change, change);
788
789 beta[j] = beta_new;
790
791 for (std::size_t i = 0; i < n; ++i) {
792 residuals[i] -= X_scaled[i][j] * beta[j];
793 }
794 }
795
796 if (max_change < tol) {
797 converged = true;
798 ++iter;
799 break;
800 }
801 }
802
803 // Transform coefficients back to original scale
804 auto coefficients = detail::rescale_coefficients(beta, X_mean, X_std, y_mean, p, standardize);
805
806 // Calculate MSE
807 double mse = 0.0;
808 for (std::size_t i = 0; i < n; ++i) {
809 double pred = coefficients[0];
810 for (std::size_t j = 0; j < p; ++j) {
811 pred += coefficients[j + 1] * X[i][j];
812 }
813 double err = y[i] - pred;
814 mse += err * err;
815 }
816 mse /= static_cast<double>(n);
817
818 return {coefficients, lambda, mse, iter, converged};
819}
820
821// ============================================================================
822// Lambda Selection (Regularization Parameter Selection)
823// ============================================================================
824
837inline std::pair<double, std::vector<double>> cv_ridge(
838 const std::vector<std::vector<double>>& X,
839 const std::vector<double>& y,
840 const std::vector<double>& lambda_grid,
841 std::size_t k = 5)
842{
843 std::vector<double> cv_errors(lambda_grid.size());
844
845 for (std::size_t l = 0; l < lambda_grid.size(); ++l) {
846 double lambda = lambda_grid[l];
847 auto folds = create_cv_folds(X.size(), k, true);
848
849 double total_error = 0.0;
850 for (std::size_t fold = 0; fold < k; ++fold) {
851 std::vector<std::size_t> test_idx = folds[fold];
852 std::vector<std::size_t> train_idx;
853 for (std::size_t f = 0; f < k; ++f) {
854 if (f != fold) {
855 train_idx.insert(train_idx.end(), folds[f].begin(), folds[f].end());
856 }
857 }
858
859 std::vector<std::vector<double>> X_train(train_idx.size());
860 std::vector<double> y_train(train_idx.size());
861 for (std::size_t i = 0; i < train_idx.size(); ++i) {
862 X_train[i] = X[train_idx[i]];
863 y_train[i] = y[train_idx[i]];
864 }
865
866 try {
867 auto model = ridge_regression(X_train, y_train, lambda);
868
869 double mse = 0.0;
870 for (std::size_t i : test_idx) {
871 double pred = model.coefficients[0];
872 for (std::size_t j = 0; j < X[i].size(); ++j) {
873 pred += model.coefficients[j + 1] * X[i][j];
874 }
875 double err = y[i] - pred;
876 mse += err * err;
877 }
878 total_error += mse / static_cast<double>(test_idx.size());
879 } catch (...) {
880 total_error += std::numeric_limits<double>::infinity();
881 }
882 }
883 cv_errors[l] = total_error / static_cast<double>(k);
884 }
885
886 // Select lambda with minimum error
887 auto min_it = std::min_element(cv_errors.begin(), cv_errors.end());
888 double best_lambda = lambda_grid[std::distance(cv_errors.begin(), min_it)];
889
890 return {best_lambda, cv_errors};
891}
892
905inline std::pair<double, std::vector<double>> cv_lasso(
906 const std::vector<std::vector<double>>& X,
907 const std::vector<double>& y,
908 const std::vector<double>& lambda_grid,
909 std::size_t k = 5)
910{
911 std::vector<double> cv_errors(lambda_grid.size());
912
913 for (std::size_t l = 0; l < lambda_grid.size(); ++l) {
914 double lambda = lambda_grid[l];
915 auto folds = create_cv_folds(X.size(), k, true);
916
917 double total_error = 0.0;
918 for (std::size_t fold = 0; fold < k; ++fold) {
919 std::vector<std::size_t> test_idx = folds[fold];
920 std::vector<std::size_t> train_idx;
921 for (std::size_t f = 0; f < k; ++f) {
922 if (f != fold) {
923 train_idx.insert(train_idx.end(), folds[f].begin(), folds[f].end());
924 }
925 }
926
927 std::vector<std::vector<double>> X_train(train_idx.size());
928 std::vector<double> y_train(train_idx.size());
929 for (std::size_t i = 0; i < train_idx.size(); ++i) {
930 X_train[i] = X[train_idx[i]];
931 y_train[i] = y[train_idx[i]];
932 }
933
934 try {
935 auto model = lasso_regression(X_train, y_train, lambda);
936
937 double mse = 0.0;
938 for (std::size_t i : test_idx) {
939 double pred = model.coefficients[0];
940 for (std::size_t j = 0; j < X[i].size(); ++j) {
941 pred += model.coefficients[j + 1] * X[i][j];
942 }
943 double err = y[i] - pred;
944 mse += err * err;
945 }
946 total_error += mse / static_cast<double>(test_idx.size());
947 } catch (...) {
948 total_error += std::numeric_limits<double>::infinity();
949 }
950 }
951 cv_errors[l] = total_error / static_cast<double>(k);
952 }
953
954 auto min_it = std::min_element(cv_errors.begin(), cv_errors.end());
955 double best_lambda = lambda_grid[std::distance(cv_errors.begin(), min_it)];
956
957 return {best_lambda, cv_errors};
958}
959
972inline std::vector<double> generate_lambda_grid(
973 const std::vector<std::vector<double>>& X,
974 const std::vector<double>& y,
975 std::size_t n_lambda = 100,
976 double lambda_min_ratio = 0.0001)
977{
978 std::size_t n = X.size();
979 std::size_t p = X[0].size();
980
981 // Center y
982 double y_mean = statcpp::mean(y.begin(), y.end());
983
984 // Calculate lambda_max (lambda where all coefficients are zero)
985 double lambda_max = 0.0;
986 for (std::size_t j = 0; j < p; ++j) {
987 double xy = 0.0;
988 for (std::size_t i = 0; i < n; ++i) {
989 xy += X[i][j] * (y[i] - y_mean);
990 }
991 lambda_max = std::max(lambda_max, std::abs(xy) / static_cast<double>(n));
992 }
993
994 if (lambda_max <= 0.0) {
995 throw std::invalid_argument(
996 "statcpp::generate_lambda_grid: lambda_max must be positive (data may be constant)");
997 }
998
999 if (n_lambda <= 1) {
1000 return {lambda_max};
1001 }
1002
1003 double lambda_min = lambda_max * lambda_min_ratio;
1004
1005 // Generate grid on logarithmic scale
1006 std::vector<double> grid(n_lambda);
1007 double log_max = std::log(lambda_max);
1008 double log_min = std::log(lambda_min);
1009 double step = (log_max - log_min) / static_cast<double>(n_lambda - 1);
1010
1011 for (std::size_t i = 0; i < n_lambda; ++i) {
1012 grid[i] = std::exp(log_max - static_cast<double>(i) * step);
1013 }
1014
1015 return grid;
1016}
1017
1018} // namespace statcpp
Basic statistical computation functions.
Dispersion and variance calculation functions.
Linear regression analysis.
void standardize_features(const std::vector< std::vector< double > > &X, std::vector< std::vector< double > > &X_scaled, std::vector< double > &X_mean, std::vector< double > &X_std, std::size_t n, std::size_t p)
特徴量の標準化 (平均0, 標準偏差1)
std::vector< double > rescale_coefficients(const std::vector< double > &beta, const std::vector< double > &X_mean, const std::vector< double > &X_std, double y_mean, std::size_t p, bool standardize)
標準化済み係数を元のスケールに逆変換する
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.
regularized_regression_result ridge_regression(const std::vector< std::vector< double > > &X, const std::vector< double > &y, double lambda, bool standardize=true, std::size_t max_iter=1000, double tol=1e-6)
Perform Ridge regression (L2 regularization)
double aicc(double log_likelihood, std::size_t n, std::size_t k)
Calculate AICc (corrected AIC)
double sample_stddev(Iterator first, Iterator last)
Sample standard deviation.
constexpr double pi
Pi constant.
cv_result loocv_linear(const std::vector< std::vector< double > > &X, const std::vector< double > &y)
Perform leave-one-out cross-validation.
regularized_regression_result lasso_regression(const std::vector< std::vector< double > > &X, const std::vector< double > &y, double lambda, bool standardize=true, std::size_t max_iter=1000, double tol=1e-6)
Perform Lasso regression (L1 regularization)
auto sum(Iterator first, Iterator last)
Sum.
multiple_regression_result multiple_linear_regression(const std::vector< std::vector< double > > &X, const std::vector< double > &y)
Perform multiple linear regression.
double predict(const simple_regression_result &model, double x)
Make prediction using simple regression model.
double beta(double a, double b)
Beta function.
std::vector< std::vector< double > > standardize(const std::vector< std::vector< double > > &data)
Z-score standardization.
double bic_linear(const simple_regression_result &model, std::size_t n)
Calculate BIC from simple regression model.
double mean(Iterator first, Iterator last)
Arithmetic mean.
std::vector< double > generate_lambda_grid(const std::vector< std::vector< double > > &X, const std::vector< double > &y, std::size_t n_lambda=100, double lambda_min_ratio=0.0001)
Automatically generate lambda grid for regularized regression.
double bic(double log_likelihood, std::size_t n, std::size_t k)
Calculate BIC (Bayesian Information Criterion)
double aic(double log_likelihood, std::size_t k)
Calculate AIC (Akaike Information Criterion)
std::pair< double, std::vector< double > > cv_lasso(const std::vector< std::vector< double > > &X, const std::vector< double > &y, const std::vector< double > &lambda_grid, std::size_t k=5)
Select optimal lambda for Lasso regression using cross-validation.
default_random_engine & get_random_engine()
Singleton accessor for global random engine.
std::vector< std::vector< std::size_t > > create_cv_folds(std::size_t n, std::size_t k, bool shuffle=true)
Generate indices for k-fold cross-validation.
double press_statistic(IteratorX x_first, IteratorX x_last, IteratorY y_first, IteratorY y_last, const simple_regression_result &model)
Calculate PRESS statistic.
double mse(Iterator1 first1, Iterator1 last1, Iterator2 first2)
Mean Squared Error (MSE)
cv_result cross_validate_linear(const std::vector< std::vector< double > > &X, const std::vector< double > &y, std::size_t k=5)
Perform k-fold cross-validation for multiple regression model.
std::pair< double, std::vector< double > > cv_ridge(const std::vector< std::vector< double > > &X, const std::vector< double > &y, const std::vector< double > &lambda_grid, std::size_t k=5)
Select optimal lambda for Ridge regression using cross-validation.
std::size_t count(Iterator first, Iterator last)
Data count.
double aic_linear(const simple_regression_result &model, std::size_t n)
Calculate AIC from simple regression model.
regularized_regression_result elastic_net_regression(const std::vector< std::vector< double > > &X, const std::vector< double > &y, double lambda, double alpha=0.5, bool standardize=true, std::size_t max_iter=1000, double tol=1e-6)
Perform Elastic Net regression (L1 + L2 regularization)
Random engine wrapper and utilities.
Structure to store cross-validation results.
std::vector< double > fold_errors
Structure to store multiple regression analysis results.
std::vector< double > coefficients
Regression coefficients (b0, b1, ..., bp)
double ss_residual
Residual sum of squares.
Structure to store regularized regression results.
Structure to store simple regression analysis results.
double ss_residual
Residual sum of squares.