statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
categorical.hpp
Go to the documentation of this file.
1
6#pragma once
7
8#include <algorithm>
9#include <cmath>
10#include <cstddef>
11#include <stdexcept>
12#include <vector>
13
14namespace statcpp {
15
16// ============================================================================
17// Contingency Table (Cross-tabulation)
18// ============================================================================
19
24 std::vector<std::vector<std::size_t>> table;
25 std::vector<std::size_t> row_totals;
26 std::vector<std::size_t> col_totals;
27 std::size_t total;
28 std::size_t n_rows;
29 std::size_t n_cols;
30};
31
43 const std::vector<std::size_t>& row_data,
44 const std::vector<std::size_t>& col_data)
45{
46 if (row_data.size() != col_data.size()) {
47 throw std::invalid_argument("statcpp::contingency_table: data lengths must match");
48 }
49 if (row_data.empty()) {
50 throw std::invalid_argument("statcpp::contingency_table: empty data");
51 }
52
53 // Determine number of categories
54 std::size_t max_row = *std::max_element(row_data.begin(), row_data.end());
55 std::size_t max_col = *std::max_element(col_data.begin(), col_data.end());
56 std::size_t n_rows = max_row + 1;
57 std::size_t n_cols = max_col + 1;
58
59 // Create contingency table
60 std::vector<std::vector<std::size_t>> table(n_rows, std::vector<std::size_t>(n_cols, 0));
61
62 for (std::size_t i = 0; i < row_data.size(); ++i) {
63 table[row_data[i]][col_data[i]]++;
64 }
65
66 // Calculate marginal frequencies
67 std::vector<std::size_t> row_totals(n_rows, 0);
68 std::vector<std::size_t> col_totals(n_cols, 0);
69 std::size_t total = 0;
70
71 for (std::size_t i = 0; i < n_rows; ++i) {
72 for (std::size_t j = 0; j < n_cols; ++j) {
73 row_totals[i] += table[i][j];
74 col_totals[j] += table[i][j];
75 total += table[i][j];
76 }
77 }
78
79 return {table, row_totals, col_totals, total, n_rows, n_cols};
80}
81
82// ============================================================================
83// Odds Ratio
84// ============================================================================
85
96
115inline odds_ratio_result odds_ratio(const std::vector<std::vector<std::size_t>>& table)
116{
117 if (table.size() != 2 || table[0].size() != 2 || table[1].size() != 2) {
118 throw std::invalid_argument("statcpp::odds_ratio: table must be 2x2");
119 }
120
121 double a = static_cast<double>(table[0][0]);
122 double b = static_cast<double>(table[0][1]);
123 double c = static_cast<double>(table[1][0]);
124 double d = static_cast<double>(table[1][1]);
125
126 if (a == 0 || b == 0 || c == 0 || d == 0) {
127 throw std::invalid_argument("statcpp::odds_ratio: zero cell count not allowed");
128 }
129
130 double or_val = (a * d) / (b * c);
131 double log_or = std::log(or_val);
132 double se_log_or = std::sqrt(1.0/a + 1.0/b + 1.0/c + 1.0/d);
133
134 // 95% confidence interval
135 double z = 1.96;
136 double ci_lower = std::exp(log_or - z * se_log_or);
137 double ci_upper = std::exp(log_or + z * se_log_or);
138
139 return {or_val, log_or, se_log_or, ci_lower, ci_upper};
140}
141
152inline odds_ratio_result odds_ratio(std::size_t a, std::size_t b, std::size_t c, std::size_t d)
153{
154 return odds_ratio({{a, b}, {c, d}});
155}
156
157// ============================================================================
158// Relative Risk (Risk Ratio)
159// ============================================================================
160
171
191inline relative_risk_result relative_risk(const std::vector<std::vector<std::size_t>>& table)
192{
193 if (table.size() != 2 || table[0].size() != 2 || table[1].size() != 2) {
194 throw std::invalid_argument("statcpp::relative_risk: table must be 2x2");
195 }
196
197 double a = static_cast<double>(table[0][0]);
198 double b = static_cast<double>(table[0][1]);
199 double c = static_cast<double>(table[1][0]);
200 double d = static_cast<double>(table[1][1]);
201
202 double n1 = a + b; // Size of exposed group
203 double n0 = c + d; // Size of unexposed group
204
205 if (n1 == 0 || n0 == 0) {
206 throw std::invalid_argument("statcpp::relative_risk: zero row total");
207 }
208 if (a == 0 || c == 0) {
209 throw std::invalid_argument("statcpp::relative_risk: zero risk in a group");
210 }
211
212 double risk1 = a / n1; // Risk in exposed group
213 double risk0 = c / n0; // Risk in unexposed group
214
215 double rr = risk1 / risk0;
216 double log_rr = std::log(rr);
217
218 // Standard error (Greenland-Robins formula)
219 double se_log_rr = std::sqrt((1.0 - risk1)/(a) + (1.0 - risk0)/(c));
220
221 // 95% confidence interval
222 double z = 1.96;
223 double ci_lower = std::exp(log_rr - z * se_log_rr);
224 double ci_upper = std::exp(log_rr + z * se_log_rr);
225
226 return {rr, log_rr, se_log_rr, ci_lower, ci_upper};
227}
228
239inline relative_risk_result relative_risk(std::size_t a, std::size_t b, std::size_t c, std::size_t d)
240{
241 return relative_risk({{a, b}, {c, d}});
242}
243
244// ============================================================================
245// Risk Difference (Attributable Risk)
246// ============================================================================
247
253 double se;
254 double ci_lower;
255 double ci_upper;
256};
257
268inline risk_difference_result risk_difference(const std::vector<std::vector<std::size_t>>& table)
269{
270 if (table.size() != 2 || table[0].size() != 2 || table[1].size() != 2) {
271 throw std::invalid_argument("statcpp::risk_difference: table must be 2x2");
272 }
273
274 double a = static_cast<double>(table[0][0]);
275 double b = static_cast<double>(table[0][1]);
276 double c = static_cast<double>(table[1][0]);
277 double d = static_cast<double>(table[1][1]);
278
279 double n1 = a + b;
280 double n0 = c + d;
281
282 if (n1 == 0 || n0 == 0) {
283 throw std::invalid_argument("statcpp::risk_difference: zero row total");
284 }
285
286 double risk1 = a / n1;
287 double risk0 = c / n0;
288
289 double rd = risk1 - risk0;
290
291 // Standard error
292 double se = std::sqrt(risk1 * (1.0 - risk1) / n1 + risk0 * (1.0 - risk0) / n0);
293
294 // 95% confidence interval
295 double z = 1.96;
296 double ci_lower = rd - z * se;
297 double ci_upper = rd + z * se;
298
299 return {rd, se, ci_lower, ci_upper};
300}
301
312inline risk_difference_result risk_difference(std::size_t a, std::size_t b, std::size_t c, std::size_t d)
313{
314 return risk_difference({{a, b}, {c, d}});
315}
316
317// ============================================================================
318// Number Needed to Treat (NNT)
319// ============================================================================
320
331inline double number_needed_to_treat(const std::vector<std::vector<std::size_t>>& table)
332{
333 auto rd = risk_difference(table);
334 if (rd.risk_difference == 0.0) {
335 throw std::invalid_argument("statcpp::number_needed_to_treat: risk difference is zero");
336 }
337 return 1.0 / std::abs(rd.risk_difference);
338}
339
340} // namespace statcpp
risk_difference_result risk_difference(const std::vector< std::vector< std::size_t > > &table)
Calculate risk difference from a 2x2 contingency table.
contingency_table_result contingency_table(const std::vector< std::size_t > &row_data, const std::vector< std::size_t > &col_data)
Create a contingency table.
odds_ratio_result odds_ratio(const std::vector< std::vector< std::size_t > > &table)
Calculate odds ratio from a 2x2 contingency table.
relative_risk_result relative_risk(const std::vector< std::vector< std::size_t > > &table)
Calculate relative risk (risk ratio) from a 2x2 contingency table.
double number_needed_to_treat(const std::vector< std::vector< std::size_t > > &table)
Calculate Number Needed to Treat (NNT)
Contingency table (cross-tabulation) result.
std::size_t n_rows
Number of rows.
std::vector< std::size_t > row_totals
Row totals.
std::size_t n_cols
Number of columns.
std::vector< std::size_t > col_totals
Column totals.
std::size_t total
Grand total.
std::vector< std::vector< std::size_t > > table
Observed frequencies.
double log_odds_ratio
Log odds ratio.
double ci_lower
95% confidence interval lower bound
double se_log_odds_ratio
Standard error of log odds ratio.
double odds_ratio
Odds ratio.
double ci_upper
95% confidence interval upper bound
Relative risk result.
double relative_risk
Relative risk.
double ci_lower
95% confidence interval lower bound
double se_log_relative_risk
Standard error of log relative risk.
double ci_upper
95% confidence interval upper bound
double log_relative_risk
Log relative risk.
Risk difference result.
double ci_lower
95% confidence interval lower bound
double ci_upper
95% confidence interval upper bound
double risk_difference
Risk difference (attributable risk)