51 const int n =
static_cast<int>(data.rows());
52 const int nVars =
static_cast<int>(data.cols());
56 RowVectorXd means = data.colwise().mean();
59 MatrixXd centered = data.rowwise() - means;
60 RowVectorXd stddev = (centered.colwise().squaredNorm() /
static_cast<double>(df)).array().sqrt();
63 double sqrtN = std::sqrt(
static_cast<double>(n));
64 RowVectorXd tstat = (means.array() - mu) / (stddev.array() / sqrtN);
67 MatrixXd matPval(1, nVars);
68 for (
int j = 0; j < nVars; ++j) {
69 matPval(0, j) = tToPval(tstat(j), df, tail);
83 return oneSample(dataA - dataB, 0.0, tail);
91 const int n =
static_cast<int>(data.rows());
92 const RowVectorXd x2 = data.array().square().colwise().mean();
93 const double dofScaling = std::sqrt(n / (n - 1.0));
94 const double sqrtN = std::sqrt(
static_cast<double>(n));
95 const auto tValues = [&](
const RowVectorXd& mu) -> RowVectorXd {
96 return mu.array() / ((x2.array() - mu.array().square()).sqrt() * dofScaling / sqrtN);
98 const RowVectorXd tObs = tValues(data.colwise().mean());
102 if (n < 2 || bits > 63) {
103 qWarning(
"StatsTtest::permutation: needs 2 to 63 samples, got %d.", n);
106 const quint64 maxPerms = (quint64(1) << bits) - 1;
107 std::vector<quint64> orders;
108 if (
static_cast<quint64
>(std::max(nPermutations, 0)) > maxPerms) {
109 for (quint64 s = 1; s <= maxPerms; ++s)
112 std::mt19937_64 gen(seed);
113 std::uniform_int_distribution<quint64> pick(1, maxPerms);
114 std::set<quint64> seen;
115 while (
static_cast<int>(orders.size()) < nPermutations - 1) {
116 const quint64 s = pick(gen);
117 if (seen.insert(s).second)
122 std::vector<double> h0;
123 h0.reserve(orders.size() + 1);
124 for (quint64 s : orders) {
126 for (
int i = 0; i < n; ++i)
127 signs(i) = ((s >> (n - 1 - i)) & 1) ? -1.0 : 1.0;
128 h0.push_back(tValues(signs.transpose() * data / n).cwiseAbs().maxCoeff());
130 h0.push_back(tObs.cwiseAbs().maxCoeff());
132 MatrixXd pvals(1, tObs.size());
133 for (
int j = 0; j < tObs.size(); ++j) {
134 const double t = tObs(j);
135 const auto count = std::count_if(h0.begin(), h0.end(), [&](
double h) {
136 return tail == StatsTailType::Both ? h >= std::abs(t) : tail == StatsTailType::Right ? h >= t
139 pvals(0, j) =
static_cast<double>(count) /
static_cast<double>(h0.size());
153 const int nA =
static_cast<int>(dataA.rows());
154 const int nB =
static_cast<int>(dataB.rows());
155 const int nVars =
static_cast<int>(dataA.cols());
156 const int df = nA + nB - 2;
158 RowVectorXd meanA = dataA.colwise().mean();
159 RowVectorXd meanB = dataB.colwise().mean();
162 MatrixXd centA = dataA.rowwise() - meanA;
163 MatrixXd centB = dataB.rowwise() - meanB;
164 RowVectorXd ssA = centA.colwise().squaredNorm();
165 RowVectorXd ssB = centB.colwise().squaredNorm();
166 RowVectorXd pooledVar = (ssA + ssB) /
static_cast<double>(df);
169 double invN = 1.0 /
static_cast<double>(nA) + 1.0 /
static_cast<double>(nB);
170 RowVectorXd tstat = (meanA - meanB).array() / (pooledVar.array() * invN).sqrt();
173 MatrixXd matPval(1, nVars);
174 for (
int j = 0; j < nVars; ++j) {
175 matPval(0, j) = tToPval(tstat(j), df, tail);
192 double x =
static_cast<double>(df) / (
static_cast<double>(df) + t * t);
195 return 1.0 - 0.5 * iBeta;
203double StatsTtest::tToPval(
double t,
int df,
StatsTailType tail)
206 const double x =
static_cast<double>(df) / (
static_cast<double>(df) + t * t);
207 const double twoSided =
regularizedBeta(x,
static_cast<double>(df) / 2.0, 0.5);
210 return t < 0.0 ? 0.5 * twoSided : 1.0 - 0.5 * twoSided;
212 return t >= 0.0 ? 0.5 * twoSided : 1.0 - 0.5 * twoSided;
231 if (x > (a + 1.0) / (a + b + 2.0)) {
235 double lnPre = a * std::log(x) + b * std::log(1.0 - x) - logBeta(a, b) - std::log(a);
236 return std::exp(lnPre) * betaCf(x, a, b);
241double StatsTtest::betaCf(
double x,
double a,
double b)
244 const int maxIter = 200;
245 const double eps = 1.0e-12;
246 const double tiny = 1.0e-30;
249 double qap = a + 1.0;
250 double qam = a - 1.0;
253 double d = 1.0 - qab * x / qap;
254 if (std::fabs(d) < tiny)
259 for (
int m = 1; m <= maxIter; ++m) {
263 double aa = m * (b - m) * x / ((qam + m2) * (a + m2));
265 if (std::fabs(d) < tiny)
268 if (std::fabs(c) < tiny)
274 aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
276 if (std::fabs(d) < tiny)
279 if (std::fabs(c) < tiny)
285 if (std::fabs(del - 1.0) < eps)
293double StatsTtest::logBeta(
double a,
double b)
295 return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
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).
Per-call output of a Student t-test: t-statistics, p-values and degrees of freedom.
static double regularizedBeta(double x, double a, double b)
static StatsTtestResult paired(const Eigen::MatrixXd &dataA, const Eigen::MatrixXd &dataB, StatsTailType tail=StatsTailType::Both)
static StatsTtestResult permutation(const Eigen::MatrixXd &data, int nPermutations=10000, StatsTailType tail=StatsTailType::Both, unsigned int seed=0)
static double tCdf(double t, int df)
static StatsTtestResult oneSample(const Eigen::MatrixXd &data, double mu=0.0, StatsTailType tail=StatsTailType::Both)
static StatsTtestResult independent(const Eigen::MatrixXd &dataA, const Eigen::MatrixXd &dataB, StatsTailType tail=StatsTailType::Both)