/// Normal-distribution percent point function, or inverse of the Normal CDF. /// This function(prob,mu,sig) == X where prob = NormalCDF(X,mu,sig). /// Ref http://www.itl.nist.gov/div898/handbook/ 1.3.6.6.1 /// @param prob probability or significance level of the test, >=0 and < 1 /// @param mu mean of the sample (location parameter of the distribution) /// @param sig std dev of the sample (scale parameter of the distribution) /// @return X the statistic at this probability double invNormalCDF(double prob, const double& mu, const double& sig) throw(Exception) { try { if(prob < 0 || prob >= 1) GPSTK_THROW(Exception("Invalid probability argument")); if(sig <= 0.0) GPSTK_THROW(Exception("Non-positive sigma")); static const double eps(1000000*std::numeric_limits<double>().epsilon()); if(prob < eps) return 0.0; if(1.0-prob < eps) GPSTK_THROW(Exception("Invalid probability -- too close to 1.0")); // find X such that prob == NormalCDF(X,mu,sig); use bracket method // we know 0.5 = NormalCDF(muwhen prob = 0.5, X = mu // also invNormalCDF(1-prob,mu,sig) = 2*mu - invNormalCDF(prob,mu,sig) // so make alpha >= 0.5 bool swap(false); double alpha(prob); if(prob < 0.5) { swap=true; alpha=1.0-prob; } // we know a0 = NormalCDF(X0,mu,sig) where a0=0.5,X0=mu and alpha >= 0.5 double X,X0(mu),X1,a; // first find X1 such that a1 = NormalCDF(X1,mu,sig) and a1 > alpha X1 = 2.0; while((a = NormalCDF(X1,mu,sig)) <= alpha) { X1 *= 2.0; } // bracket int niter(0); // iteration count while(1) { X = (X0+X1)/2.0; a = NormalCDF(X,mu,sig); //LOG(INFO) << "LOOP invNormalCDF X = " << niter << " " << std::fixed //<< std::setprecision(15) << X0 << " < " << X << " < " << X1 //<< " and a = " << a << " alpha = " << alpha << " a-alpha = " //<< std::scientific << std::setprecision(2) << a-alpha << " eps " << eps //<< " fabs(a-alpha)-eps " << ::fabs(alpha-a)-eps; if(::fabs(alpha-a) < eps) break; if(a > alpha) { X1 = X; } else { X0 = X; } if(++niter > 100) GPSTK_THROW(Exception("Failed to converge")); } return (swap ? 2.0*mu-X : X); } catch(Exception& e) { GPSTK_RETHROW(e); } }
TWxTestResult WxTest(const TVector<double>& baseline, const TVector<double>& test) { TVector<double> diffs; for (ui32 i = 0; i < baseline.size(); i++) { const double i1 = baseline[i]; const double i2 = test[i]; const double diff = i1 - i2; if (diff != 0) { diffs.push_back(diff); } } if (diffs.size() < 2) { TWxTestResult result; result.PValue = 0.5; result.WMinus = result.WPlus = 0; return result; } Sort(diffs.begin(), diffs.end(), [&](double x, double y) { return Abs(x) < Abs(y); }); double w_plus = 0; double w_minus = 0; double n = diffs.size(); for (int i = 0; i < n; ++i) { double sum = 0; double weight = 0; int j = i; double signPlus = 0; double signMinus = 0; for (j = i; j < n && diffs[j] == diffs[i]; ++j) { sum += (j + 1); ++weight; signPlus += diffs[i] >= 0; signMinus += diffs[i] < 0; } const double meanRank = sum / weight; w_plus += signPlus * meanRank; w_minus += signMinus * meanRank; i = j - 1; } TWxTestResult result; result.WPlus = w_plus; result.WMinus = w_minus; const double w = result.WPlus - result.WMinus; if (n > 16) { double z = w / sqrt(n * (n + 1) * (2 * n + 1) * 1.0 / 6); result.PValue = 2 * (1.0 - NormalCDF(Abs(z))); } else { result.PValue = 2 * CalcLevelOfSignificanceWXMPSR(Abs(w), (int) n); } result.PValue = 1.0 - result.PValue; return result; }