v2.0.0
Loading...
Searching...
No Matches
sts_ttest.cpp
Go to the documentation of this file.
1//=============================================================================================================
24
25//=============================================================================================================
26// INCLUDES
27//=============================================================================================================
28
29#include "sts_ttest.h"
30
31#include <cmath>
32#include <algorithm>
33
34//=============================================================================================================
35// USED NAMESPACES
36//=============================================================================================================
37
38using namespace STSLIB;
39using namespace Eigen;
40
41//=============================================================================================================
42// DEFINE METHODS
43//=============================================================================================================
44
45StatsTtestResult StatsTtest::oneSample(const MatrixXd& data, double mu, StatsTailType tail)
46{
47 const int n = static_cast<int>(data.rows());
48 const int nVars = static_cast<int>(data.cols());
49 const int df = n - 1;
50
51 // Column means
52 RowVectorXd means = data.colwise().mean();
53
54 // Column standard deviations
55 MatrixXd centered = data.rowwise() - means;
56 RowVectorXd stddev = (centered.colwise().squaredNorm() / static_cast<double>(df)).array().sqrt();
57
58 // t-statistics: (mean - mu) / (std / sqrt(n))
59 double sqrtN = std::sqrt(static_cast<double>(n));
60 RowVectorXd tstat = (means.array() - mu) / (stddev.array() / sqrtN);
61
62 // p-values
63 MatrixXd matPval(1, nVars);
64 for (int j = 0; j < nVars; ++j) {
65 matPval(0, j) = tToPval(tstat(j), df, tail);
66 }
67
68 StatsTtestResult result;
69 result.matTstat = tstat;
70 result.matPval = matPval;
71 result.degreesOfFreedom = df;
72 return result;
73}
74
75//=============================================================================================================
76
77StatsTtestResult StatsTtest::paired(const MatrixXd& dataA, const MatrixXd& dataB, StatsTailType tail)
78{
79 return oneSample(dataA - dataB, 0.0, tail);
80}
81
82//=============================================================================================================
83
84StatsTtestResult StatsTtest::independent(const MatrixXd& dataA, const MatrixXd& dataB, StatsTailType tail)
85{
86 const int nA = static_cast<int>(dataA.rows());
87 const int nB = static_cast<int>(dataB.rows());
88 const int nVars = static_cast<int>(dataA.cols());
89 const int df = nA + nB - 2;
90
91 RowVectorXd meanA = dataA.colwise().mean();
92 RowVectorXd meanB = dataB.colwise().mean();
93
94 // Pooled variance
95 MatrixXd centA = dataA.rowwise() - meanA;
96 MatrixXd centB = dataB.rowwise() - meanB;
97 RowVectorXd ssA = centA.colwise().squaredNorm();
98 RowVectorXd ssB = centB.colwise().squaredNorm();
99 RowVectorXd pooledVar = (ssA + ssB) / static_cast<double>(df);
100
101 // t-statistic
102 double invN = 1.0 / static_cast<double>(nA) + 1.0 / static_cast<double>(nB);
103 RowVectorXd tstat = (meanA - meanB).array() / (pooledVar.array() * invN).sqrt();
104
105 // p-values
106 MatrixXd matPval(1, nVars);
107 for (int j = 0; j < nVars; ++j) {
108 matPval(0, j) = tToPval(tstat(j), df, tail);
109 }
110
111 StatsTtestResult result;
112 result.matTstat = tstat;
113 result.matPval = matPval;
114 result.degreesOfFreedom = df;
115 return result;
116}
117
118//=============================================================================================================
119
120double StatsTtest::tCdf(double t, int df)
121{
122 // P(T <= t) for t-distribution with df degrees of freedom.
123 // Using: P(T <= t) = 1 - 0.5 * I_x(df/2, 1/2) where x = df/(df + t^2) [for t >= 0]
124 // For t < 0: P(T <= t) = 0.5 * I_x(df/2, 1/2) where x = df/(df + t^2)
125 double x = static_cast<double>(df) / (static_cast<double>(df) + t * t);
126 double iBeta = regularizedBeta(x, static_cast<double>(df) / 2.0, 0.5);
127 if (t >= 0.0) {
128 return 1.0 - 0.5 * iBeta;
129 } else {
130 return 0.5 * iBeta;
131 }
132}
133
134//=============================================================================================================
135
136double StatsTtest::tToPval(double t, int df, StatsTailType tail)
137{
138 double cdf = tCdf(t, df);
139 switch (tail) {
141 return cdf;
143 return 1.0 - cdf;
145 default:
146 return 2.0 * std::min(cdf, 1.0 - cdf);
147 }
148}
149
150//=============================================================================================================
151
152double StatsTtest::regularizedBeta(double x, double a, double b)
153{
154 // I_x(a,b) = x^a * (1-x)^b / (a * B(a,b)) * CF(x,a,b)
155 // where CF is the continued fraction expansion.
156 // For x > (a+1)/(a+b+2), use the identity I_x(a,b) = 1 - I_{1-x}(b,a).
157 if (x <= 0.0) return 0.0;
158 if (x >= 1.0) return 1.0;
159
160 if (x > (a + 1.0) / (a + b + 2.0)) {
161 return 1.0 - regularizedBeta(1.0 - x, b, a);
162 }
163
164 double lnPre = a * std::log(x) + b * std::log(1.0 - x) - logBeta(a, b) - std::log(a);
165 return std::exp(lnPre) * betaCf(x, a, b);
166}
167
168//=============================================================================================================
169
170double StatsTtest::betaCf(double x, double a, double b)
171{
172 // Continued fraction for I_x(a,b) using Lentz's algorithm.
173 const int maxIter = 200;
174 const double eps = 1.0e-12;
175 const double tiny = 1.0e-30;
176
177 double qab = a + b;
178 double qap = a + 1.0;
179 double qam = a - 1.0;
180
181 double c = 1.0;
182 double d = 1.0 - qab * x / qap;
183 if (std::fabs(d) < tiny) d = tiny;
184 d = 1.0 / d;
185 double h = d;
186
187 for (int m = 1; m <= maxIter; ++m) {
188 double m2 = 2.0 * m;
189
190 // Even step
191 double aa = m * (b - m) * x / ((qam + m2) * (a + m2));
192 d = 1.0 + aa * d;
193 if (std::fabs(d) < tiny) d = tiny;
194 c = 1.0 + aa / c;
195 if (std::fabs(c) < tiny) c = tiny;
196 d = 1.0 / d;
197 h *= d * c;
198
199 // Odd step
200 aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
201 d = 1.0 + aa * d;
202 if (std::fabs(d) < tiny) d = tiny;
203 c = 1.0 + aa / c;
204 if (std::fabs(c) < tiny) c = tiny;
205 d = 1.0 / d;
206 double del = d * c;
207 h *= del;
208
209 if (std::fabs(del - 1.0) < eps) break;
210 }
211 return h;
212}
213
214//=============================================================================================================
215
216double StatsTtest::logBeta(double a, double b)
217{
218 return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
219}
Frequentist Student's t-tests with exact p-values via the regularised incomplete beta function.
Statistical testing (t-tests, F-tests, cluster permutation, multiple comparison correction).
StatsTailType
Direction of the alternative hypothesis for a t- or F-test (left, right, or two-sided).
Definition sts_types.h:37
Per-call output of a Student t-test: t-statistics, p-values and degrees of freedom.
Definition sts_ttest.h:62
Eigen::MatrixXd matPval
Definition sts_ttest.h:64
Eigen::MatrixXd matTstat
Definition sts_ttest.h:63
static double regularizedBeta(double x, double a, double b)
static StatsTtestResult paired(const Eigen::MatrixXd &dataA, const Eigen::MatrixXd &dataB, StatsTailType tail=StatsTailType::Both)
Definition sts_ttest.cpp:77
static double tCdf(double t, int df)
static StatsTtestResult oneSample(const Eigen::MatrixXd &data, double mu=0.0, StatsTailType tail=StatsTailType::Both)
Definition sts_ttest.cpp:45
static StatsTtestResult independent(const Eigen::MatrixXd &dataA, const Eigen::MatrixXd &dataB, StatsTailType tail=StatsTailType::Both)
Definition sts_ttest.cpp:84