package umontreal.iro.lecuyer.gof;
   import cern.colt.list.*;

import umontreal.iro.lecuyer.util.*;
import umontreal.iro.lecuyer.probdist.*;
import java.util.Arrays;

/**
 * This class provides methods to compute several types of EDF goodness-of-fit
 * test statistics and to apply certain transformations to a set of
 * observations.  This includes the probability integral transformation
 * 
 * Ui = F(Xi), as well as the power ratio and iterated spacings
 * transformations. Here, 
 * U(0),..., U(N-1) stand
 * for N observations 
 * U0,..., UN-1 sorted by increasing order, where
 * 
 * 0 <= Ui <= 1.
 * 
 * 

* Note: This class uses the Colt library. * */ public class GofStat { private GofStat() {} // Used in discontinuous distributions private static double EPSILOND = 1.0E-15; /** * Applies the transformation * Ui = F(Vi) for * i = 0, 1,…, N - 1, * where F is a continuous distribution function, * and returns the result as an array of length N. * V represents the N observations contained in data, * and U, the returned transformed observations. * If data contains random variables from the distribution function * dist, then the result will contain uniform random variables * over [0, 1]. * * @param data array of observations to be transformed * * @param dist assumed distribution of the observations * * @return the array of transformed observations * */ public static DoubleArrayList unifTransform (DoubleArrayList data, ContinuousDistribution dist) { double[] v = data.elements(); int n = data.size(); double[] u = new double[n]; for (int i = 0; i < n; i++) u[i] = dist.cdf (v[i]); return new DoubleArrayList(u); } /** * Applies the transformation * Ui = F(Vi) for * i = 0, 1,…, N - 1, * where F is a discrete distribution function, * and returns the result as an array of length N. * V represents the N observations contained in data, * and U, the returned transformed observations. * *

* Note: If V are the values of random variables with * distribution function dist, then the result will contain * the values of discrete random variables distributed over the * set of values taken by dist, * not uniform random variables over [0, 1]. * * @param data array of observations to be transformed * * @param dist assumed distribution of the observations * * @return the array of transformed observations * */ public static DoubleArrayList unifTransform (DoubleArrayList data, DiscreteDistribution dist) { double[] v = data.elements(); int n = data.size(); double[] u = new double[n]; for (int i = 0; i < n; i++) u[i] = dist.cdf ((int)v[i]); return new DoubleArrayList (u); } /** * Assumes that the real-valued observations * U0,..., UN-1 * contained in sortedData * are already sorted in increasing order and computes the differences * between the successive observations. Let D be the differences * returned in spacings. * The difference * Ui - Ui-1 is put in Di for * n1 < i <= n2, whereas * Un1 - a is put into Dn1 * and * b - Un2 is put into Dn2+1. * The number of observations must be greater or equal than n2, we * must have * n1 < n2, and n1 and n2 are greater than 0. * The size of spacings will be at least N + 1 after * the call returns. * * @param sortedData array of sorted observations * * @param spacings pointer to an array object that will be filled with spacings * * @param n1 starting index, in sortedData, of the processed observations * * @param n2 ending index, in sortedData of the processed observations * * @param a minimum value of the observations * * @param b maximum value of the observations * * */ public static void diff (IntArrayList sortedData, IntArrayList spacings, int n1, int n2, int a, int b) { if (n1 < 0 || n2 < 0 || n1 >= n2 || n2 >= sortedData.size()) throw new IllegalArgumentException ("n1 and n2 not valid."); int[] u = sortedData.elements(); int n = sortedData.size(); if (spacings.size() <= (n2 + 2)) spacings.setSize (n2 + 2); int[] d = spacings.elements(); d[n1] = u[n1] - a; for (int i = n1 + 1; i <= n2; i++) d[i] = u[i] - u[i - 1]; d[n2+1] = b - u[n2]; } /** * Same as method * {@link #diff diff}(IntArrayList,IntArrayList,int,int,int,int), * but for the continuous case. * * @param sortedData array of sorted observations * * @param spacings pointer to an array object that will be filled with spacings * * @param n1 starting index of the processed observations in sortedData * * @param n2 ending index, in sortedData of the processed observations * * @param a minimum value of the observations * * @param b maximum value of the observations * * */ public static void diff (DoubleArrayList sortedData, DoubleArrayList spacings, int n1, int n2, double a, double b) { if (n1 < 0 || n2 < 0 || n1 >= n2 || n2 >= sortedData.size()) throw new IllegalArgumentException ("n1 and n2 not valid."); double[] u = sortedData.elements(); int n = sortedData.size(); if (spacings.size() <= (n2 + 2)) spacings.setSize (n2 + 2); double[] d = spacings.elements(); d[n1] = u[n1] - a; for (int i = n1 + 1; i <= n2; i++) d[i] = u[i] - u[i - 1]; d[n2+1] = b - u[n2]; } /** * Applies one iteration of the iterated spacings * transformation. * Let U be the N observations contained into data, * and let S be the spacings contained into spacings, * Assumes that S[0..N] contains the spacings * between N real numbers * U0,..., UN-1 in the interval [0, 1]. * These spacings are defined by *

*

*
* Si = U(i) - U(i-1),        1 <= i < N, *

* where U(0) = 0, * U(N-1) = 1, and * * U(0),..., U(N-1), are the Ui sorted in increasing order. * These spacings may have been obtained by calling * {@link #diff(DoubleArrayList,DoubleArrayList,int,int,double,double) diff}. * This method transforms the spacings into new * spacings: * it sorts * S0,..., SN to obtain * * S(0) <= S(1) <= S(2) <=  ...  <= S(N), * computes the weighted differences *
*
* * * * * * * * * * * * * * * * * * * * * * *
S0=(N + 1)S(0), *  
S1=N(S(1) - S(0)), *  
S2=(N - 1)(S(2) - S(1)), *  
 ...  *  
SN=S(N) - S(N-1), *  
*
* * and computes * Vi = S0 + S1 + ... + Si for * 0 <= i < N. * It then returns * S0,..., SN in S[0..N] and * * V1,..., VN in V[1..N]. * *

* Under the assumption that the Ui are i.i.d. U(0, 1), the new * Si can be considered as a new set of spacings having the same * distribution as the original spacings, and the Vi are a new sample * of i.i.d. U(0, 1) random variables, sorted by increasing order. * *

* This transformation is useful to detect clustering in a data * set: A pair of observations that are close to each other is transformed * into an observation close to zero. A data set with unusually clustered * observations is thus transformed to a data set with an * accumulation of observations near zero, which is easily detected by * the Anderson-Darling GOF test. * * @param data array of observations * * @param spacings spacings between the observations, will be filled with the new spacings * * */ public static void iterateSpacings (DoubleArrayList data, DoubleArrayList spacings) { if (spacings.size() < (data.size()+1)) throw new IllegalArgumentException ("Invalid array sizes."); double[] v = data.elements(); spacings.quickSortFromTo (0, data.size()); double[] s = spacings.elements(); int n = data.size(); for (int i = 0; i < n; i++) s[n - i] = (i + 1) * (s[n - i] - s[n - i - 1]); s[0] = (n + 1) * s[0]; v[0] = s[0]; for (int i = 1; i < n; i++) v[i] = v[i - 1] + s[i]; } /** * Applies the power ratios transformation W. * Let U be the N observations contained into sortedData. * Assumes that U contains N real numbers * * U(0),..., U(N-1) from the interval [0, 1], * already sorted in increasing order, and computes the transformations: *

*

*
* U'i = (U(i)/U(i+1))i+1,        i = 0,..., N - 1, *

* with * U(N) = 1. * These U'i are sorted in increasing order and put back in * U[1...N]. * If the U(i) are i.i.d. U(0, 1) sorted by increasing order, * then the U'i are also i.i.d. U(0, 1). * *

* This transformation is useful to detect clustering, as explained in * {@link #iterateSpacings(DoubleArrayList,DoubleArrayList) iterateSpacings}, * except that here a pair of * observations close to each other is transformed * into an observation close to 1. * An accumulation of observations near 1 is also easily detected by * the Anderson-Darling GOF test. * * @param sortedData sorted array of real-valued observations in the interval [0, 1] * that will be overwritten with the transformed observations * */ public static void powerRatios (DoubleArrayList sortedData) { double[] u = sortedData.elements(); int n = sortedData.size(); for (int i = 0; i < (n-1); i++) { if (u[i + 1] == 0.0 || u[i + 1] == -0.0) u[i] = 1.0; else u[i] = Math.pow (u[i] / u[i + 1], (double) i + 1); } u[n-1] = Math.pow (u[n-1], (double) n); sortedData.quickSortFromTo (0, sortedData.size() - 1); } /** * This class helps managing the partitions of possible outcomes * into categories for applying chi-square tests. * It permits one to automatically regroup categories to make sure that * the expected number of observations in each category is large enough. * To use this facility, one must first construct an * OutcomeCategoriesChi2 object by passing to the constructor * the expected number of observations for each original category. * Then, calling the method {@link #regroupCategories regroupCategories} will regroup * categories in a way that the expected number of observations in each * category reaches a given threshold minExp. * Experts in statistics recommend that minExp be always larger * than or equal to 5 for the chi-square test to be valid. Thus, * minExp = 10 is a safe value to use. * After the call, nbExp gives the expected numbers in the new * categories and loc[i] gives the relocation of category i, * for each i. That is, loc[i] = j means that category i has * been merged with category j because its original expected number was * too small, and nbExp[i] has been added to nbExp[j] * and then set to zero. * In this case, all observations that previously belonged * to category i are redirected to category j. * The variable nbCategories gives the final number of categories, * smin contains the new index of the lowest category, * and smax the new index of the highest category. * */ public static class OutcomeCategoriesChi2 { /** * Total number of categories. * */ public int nbCategories; /** * Minimum index for valid expected numbers * in the array nbExp. * */ public int smin; /** * Maximum index for valid expected numbers * in the array nbExp. * */ public int smax; /** * Expected number of observations for each category. * */ public double[] nbExp; /** * loc[i] gives the relocation of the category i in * the nbExp array. * */ public int[] loc; /** * Constructs an OutcomeCategoriesChi2 object * using the array nbExp for the number of expected observations in * each category. The smin and smax fields are set to 0 and * (n - 1) respectively, where n is the length of array nbExp. * The loc field is set such that loc[i]=i for each i. * The field nbCategories is set to n. * * @param nbExp array of expected observations for each category * * */ public OutcomeCategoriesChi2 (double[] nbExp) { this.nbExp = nbExp; smin = 0; smax = nbExp.length - 1; nbCategories = nbExp.length; loc = new int[nbExp.length]; for (int i = 0; i < nbExp.length; i++) loc[i] = i; } /** * Constructs an OutcomeCategoriesChi2 object using the * given nbExp expected observations array. Only the expected * numbers from the smin to smax (inclusive) indices will be * considered valid. The loc field is set such that loc[i]=i * for each i in the interval [smin, smax]. All loc[i] * for i  <=  smin are set to smin, and all loc[i] for * i  >=  smax are set to smax. * The field nbCategories is set to (smax - smin + 1). * * @param nbExp array of expected observations for each category * * @param smin Minimum index for valid expected number of observations * * @param smax Maximum index for valid expected number of observations * * */ public OutcomeCategoriesChi2 (double[] nbExp, int smin, int smax) { this.nbExp = nbExp; this.smin = smin; this.smax = smax; nbCategories = smax - smin + 1; loc = new int[nbExp.length]; for (int i = 0; i < smin; i++) loc[i] = smin; for (int i = smin; i < smax; i++) loc[i] = i; for (int i = smax; i < nbExp.length; i++) loc[i] = smax; } /** * Constructs an OutcomeCategoriesChi2 object. * The field nbCategories is set to nbCat. * * @param nbExp array of expected observations for each category * * @param smin Minimum index for valid expected number of observations * * @param smax Maximum index for valid expected number of observations * * @param loc array for which loc[i] gives the relocation of the category i * * */ public OutcomeCategoriesChi2 (double[] nbExp, int[] loc, int smin, int smax, int nbCat) { this.nbExp = nbExp; this.smin = smin; this.smax = smax; this.nbCategories = nbCat; this.loc = loc; } /** * Regroup categories as explained earlier, so that the expected * number of observations in each category is at least minExp. * We usually choose minExp = 10. * * @param minExp mininum number of expected observations in each category * * */ public void regroupCategories (double minExp) { int s0 = 0, j; double somme; nbCategories = 0; int s = smin; while (s <= smax) { /* Merge categories to ensure that the number expected in each category is >= minExp. */ if (nbExp[s] < minExp) { s0 = s; somme = nbExp[s]; while (somme < minExp && s < smax) { nbExp[s] = 0.0; ++s; somme += nbExp[s]; } nbExp[s] = somme; for (j = s0; j <= s; j++) loc[j] = s; } else loc[s] = s; ++nbCategories; ++s; } smin = loc[smin]; // Special case: the last category, if nbExp < minExp if (nbExp[smax] < minExp) { if (s0 > smin) --s0; nbExp[s0] += nbExp[smax]; nbExp[smax] = 0.0; --nbCategories; for (j = s0 + 1; j <= smax; j++) loc[j] = s0; smax = s0; } if (nbCategories <= 1) throw new IllegalStateException ("nbCategories < 2"); } /** * Provides a report on the categories. * * @return the categories represented as a string * */ public String toString() { int s, s0; double somme; final double EPSILON = 5.0E-16; StringBuffer sb = new StringBuffer(); sb.append ("-----------------------------------------------" + PrintfFormat.NEWLINE); if (nbExp[smin] < EPSILON) sb.append ("Only expected numbers larger than " + PrintfFormat.g (6, 1, EPSILON) + " are printed" + PrintfFormat.NEWLINE); sb.append ("Number of categories: " + PrintfFormat.d (4, nbCategories) + PrintfFormat.NEWLINE + "Expected numbers per category:" + PrintfFormat.NEWLINE + PrintfFormat.NEWLINE + "Category s nbExp[s]" + PrintfFormat.NEWLINE); // Do not print values < EPSILON s = smin; while (nbExp[s] < EPSILON) s++; int s1 = s; s = smax; while (nbExp[s] < EPSILON) s--; int s2 = s; somme = 0.0; for (s = s1 ; s <= s2; s++) if (loc[s] == s) { somme += nbExp[s]; sb.append (PrintfFormat.d (4, s) + " " + PrintfFormat.f (18, 4, nbExp[s]) + PrintfFormat.NEWLINE); } sb.append (PrintfFormat.NEWLINE + "Total expected number = " + PrintfFormat.f (18, 2, somme) + PrintfFormat.NEWLINE + PrintfFormat.NEWLINE + "The groupings:" + PrintfFormat.NEWLINE + " Category s loc[s]" + PrintfFormat.NEWLINE); for (s = smin; s <= smax; s++) { if ((s == smin) && (s > 0)) sb.append ("<= "); else if ((s == smax) && (s < loc.length - 1)) sb.append (">= "); else sb.append (" "); sb.append (PrintfFormat.d (4, s) + " " + PrintfFormat.d (12, loc[s]) + PrintfFormat.NEWLINE); } sb.append (PrintfFormat.NEWLINE + PrintfFormat.NEWLINE); return sb.toString(); } } /** * Computes and returns the chi-square statistic for the * observations oi in count[smin...smax], for which the * corresponding expected values ei are in nbExp[smin...smax]. * Assuming that i goes from 1 to k, where k = smax-smin+1 * is the number of categories, the chi-square statistic is defined as *

*

*
* X2 = ∑i=1k(oi - ei)2/ei.

*

* Under the hypothesis that the ei are the correct expectations and * if these ei are large enough, X2 follows approximately the * chi-square distribution with k - 1 degrees of freedom. * If some of the ei are too small, one can use * OutcomeCategoriesChi2 to regroup categories. * * @param nbExp number of expected observations in each category (or interval) * * @param count number of counted observations in each category * * @param smin index of the first valid data in count and nbExp * * @param smax index of the last valid data in count and nbExp * * @return the X2 statistic * */ public static double chi2 (double[] nbExp, int[] count, int smin, int smax) { double diff, khi = 0.0; for (int s = smin; s <= smax; s++) { if (nbExp[s] <= 0.0) { if (count[s] != 0) throw new IllegalArgumentException ( "nbExp[s] = 0 and count[s] > 0"); } else { diff = count[s] - nbExp[s]; khi += diff * diff / nbExp[s]; } } return khi; } /** * Computes and returns the chi-square statistic for the * observations stored in data, assuming that these observations follow * the discrete distribution dist. For dist, we assume that * there is one set * S = {a, a + 1,..., b - 1, b}, where a < b and a >=  0, * for which p(s) > 0 if s∈S and p(s) = 0 otherwise. * *

* Generally, it is not possible to divide the integers in intervals satisfying * * nP(a0 <= s < a1) = nP(a1 <= s < a2) = ... = nP(aj-1 <= s < aj) * for a discrete distribution, where n is the sample size, i.e., * the number of * observations stored into data. * To perform a general chi-square test, the method starts * from smin and finds the first non-negligible * probability * p(s) >= ε, where * ε = {@link DiscreteDistributionInt#EPSILON DiscreteDistributionInt.EPSILON}. * It uses smax to allocate an array storing the * number of expected observations (np(s)) for each s >=  smin. * Starting from s = smin, the np(s) terms are computed and * the allocated array grows if required until a negligible probability * term is found. * This gives the number of expected elements for * each category, where an outcome category corresponds here to * an interval in which sample observations could lie. * The categories are regrouped to have at least * minExp observations per category. * The method then counts the number of samples in each categories and calls * {@link #chi2(double[],int[],int,int) chi2} to get the chi-square test * statistic. If numCat is not * null, the number of categories after regrouping is returned * in numCat[0]. The number of degrees of freedom is equal to * numCat[0]-1. We usually choose minExp = 10. * * @param data observations, not necessarily sorted * * @param dist assumed probability distribution * * @param smin estimated minimum value of s for which p(s) > 0 * * @param smax estimated maximum value of s for which p(s) > 0 * * @param minExp minimum number of expected observations in each * interval * * @param numCat one-element array that will be filled with the number of * categories after regrouping * * @return the chi-square statistic for a discrete distribution * */ public static double chi2 (IntArrayList data, DiscreteDistributionInt dist, int smin, int smax, double minExp, int[] numCat) { int i; int n = data.size(); // Find the first non-negligible probability term and fix // the real smin. The linear search starts from the given smin. i = smin; while (dist.prob (i)*n <= DiscreteDistributionInt.EPSILON) i++; smin = i--; // smax > smin is required while (smax <= smin) smax = 2*smax + 1; // Allocate and fill the array of expected observations // Each category s corresponds to a value s for which p(s)>0. double[] nbExp = new double[smax+1]; do { i++; if (i > smax) { smax *= 2; double[] newNbExp = new double[smax + 1]; System.arraycopy (nbExp, smin, newNbExp, smin, nbExp.length - smin); nbExp = newNbExp; } nbExp[i] = dist.prob (i)*n; } while (nbExp[i] > DiscreteDistributionInt.EPSILON); smax = i - 1; // Regroup the expected observations intervals // satisfying np(s)>=minExp OutcomeCategoriesChi2 cat = new OutcomeCategoriesChi2 (nbExp, smin, smax); cat.regroupCategories (minExp); if (numCat != null) numCat[0] = cat.nbCategories; // Count the number of observations in each categories. int[] count = new int[cat.smax+1]; for (i = 0; i < count.length; i++) count[i] = 0; for (i = 0; i < n; i++) { int s = data.get (i); while (cat.loc[s] != s) s = cat.loc[s]; count[s]++; } // Perform the chi-square test return chi2 (cat.nbExp, count, cat.smin, cat.smax); } /** * Similar to {@link #chi2(double[],int[],int,int) chi2}, * except that the expected * number of observations per category is assumed to be the same for * all categories, and equal to nbExp. * * @param nbExp number of expected observations in each category (or interval) * * @param count number of counted observations in each category * * @param smin index of the first valid data in count and nbExp * * @param smax index of the last valid data in count and nbExp * * @return the X2 statistic * */ public static double chi2Equal (double nbExp, int[] count, int smin, int smax) { double diff, khi = 0.0; for (int s = smin; s <= smax; s++) { diff = count[s] - nbExp; khi += diff * diff; } return khi / nbExp; } /** * Computes the chi-square statistic for a continuous distribution. * Here, the equiprobable case can be used. Assuming that data contains * observations coming from the uniform distribution, the [0, 1] interval * is divided into 1/p subintervals, where p = minExp/n, n * being the sample size, i.e., the number of observations stored in * data. For each subinterval, the method counts the number of * contained observations and the chi-square statistic is computed * using {@link #chi2Equal(double,int[],int,int) chi2Equal}. * We usually choose minExp = 10. * * @param data array of observations in [0, 1) * * @param minExp minimum number of expected observations in each subintervals * * @return the chi-square statistic for a continuous distribution * */ public static double chi2Equal (DoubleArrayList data, double minExp) { int n = data.size(); if (n < (int)Math.ceil (minExp)) throw new IllegalArgumentException ("Not enough observations"); double p = minExp/n; int m = (int)Math.ceil (1.0/p); // to avoid an exception when data[i] = 1/p, reserve one element more int[] count = new int[m + 1]; for (int i = 0; i < n; i++) { int j = (int)Math.floor (data.get (i)/p); count[j]++; } // put the elements in count[m] where they belong: in count[m-1] count[m - 1] += count[m]; return chi2Equal (minExp, count, 0, m - 1); } /** * Equivalent to chi2Equal (data, 10). * * @param data array of observations in [0, 1) * * @return the chi-square statistic for a continuous distribution * */ public static double chi2Equal (DoubleArrayList data) { return chi2Equal (data, 10.0); } /** * Computes and returns the scan statistic SN(d ), * defined in {@link FBar#scan FBar.scan}. * Let U be the N observations contained into sortedData. * The N observations in U[0..N - 1] must be real numbers * in the interval [0, 1], sorted in increasing order. * (See {@link FBar#scan FBar.scan} for the distribution function of SN(d )). * * @param sortedData sorted array of real-valued observations in the interval [0, 1] * * @param d length of the test interval (∈(0, 1)) * * @return the scan statistic * */ public static int scan (DoubleArrayList sortedData, double d) { double[] u = sortedData.elements(); int n = sortedData.size(); int m = 1, j = 0, i = -1; double High = 0.0; while (j < (n-1) && High < 1.0) { ++i; High = u[i] + d; while (j < n && u[j] < High) ++j; // j is now the index of the first obs. to the right of High. if (j - i > m) m = j - i; } return m; } /** * Computes and returns the Cramér-von Mises statistic WN2. It is * defined by *

*

*
* WN2 = 1/(12N) + ∑j=0N-1(U(j) - (j + 0.5)/N)2,

*

* assuming that sortedData contains * U(0),..., U(N-1) * sorted in increasing order. * * @param sortedData array of sorted real-valued observations in the interval [0, 1] * * @return the Cramér-von Mises statistic * */ public static double cramerVonMises (DoubleArrayList sortedData) { double w, w2; double[] u = sortedData.elements(); int n = sortedData.size(); if (n <= 0) { System.err.println ("cramerVonMises: n <= 0"); return 0.0; } w2 = 1.0 / (12 * n); for (int i = 0; i < n; i++) { w = u[i] - (i + 0.5) / n; w2 += w * w; } return w2; } /** * Computes and returns the Watson statistic GN. It is * defined by *
*
* * * * * * * * * * *
GN=(N)1/2max0 <= j <= N-1{(j + 1)/N - U(j) + bar(U)N -1/2} *  
 =(N)1/2(DN+ + bar(U)N - 1/2), *  
*
* * where * bar(U)N is the average of the observations U(j), * assuming that sortedData contains the sorted * U(0),..., U(N-1). * * @param sortedData array of sorted real-valued observations in the interval [0, 1] * * @return the Watson statistic GN * */ public static double watsonG (DoubleArrayList sortedData) { double[] u = sortedData.elements(); int n = sortedData.size(); double sumZ; double d2; double dp, g; double unSurN = 1.0 / n; if (n <= 0) { System.err.println ("watsonG: n <= 0"); return 0.0; } // degenerate case n = 1 if (n == 1) return 0.0; // We assume that u is already sorted. dp = sumZ = 0.0; for (int i = 0; i < n; i++) { d2 = (i + 1) * unSurN - u[i]; if (d2 > dp) dp = d2; sumZ += u[i]; } sumZ = sumZ * unSurN - 0.5; g = Math.sqrt ((double) n) * (dp + sumZ); return g; } /** * Computes and returns the Watson statistic UN2. It is * defined by *
*
* * * * * * * * * * *
WN2=1/(12N) + ∑j=0N-1{U(j) - (j + 0.5)/N}2, *  
UN2=WN2 - N(bar(U)N -1/2)2.

*  
*
* * where * bar(U)N is the average of the observations U(j), * assuming that sortedData contains the sorted * * U(0),..., U(N-1). * * @param sortedData array of sorted real-valued observations in the interval [0, 1] * * @return the Watson statistic UN2 * * */ public static double watsonU (DoubleArrayList sortedData) { double sumZ, w, w2, u2; double[] u = sortedData.elements(); int n = sortedData.size(); if (n <= 0) { System.err.println ("watsonU: n <= 0"); return 0.0; } // degenerate case n = 1 if (n == 1) return 1.0 / 12.0; sumZ = 0.0; w2 = 1.0 / (12 * n); for (int i = 0; i < n; i++) { sumZ += u[i]; w = u[i] - (i + 0.5) / n; w2 += w * w; } sumZ = sumZ / n - 0.5; u2 = w2 - sumZ * sumZ * n; return u2; } public static double EPSILONAD = Num.DBL_EPSILON / 2.0; /** * Computes and returns the Anderson-Darling statistic AN2. * It is * defined by *
*
* * * * * * *
AN2=- N - 1/N    ∑j=0N-1{(2j + 1)ln(U(j)) + (2N - 1 - 2j)ln(1 - U(j))}, *  
*
* * assuming that sortedData contains * U(0),..., U(N-1). * * @param sortedData array of sorted real-valued observations in the interval [0, 1] * * @return the Anderson-Darling statistic * */ public static double andersonDarling (DoubleArrayList sortedData) { double u1; double u, a2; double[] v = sortedData.elements(); int n = sortedData.size(); if (n <= 0) { System.err.println ("andersonDarling: n <= 0"); return 0.0; } a2 = 0.0; for (int i = 0; i < n; i++) { u = v[i]; u1 = 1.0 - u; if (u < EPSILONAD) u = EPSILONAD; else if (u1 < EPSILONAD) u1 = EPSILONAD; a2 += (2*i + 1)*Math.log (u) + (1 + 2*(n - i - 1))* Math.log (u1); } a2 = -n - a2 / n; return a2; } /** * Computes the Kolmogorov-Smirnov (KS) test statistics * DN+, DN-, and DN. It is * defined by *
*
* * * * * * * * * * * * * * *
DN+=max0 <= j <= N-1((j + 1)/N - U(j)), *  
DN-=max0 <= j <= N-1(U(j) - j/N), *  
DN=max (DN+, DN-). *  
*
* * and returns an array of length 3 that contains their values at positions * 0, 1, and 2, respectively. * *

* These statistics compare the empirical distribution of * * U(1),..., U(N), which are assumed to be in sortedData, * with the uniform distribution. * * @param sortedData array of sorted real-valued observations in the interval [0, 1] * * @return an array containing DN+, DN- and DN, in that order * */ public static double[] kolmogorovSmirnov (DoubleArrayList sortedData) { double[] ret = new double[3]; double[] u = sortedData.elements(); int n = sortedData.size(); if (n <= 0) { ret[0] = ret[1] = ret[2] = 0.0; System.err.println ("kolmogorovSmirnov: n <= 0"); return ret; } double[] retjo = kolmogorovSmirnovJumpOne (sortedData, 0.0); ret[0] = retjo[0]; ret[1] = retjo[1]; if (ret[1] > ret[0]) ret[2] = ret[1]; else ret[2] = ret[0]; return ret; } /** * Compute the KS statistics DN+(a) and DN-(a) defined in * the description of the method * {@link FDist#kolmogorovSmirnovPlusJumpOne FDist.kolmogorovSmirnovPlusJumpOne}, assuming that F is the * uniform distribution over [0, 1] and that * * U(1),..., U(N) are in sortedData. * Returns an array of length 2 that contains their values at positions * 0 and 1, respectively. * * @param sortedData array of sorted real-valued observations in the interval [0, 1] * * @param a size of the jump * * @return an array containing DN+ and DN-, in that order * */ public static double[] kolmogorovSmirnovJumpOne (DoubleArrayList sortedData, double a) { /* Statistics KS+ and KS-. Case with 1 jump at a, near the lower tail of the distribution. */ double[] u = sortedData.elements(); int n = sortedData.size(); int j, i; double d2, d1, unSurN; double[] ret = new double[2]; if (n <= 0) { ret[0] = ret[1] = 0.0; System.err.println ("kolmogorovSmirnovJumpOne: n <= 0"); return ret; } ret[0] = 0.0; ret[1] = 0.0; unSurN = 1.0 / n; j = 0; while (j < n && u[j] <= a + EPSILOND) ++j; for (i = j - 1; i < n; i++) { if (i >= 0) { d1 = (i + 1) * unSurN - u[i]; if (d1 > ret[0]) ret[0] = d1; } if (i >= j) { d2 = u[i] - i * unSurN; if (d2 > ret[1]) ret[1] = d2; } } return ret; } /** * Computes a variant of the p-value p whenever a test statistic * has a discrete probability distribution. * This p-value is defined as follows: *
*

* * * * * * * * * * *
pL=P[Y <= y] *  
pR=P[Y >= y] *  
*
* *

*

*
* * * * * * * * * * * * * *
p =pR,        if pR < pL,
p =1 - pL,        if pR >= pL and pL < 0.5,
p =0.5        otherwise.
*

* The function takes pL and pR as input and returns p. * * @param pL left p-value * * @param pR right p-value * * @return the p-value for a test on a discrete distribution */ public static double pDisc (double pL, double pR) { double p; if (pR < pL) p = pR; else if (pL > 0.5) p = 0.5; else p = 1.0 - pL; // Note: si p est tres proche de 1, on perd toute la precision ici! // Note2: je ne pense pas que cela puisse se produire a cause des if (RS) return p; } }

Ads help maintain this website.