import mean from "./mean.js";
import shuffleInPlace from "./shuffle_in_place.js";
/**
* 执行[置换检验](https://en.wikipedia.org/wiki/Resampling_(statistics)#Permutation_tests)
* 用于判断两个数据集是否具有*显著性差异*,使用组间均值差作为检验统计量。
* 支持以下假设类型:
* - two_side = 零假设:两个分布相同(双尾检验)
* - greater = 零假设:样本X观测值通常小于样本Y(右尾检验)
* - less = 零假设:样本X观测值通常大于样本Y(左尾检验)
* [了解单尾与双尾检验的区别](https://en.wikipedia.org/wiki/One-_and_two-tailed_tests)
*
* @param {Array<number>} sampleX 第一数据集(如实验组)
* @param {Array<number>} sampleY 第二数据集(如对照组)
* @param {string} alternative 备择假设类型,可选'two_sided'(默认), 'greater', 或'less'
* @param {number} k 置换分布生成值数量
* @param {Function} [randomSource=Math.random] 可选随机源
* @returns {number} p值 在零假设下观测到当前(或更极端)组间差异的概率
*
* @example
* var control = [2, 5, 3, 6, 7, 2, 5];
* var treatment = [20, 5, 13, 12, 7, 2, 2];
* permutationTest(control, treatment); // ~0.1324
*/
function permutationTest(sampleX, sampleY, alternative, k, randomSource) {
// 参数默认值设置
if (k === undefined) {
k = 10000;
}
if (alternative === undefined) {
alternative = "two_side";
}
// 参数有效性验证
if (
alternative !== "two_side" &&
alternative !== "greater" &&
alternative !== "less"
) {
throw new Error("`alternative`参数必须为'two_side', 'greater'或'less'");
}
// 计算各样本均值
const meanX = mean(sampleX);
const meanY = mean(sampleY);
// 计算初始检验统计量(后续将与此值比较)
const testStatistic = meanX - meanY;
// 创建检验统计量分布存储
const testStatDsn = new Array(k);
// 合并数据集以便后续混洗
const allData = sampleX.concat(sampleY);
const midIndex = Math.floor(allData.length / 2);
// 生成置换分布
for (let i = 0; i < k; i++) {
// 1. 混洗数据分配
shuffleInPlace(allData, randomSource);
const permLeft = allData.slice(0, midIndex);
const permRight = allData.slice(midIndex, allData.length);
// 2. 重新计算检验统计量
const permTestStatistic = mean(permLeft) - mean(permRight);
// 3. 存储统计量构建分布
testStatDsn[i] = permTestStatistic;
}
// 根据备择假设计算极端统计量数量
let numExtremeTStats = 0;
if (alternative === "two_side") {
// 双尾检验:统计绝对值大于等于观测值的案例
for (let i = 0; i <= k; i++) {
if (Math.abs(testStatDsn[i]) >= Math.abs(testStatistic)) {
numExtremeTStats += 1;
}
}
} else if (alternative === "greater") {
// 右尾检验:统计大于等于观测值的案例
for (let i = 0; i <= k; i++) {
if (testStatDsn[i] >= testStatistic) {
numExtremeTStats += 1;
}
}
} else {
// 左尾检验:统计小于等于观测值的案例
for (let i = 0; i <= k; i++) {
/* c8 ignore start */
if (testStatDsn[i] <= testStatistic) {
numExtremeTStats += 1;
}
/* c8 ignore end */
}
}
// 计算p值(极端案例比例)
return numExtremeTStats / k;
}
export default permutationTest;