statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
multivariate.hpp
Go to the documentation of this file.
1
9#pragma once
10
11#include <algorithm>
12#include <cmath>
13#include <cstddef>
14#include <limits>
15#include <stdexcept>
16#include <utility>
17#include <vector>
18
19#include "statcpp/linear_regression.hpp" // for detail::validate_matrix_structure
20
21namespace statcpp {
22
23// ============================================================================
24// Covariance Matrix
25// ============================================================================
26
37inline std::vector<std::vector<double>> covariance_matrix(
38 const std::vector<std::vector<double>>& data)
39{
40 // Validate matrix structure (empty check and column count consistency)
41 detail::validate_matrix_structure(data, "covariance_matrix");
42
43 std::size_t n = data.size(); // Number of observations
44 std::size_t p = data[0].size(); // Number of variables
45
46 if (n < 2) {
47 throw std::invalid_argument("statcpp::covariance_matrix: need at least 2 observations");
48 }
49
50 // Calculate mean for each variable
51 std::vector<double> means(p, 0.0);
52 for (std::size_t j = 0; j < p; ++j) {
53 for (std::size_t i = 0; i < n; ++i) {
54 means[j] += data[i][j];
55 }
56 means[j] /= static_cast<double>(n);
57 }
58
59 // Calculate covariance matrix
60 std::vector<std::vector<double>> cov(p, std::vector<double>(p, 0.0));
61 for (std::size_t j1 = 0; j1 < p; ++j1) {
62 for (std::size_t j2 = j1; j2 < p; ++j2) {
63 double sum = 0.0;
64 for (std::size_t i = 0; i < n; ++i) {
65 sum += (data[i][j1] - means[j1]) * (data[i][j2] - means[j2]);
66 }
67 cov[j1][j2] = sum / static_cast<double>(n - 1);
68 cov[j2][j1] = cov[j1][j2]; // Symmetric matrix
69 }
70 }
71
72 return cov;
73}
74
75// ============================================================================
76// Correlation Matrix
77// ============================================================================
78
89inline std::vector<std::vector<double>> correlation_matrix(
90 const std::vector<std::vector<double>>& data)
91{
92 // Validate matrix structure
93 detail::validate_matrix_structure(data, "correlation_matrix");
94
95 std::size_t n = data.size();
96 std::size_t p = data[0].size();
97
98 if (n < 2) {
99 throw std::invalid_argument("statcpp::correlation_matrix: need at least 2 observations");
100 }
101
102 // Calculate covariance matrix
103 auto cov = covariance_matrix(data);
104
105 // Calculate standard deviations
106 std::vector<double> stddevs(p);
107 for (std::size_t j = 0; j < p; ++j) {
108 stddevs[j] = std::sqrt(cov[j][j]);
109 if (stddevs[j] == 0.0) {
110 throw std::invalid_argument("statcpp::correlation_matrix: zero variance variable");
111 }
112 }
113
114 // Convert to correlation matrix
115 std::vector<std::vector<double>> corr(p, std::vector<double>(p, 0.0));
116 for (std::size_t j1 = 0; j1 < p; ++j1) {
117 for (std::size_t j2 = 0; j2 < p; ++j2) {
118 corr[j1][j2] = cov[j1][j2] / (stddevs[j1] * stddevs[j2]);
119 }
120 }
121
122 return corr;
123}
124
125// ============================================================================
126// Standardization (Z-score)
127// ============================================================================
128
139inline std::vector<std::vector<double>> standardize(
140 const std::vector<std::vector<double>>& data)
141{
142 // Validate matrix structure
143 detail::validate_matrix_structure(data, "standardize");
144
145 std::size_t n = data.size();
146 std::size_t p = data[0].size();
147
148 if (n < 2) {
149 throw std::invalid_argument("statcpp::standardize: need at least 2 observations");
150 }
151
152 // Calculate mean and standard deviation for each variable
153 std::vector<double> means(p, 0.0);
154 std::vector<double> stddevs(p, 0.0);
155
156 for (std::size_t j = 0; j < p; ++j) {
157 for (std::size_t i = 0; i < n; ++i) {
158 means[j] += data[i][j];
159 }
160 means[j] /= static_cast<double>(n);
161
162 for (std::size_t i = 0; i < n; ++i) {
163 double diff = data[i][j] - means[j];
164 stddevs[j] += diff * diff;
165 }
166 stddevs[j] = std::sqrt(stddevs[j] / static_cast<double>(n - 1));
167
168 if (stddevs[j] == 0.0) {
169 throw std::invalid_argument("statcpp::standardize: zero variance variable");
170 }
171 }
172
173 // Standardize
174 std::vector<std::vector<double>> result(n, std::vector<double>(p));
175 for (std::size_t i = 0; i < n; ++i) {
176 for (std::size_t j = 0; j < p; ++j) {
177 result[i][j] = (data[i][j] - means[j]) / stddevs[j];
178 }
179 }
180
181 return result;
182}
183
184// ============================================================================
185// Min-Max Scaling
186// ============================================================================
187
198inline std::vector<std::vector<double>> min_max_scale(
199 const std::vector<std::vector<double>>& data)
200{
201 // Validate matrix structure
202 detail::validate_matrix_structure(data, "min_max_scale");
203
204 std::size_t n = data.size();
205 std::size_t p = data[0].size();
206
207 // Calculate minimum and maximum for each variable
208 std::vector<double> mins(p, std::numeric_limits<double>::max());
209 std::vector<double> maxs(p, std::numeric_limits<double>::lowest());
210
211 for (std::size_t j = 0; j < p; ++j) {
212 for (std::size_t i = 0; i < n; ++i) {
213 mins[j] = std::min(mins[j], data[i][j]);
214 maxs[j] = std::max(maxs[j], data[i][j]);
215 }
216
217 if (maxs[j] == mins[j]) {
218 throw std::invalid_argument("statcpp::min_max_scale: zero range variable");
219 }
220 }
221
222 // Scale
223 std::vector<std::vector<double>> result(n, std::vector<double>(p));
224 for (std::size_t i = 0; i < n; ++i) {
225 for (std::size_t j = 0; j < p; ++j) {
226 result[i][j] = (data[i][j] - mins[j]) / (maxs[j] - mins[j]);
227 }
228 }
229
230 return result;
231}
232
233// ============================================================================
234// Principal Component Analysis (PCA)
235// ============================================================================
236
243 std::vector<std::vector<double>> components;
244 std::vector<double> explained_variance;
245 std::vector<double> explained_variance_ratio;
246};
247
259inline std::pair<double, std::vector<double>> power_iteration(
260 const std::vector<std::vector<double>>& matrix,
261 std::size_t max_iter = 1000,
262 double tol = 1e-10)
263{
264 std::size_t n = matrix.size();
265 std::vector<double> v(n, 1.0 / std::sqrt(static_cast<double>(n)));
266
267 double eigenvalue = 0.0;
268
269 for (std::size_t iter = 0; iter < max_iter; ++iter) {
270 // Calculate Av
271 std::vector<double> Av(n, 0.0);
272 for (std::size_t i = 0; i < n; ++i) {
273 for (std::size_t j = 0; j < n; ++j) {
274 Av[i] += matrix[i][j] * v[j];
275 }
276 }
277
278 // Calculate norm
279 double norm = 0.0;
280 for (std::size_t i = 0; i < n; ++i) {
281 norm += Av[i] * Av[i];
282 }
283 norm = std::sqrt(norm);
284
285 if (norm == 0.0) break;
286
287 // Estimate eigenvalue
288 double new_eigenvalue = 0.0;
289 for (std::size_t i = 0; i < n; ++i) {
290 new_eigenvalue += v[i] * Av[i];
291 }
292
293 // Normalize
294 for (std::size_t i = 0; i < n; ++i) {
295 v[i] = Av[i] / norm;
296 }
297
298 // Convergence check
299 if (std::abs(new_eigenvalue - eigenvalue) < tol) {
300 eigenvalue = new_eigenvalue;
301 break;
302 }
303 eigenvalue = new_eigenvalue;
304 }
305
306 return {eigenvalue, v};
307}
308
325inline pca_result pca(const std::vector<std::vector<double>>& data,
326 std::size_t n_components)
327{
328 // Validate matrix structure
330
331 std::size_t p = data[0].size();
332
333 if (n_components > p) {
334 n_components = p;
335 }
336
337 // Calculate covariance matrix
338 auto cov = covariance_matrix(data);
339
340 pca_result result;
341 result.components.resize(p, std::vector<double>(n_components));
342 result.explained_variance.resize(n_components);
343 result.explained_variance_ratio.resize(n_components);
344
345 // Total variance (trace)
346 double total_variance = 0.0;
347 for (std::size_t j = 0; j < p; ++j) {
348 total_variance += cov[j][j];
349 }
350
351 // Calculate each principal component (deflation method)
352 auto working_cov = cov;
353
354 for (std::size_t k = 0; k < n_components; ++k) {
355 auto [eigenvalue, eigenvector] = power_iteration(working_cov);
356
357 result.explained_variance[k] = eigenvalue;
358 result.explained_variance_ratio[k] = eigenvalue / total_variance;
359
360 for (std::size_t j = 0; j < p; ++j) {
361 result.components[j][k] = eigenvector[j];
362 }
363
364 // Deflation: remove processed eigenvalue component
365 for (std::size_t i = 0; i < p; ++i) {
366 for (std::size_t j = 0; j < p; ++j) {
367 working_cov[i][j] -= eigenvalue * eigenvector[i] * eigenvector[j];
368 }
369 }
370 }
371
372 return result;
373}
374
385inline std::vector<std::vector<double>> pca_transform(
386 const std::vector<std::vector<double>>& data,
387 const pca_result& pca)
388{
389 // Validate matrix structure
390 detail::validate_matrix_structure(data, "pca_transform");
391
392 // Also validate PCA components structure
393 if (pca.components.empty() || pca.components[0].empty()) {
394 throw std::invalid_argument("statcpp::pca_transform: pca.components is empty");
395 }
396
397 std::size_t n = data.size();
398 std::size_t p = data[0].size();
399 std::size_t n_components = pca.components[0].size();
400
401 // Check that data and PCA components dimensions match
402 if (p != pca.components.size()) {
403 throw std::invalid_argument("statcpp::pca_transform: dimension mismatch between data and pca.components");
404 }
405
406 // Calculate means
407 std::vector<double> means(p, 0.0);
408 for (std::size_t j = 0; j < p; ++j) {
409 for (std::size_t i = 0; i < n; ++i) {
410 means[j] += data[i][j];
411 }
412 means[j] /= static_cast<double>(n);
413 }
414
415 // Project
416 std::vector<std::vector<double>> result(n, std::vector<double>(n_components, 0.0));
417 for (std::size_t i = 0; i < n; ++i) {
418 for (std::size_t k = 0; k < n_components; ++k) {
419 for (std::size_t j = 0; j < p; ++j) {
420 result[i][k] += (data[i][j] - means[j]) * pca.components[j][k];
421 }
422 }
423 }
424
425 return result;
426}
427
428} // namespace statcpp
Linear regression analysis.
void validate_matrix_structure(const std::vector< std::vector< double > > &data, const char *func_name)
Validate 2D matrix structure.
std::vector< std::vector< double > > min_max_scale(const std::vector< std::vector< double > > &data)
Min-Max normalization (0-1 scaling)
auto sum(Iterator first, Iterator last)
Sum.
std::vector< std::vector< double > > standardize(const std::vector< std::vector< double > > &data)
Z-score standardization.
std::pair< double, std::vector< double > > power_iteration(const std::vector< std::vector< double > > &matrix, std::size_t max_iter=1000, double tol=1e-10)
Find largest eigenvalue and eigenvector using power iteration.
std::vector< double > diff(Iterator first, Iterator last, std::size_t order=1)
Difference series (first-order or d-th order differencing)
std::vector< std::vector< double > > covariance_matrix(const std::vector< std::vector< double > > &data)
Calculate sample covariance matrix.
std::vector< std::vector< double > > correlation_matrix(const std::vector< std::vector< double > > &data)
Calculate Pearson correlation matrix.
pca_result pca(const std::vector< std::vector< double > > &data, std::size_t n_components)
Principal Component Analysis.
std::vector< std::vector< double > > pca_transform(const std::vector< std::vector< double > > &data, const pca_result &pca)
Project data onto principal component space.
std::vector< std::vector< double > > components
Principal components (eigenvectors) p x n_components.
std::vector< double > explained_variance
Explained variance (eigenvalues)
std::vector< double > explained_variance_ratio
Variance explained ratio.