packages feed

approx-rand-test 0.0.4 → 0.1.0

raw patch · 9 files changed

+467/−129 lines, 9 filesdep +Chartdep +colourdep +containersdep −resourcetdep ~conduitdep ~statisticsdep ~vectorPVP ok

version bump matches the API change (PVP)

Dependencies added: Chart, colour, containers, data-accessor, filepath, vector-algorithms

Dependencies removed: resourcet

Dependency ranges changed: conduit, statistics, vector

API changes (from Hackage documentation)

- Statistics.Test.ApproxRand: approxRandPairScores :: TestStatistic -> Int -> Sample -> Sample -> RandWithError [Double]
- Statistics.Test.ApproxRand: approxRandScores :: TestStatistic -> Int -> Sample -> Sample -> Rand [Double]
+ Statistics.Test.ApproxRand: TestOptions :: TestType -> TestStatistic -> Int -> Double -> TestOptions
+ Statistics.Test.ApproxRand: TestResult :: Significance -> Double -> Sample -> TestResult
+ Statistics.Test.ApproxRand: approxRandPairStats :: TestStatistic -> Int -> Sample -> Sample -> RandWithError Sample
+ Statistics.Test.ApproxRand: approxRandStats :: TestStatistic -> Int -> Sample -> Sample -> Rand Sample
+ Statistics.Test.ApproxRand: data Significance
+ Statistics.Test.ApproxRand: data TestOptions
+ Statistics.Test.ApproxRand: instance Eq Significance
+ Statistics.Test.ApproxRand: instance Ord Significance
+ Statistics.Test.ApproxRand: instance Show Significance
+ Statistics.Test.ApproxRand: toIterations :: TestOptions -> Int
+ Statistics.Test.ApproxRand: toPValue :: TestOptions -> Double
+ Statistics.Test.ApproxRand: toTestStatistic :: TestOptions -> TestStatistic
+ Statistics.Test.ApproxRand: toTestType :: TestOptions -> TestType
+ Statistics.Test.ApproxRand: trRandomizedStats :: TestResult -> Sample
+ Statistics.Test.ApproxRand: trSignificance :: TestResult -> Significance
+ Statistics.Test.ApproxRand: trStat :: TestResult -> Double
+ Statistics.Test.Correction: bonferroni :: (Fractional f, Integral i) => f -> i -> f
+ Statistics.Test.Correction: sidak :: (Floating f, Integral i) => f -> i -> f
- Statistics.Test.ApproxRand: NotSignificant :: Double -> TestResult
+ Statistics.Test.ApproxRand: NotSignificant :: Double -> Significance
- Statistics.Test.ApproxRand: Significant :: Double -> TestResult
+ Statistics.Test.ApproxRand: Significant :: Double -> Significance
- Statistics.Test.ApproxRand: approxRandPairTest :: TestType -> TestStatistic -> Int -> Double -> Sample -> Sample -> RandWithError TestResult
+ Statistics.Test.ApproxRand: approxRandPairTest :: TestOptions -> Sample -> Sample -> RandWithError TestResult
- Statistics.Test.ApproxRand: approxRandTest :: TestType -> TestStatistic -> Int -> Double -> Sample -> Sample -> Rand TestResult
+ Statistics.Test.ApproxRand: approxRandTest :: TestOptions -> Sample -> Sample -> Rand TestResult

Files

approx-rand-test.cabal view
@@ -1,5 +1,5 @@ Name:          approx-rand-test-Version:       0.0.4+Version:       0.1.0 License:       OtherLicense License-file:  LICENSE Copyright:     Copyright 2012 Daniël de Kok@@ -11,6 +11,9 @@ Description:   Utility to perform approximate randomization tests. Cabal-Version: >= 1.8 Build-Type:    Simple+Extra-Source-Files:+  utils/cairo/CairoHistogram.hs+  utils/nocairo/CairoHistogram.hs  Source-Repository head   Type:     git@@ -19,45 +22,62 @@ Source-Repository this   Type:     git   Location: git://github.com/danieldk/approx-rand-test.git-  Tag:      0.0.4+  Tag:      0.1.0 +Flag withCairo+  Description: Enable non-terminal histogram output, requiring Cairo.+  Default:     False+ Library   HS-Source-Dirs:  src   Ghc-Options:     -O2 -Wall-  Exposed-modules: Statistics.Test.ApproxRand-  Build-Depends:   base >= 4 && < 5, vector == 0.9.*,+  Exposed-modules: Statistics.Test.ApproxRand, Statistics.Test.Correction+  Build-Depends:   base >= 4 && < 5, vector >= 0.9 && < 0.11,                    mersenne-random-pure64 == 0.2.0.*,                    monad-mersenne-random == 0.1,-                   mtl == 2.1.*, statistics == 0.10.1.*,+                   mtl == 2.1.*, statistics == 0.10.*,                    transformers == 0.3.*   Executable approx_rand_test   Main-Is:        approx-rand-test.hs-  Other-Modules:  SampleIO+  Other-Modules:  Histogram, SampleIO, TextHistogram   HS-Source-Dirs: utils   Ghc-Options:    -O2 -Wall   Build-Depends:  base >= 4 && < 5, approx-rand-test,-                  conduit == 0.4.*, text == 0.11.2.*,-                  vector == 0.9.*,+                  conduit >= 0.4 && < 0.6, text == 0.11.2.*,+                  containers == 0.4.*, vector >= 0.9 && < 0.11,                   mersenne-random-pure64 == 0.2.0.*,                   monad-mersenne-random == 0.1,-                  resourcet == 0.3.*,-                  statistics == 0.10.1.*+                  statistics == 0.10.*+  if flag(withCairo)+    Hs-Source-Dirs: utils/cairo+    Build-Depends:  Chart >= 0.15 && < 0.17, colour == 2.3.*,+                    data-accessor == 0.2.2.*, filepath >= 1.3 && < 1.5,+                    vector-algorithms == 0.5.*+  else+    Hs-Source-Dirs: utils/nocairo   Executable approx_rand_test_paired   Main-Is:        approx-rand-test-paired.hs-  Other-Modules:  SampleIO+  Other-Modules:  Histogram, SampleIO, TextHistogram   HS-Source-Dirs: utils   Ghc-Options:    -O2 -Wall   Build-Depends:  base >= 4 && < 5, approx-rand-test,-                  conduit == 0.4.*, text == 0.11.2.*,-                  vector == 0.9.*,+                  conduit >= 0.4 && < 0.6, text == 0.11.2.*,+                  containers == 0.4.*, vector >= 0.9 && < 0.11,                   mersenne-random-pure64 == 0.2.0.*,                   monad-mersenne-random == 0.1,-                  mtl == 2.1.*, resourcet == 0.3.*,-                  statistics == 0.10.1.*+                  mtl == 2.1.*, statistics == 0.10.*+  if flag(withCairo)+    Hs-Source-Dirs: utils/cairo+    Build-Depends:  Chart >= 0.15 && < 0.17, colour == 2.3.*,+                    data-accessor == 0.2.2.*, filepath >= 1.3 && < 1.5,+                    vector-algorithms == 0.5.*++  else+    Hs-Source-Dirs: utils/nocairo  Test-Suite tests   Type:           exitcode-stdio-1.0
src/Statistics/Test/ApproxRand.hs view
@@ -21,15 +21,17 @@   -- $examples    -- * Data types+  TestOptions(..),   TestResult(..),+  Significance(..),   RandWithError,    -- * Approximate randomization tests   approxRandTest,-  approxRandScores,+  approxRandStats,    approxRandPairTest,-  approxRandPairScores,+  approxRandPairStats,    -- * Test statistics   TestStatistic,@@ -45,7 +47,6 @@ import           Control.Monad.Mersenne.Random (R(..), Rand(..), getBool) import           Control.Monad.ST (runST) import           Control.Monad.Trans.Class (lift)-import           Data.List (foldl') import           Data.Vector.Generic ((++)) import qualified Data.Vector.Generic as VG import qualified Data.Vector.Generic.Mutable as GM@@ -97,43 +98,58 @@ -- difference as the test statistic, by running 'approxRandTest' in the 'Rand' -- monad (at the /p = 0.01/ level): ----- > evalRandom (approxRandTest TwoSided meanDifference 10000 0.01 s1 s2) prng+-- > evalRandom (approxRandTest TwoTailed meanDifference 10000 0.01 s1 s2) prng ----- It is also possible to obtain the test scores of the shuffled samples--- directly (e.g. to inspect the distribution of test scores) using the--- 'approxRandScores'/'approxRandPiarScores' functions:+-- It is also possible to obtain the test statistics of the shuffled samples+-- directly (e.g. to inspect the distribution of test statistics) using the+-- 'approxRandStats'/'approxRandPiarStats' functions: ----- > evalRandom (approxRandScores meanDifference 10000 0.01 s1 s2) prng+-- > evalRandom (approxRandStats meanDifference 10000 0.01 s1 s2) prng  -- | Computations with random numbers that can fail. type RandWithError a = ErrorT String Rand a +-- | Options for randomization tests+--+data TestOptions = TestOptions {+    toTestType      :: TestType,      -- ^ Type of test ('OneTailed' or 'TwoTailed')+    toTestStatistic :: TestStatistic, -- ^ Test statistic+    toIterations    :: Int,           -- ^ Number of shuffled samples to create+    toPValue        :: Double         -- ^ he p-value at which to test (e.g. 0.05)+}+ -- | -- The result of hypothesis testing.-data TestResult =-    Significant Double     -- ^ The null hypothesis should be rejected-  | NotSignificant Double  -- ^ Data is compatible with the null hypothesis+data TestResult = TestResult {+    trSignificance     :: Significance, -- ^ Significance+    trStat             :: Double,       -- ^ Test statistic for the samples+    trRandomizedStats  :: Sample        -- ^ Test statistics for the+                                        --   randomized samples+  } deriving (Eq, Ord, Show)++-- |+-- Significance.+data Significance =+    Significant    Double -- ^ The null hypothesis should be rejected+  | NotSignificant Double -- ^ Data is compatible with the null hypothesis   deriving (Eq, Ord, Show)  -- | -- Apply a pair-wise approximate randomization test. ----- In pair-wise approximate randomization tests the scores at a given+-- In pair-wise approximate randomization tests the data points at a given -- index are swapped between samples with a probability of 0.5. Since -- swapping is pairwise, the samples should have the same length. approxRandPairTest ::-     TestType                 -- ^ Type of test ('OneTailed' or 'TwoTailed')-  -> TestStatistic            -- ^ Test statistic-  -> Int                      -- ^ Number of shuffled samples to create-  -> Double                   -- ^ The p-value at which to test (e.g. 0.05)+     TestOptions              -- ^ Options for the test   -> Sample                   -- ^ First sample   -> Sample                   -- ^ Second sample   -> RandWithError TestResult -- ^ The test result-approxRandPairTest testType stat n pTest s1 s2 =-  (significance testType pTest n . countExtremes tOrig) `liftM`-    approxRandPairScores stat n s1 s2-  where-    tOrig = stat s1 s2+approxRandPairTest (TestOptions testType stat n pTest) s1 s2 = do+  stats <- approxRandPairStats stat n s1 s2+  let tOrig = stat s1 s2+  let sig = significance testType pTest n $ countExtremes tOrig $ stats+  return $ TestResult sig tOrig stats  -- | -- Apply an approximate randomization test.@@ -143,26 +159,23 @@ -- the original samples and the shuffled samples, to detect whether the -- difference of the samples is extreme or not. approxRandTest ::-     TestType        -- ^ Type of test ('OneTailed' or 'TwoTailed')-  -> TestStatistic   -- ^ Test statistic-  -> Int             -- ^ Number of shuffled sample to create-  -> Double          -- ^ The p-value at which to test (e.g. 0.05)+     TestOptions     -- ^ Options for the test   -> Sample          -- ^ First sample   -> Sample          -- ^ Second sample   -> Rand TestResult -- ^ The test result-approxRandTest testType stat n pTest s1 s2 =-  (significance testType pTest n . countExtremes tOrig) `liftM`-    approxRandScores stat n s1 s2-  where-    tOrig = stat s1 s2+approxRandTest (TestOptions testType stat n pTest) s1 s2 = do+  stats <- approxRandStats stat n s1 s2+  let tOrig = stat s1 s2+  let sig = significance testType pTest n $ countExtremes tOrig stats+  return $ TestResult sig tOrig stats  -- | Determine the significance. significance ::-     TestType   -- ^ Type of test ('OneTailed' or 'TwoTailed')-  -> Double     -- ^ The p-value at which to test (e.g. 0.05)-  -> Int        -- ^ Number of sample shuffles-  -> (Int, Int) -- ^ Extreme score counts-  -> TestResult -- ^ The test result+     TestType     -- ^ Type of test ('OneTailed' or 'TwoTailed')+  -> Double       -- ^ The p-value at which to test (e.g. 0.05)+  -> Int          -- ^ Number of sample shuffles+  -> (Int, Int)   -- ^ Extreme statistic counts+  -> Significance -- ^ The test result significance TwoTailed pTest n =   significant (pTest / 2) . pValue n . uncurry min significance OneTailed pTest n =@@ -170,15 +183,15 @@  -- | Wrap a p-value in a 'TestResult'. significant ::-     Double     -- ^ The p-value at which to test-  -> Double     -- ^ The p-value-  -> TestResult -- ^ The test result+     Double       -- ^ The p-value at which to test+  -> Double       -- ^ The p-value+  -> Significance -- ^ The test result significant pTail p =   if p < pTail then Significant p else NotSignificant p  -- | Calculate a p-value pValue ::-     Int    -- ^ Number of extreme scores+     Int    -- ^ Number of extreme outcomes   -> Int    -- ^ Number of shuffles   -> Double -- ^ The p-value pValue n r = (fromIntegral r + 1) / (fromIntegral n + 1)@@ -190,14 +203,14 @@ -- count value smaller than or equal to that value. Since we do not know -- the tail (yet), we count both. ----- Note: we can determine the tail by (1) averaging the test scores of the--- randomized samples, or (2) taking the smaller of the two counts.+-- Note: we can determine the tail by (1) averaging the test statistics of+-- the randomized samples, or (2) taking the smaller of the two counts. countExtremes ::      Double     -- ^ Test statistic value of the original samples-  -> [Double]   -- ^ Test statistic values of the randomized samples.+  -> Sample     -- ^ Test statistic values of the randomized samples.   -> (Int, Int) -- ^ Count of left- and right-tail extremes. countExtremes tOrig =-  foldl' count (0, 0)+  VG.foldl' count (0, 0)   where     count (left, right) tPerm =       let !newLeft = if tPerm <= tOrig then succ left else left in@@ -206,34 +219,35 @@  -- | -- Generate a given number of pairwise shuffled samples, and calculate--- the test score for each shuffle.+-- the test statistic for each shuffle. ----- Since the scores at a given index are swapped (with a probability of+-- Since the data points at a given index are swapped (with a probability of -- 0.5), the samples should have the same length.-approxRandPairScores ::+approxRandPairStats ::      TestStatistic          -- ^ Test statistic   -> Int                    -- ^ Number of shuffled samples to create   -> Sample                 -- ^ First sample   -> Sample                 -- ^ Second sample-  -> RandWithError [Double] -- ^ The scores of each shuffle-approxRandPairScores stat n s1 s2 = do+  -> RandWithError Sample   -- ^ The statistics of the shuffles+approxRandPairStats stat n s1 s2 = do   when (VG.length s1 /= VG.length s2) $-    throwError "Cannot calculate pairwise scores: samples have different sizes"-  lift $ replicateM n $ uncurry stat `liftM` shuffleVectorsPairwise s1 s2+    throwError "Cannot calculate pairwise statistic: samples have different sizes"+  lift $ liftM VG.fromList $ replicateM n $+    uncurry stat `liftM` shuffleVectorsPairwise s1 s2  -- | -- Generate a given number of shuffled samples, and calculate the test--- score for each shuffle.+-- statistic for each shuffle. -- -- This function does not require the samples to have an equal length.-approxRandScores ::+approxRandStats ::      TestStatistic -- ^ Test statistic   -> Int           -- ^ Number of shuffled samples to create   -> Sample        -- ^ First sample   -> Sample        -- ^ Second sample-  -> Rand [Double] -- ^ The scores of each shuffle-approxRandScores stat n s1 s2 =-  replicateM n $ uncurry stat `liftM` shuffleVectors s1 s2+  -> Rand Sample   -- ^ The statistics of the shuffles+approxRandStats stat n s1 s2 =+  liftM VG.fromList $ replicateM n $ uncurry stat `liftM` shuffleVectors s1 s2  -- | Pair-wise shuffle of two vectors. shuffleVectorsPairwise :: (VG.Vector v a, VG.Vector v Bool) =>
+ src/Statistics/Test/Correction.hs view
@@ -0,0 +1,42 @@+-- |+-- Copyright  : (c) 2012 Daniël de Kok+-- License    : BSD3+--+-- Maintainer : Daniël de Kok <me@danieldk.eu>+-- Stability  : experimental+--+-- The more tests that are applied on a dataset, the more likely it becomes+-- that a Type I error occurs (rejecting the null hypothesis when it is+-- true). This module provides significance level corrections for repeated+-- tests.+--+-- For more information, see:+--+--  * /Bonferroni and Šidák corrections for multiple comparisons, H. Abdi, 2007, in: N.J. Salkind (ed.), Encyclopedia of Measurement and Statistics, Thousand Oaks, CA: Sage/++module Statistics.Test.Correction (+  sidak,+  bonferroni+) where++-- |+-- Bonferroni correction: α* = α / n, where α is the intended+-- significance level over all tests, n the number of tests, and α* the+-- corrected significance level.+bonferroni :: (Fractional f, Integral i) =>+     f -- ^ Significance level+  -> i -- ^ Number of comparisons+  -> f -- ^ The corrected significance level+bonferroni level n =+  level / fromIntegral n++-- |+-- Sidak correction: α* = 1 - (1 - α)^(1 / n), where α is the intended+-- significance level over all tests, n the number of tests, and α* the+-- corrected significance level.+sidak :: (Floating f, Integral i) =>+     f -- ^ Significance level+  -> i -- ^ Number of comparisons+  -> f -- ^ The corrected significance level+sidak level n =+  1 - (1 - level) ** (1 / (fromIntegral n))
+ utils/Histogram.hs view
@@ -0,0 +1,33 @@+module Histogram (+  histogram+) where++import qualified Data.Set as Set+import qualified Data.Vector.Generic as VG+import           Data.Vector.Unboxed ((!))+import qualified Statistics.Sample.Histogram as SSH+import           Statistics.Test.ApproxRand+import           Statistics.Types (Sample)++histogram ::+     Int+  -> TestResult+  -> Either String [(Double, Int)]+histogram prefBins (TestResult _ _ randomizedStats) =+  if bins < 2 then+    Left "Cannot make a histogram, because the data cannot be divided in more than one bin."+  else+    Right $ VG.toList $ VG.zip ticks numSamples+  where+    bins = numBins prefBins randomizedStats+    (lowerBounds, numSamples) =+      SSH.histogram prefBins randomizedStats++    -- Compute the middle values of the bins.+    bucketSize = (lowerBounds ! 1) - (lowerBounds ! 0)+    bucketHalf = bucketSize / 2+    ticks = VG.map ((+) bucketHalf) lowerBounds++numBins :: Int -> Sample -> Int+numBins pref =+  min pref . Set.size . VG.foldl' (flip Set.insert) Set.empty
+ utils/TextHistogram.hs view
@@ -0,0 +1,30 @@+module TextHistogram (+  printHistogram,+) where++import           Control.Monad (forM_, when)+import           Statistics.Test.ApproxRand+import           System.IO (hPutStrLn, stderr)+import           Text.Printf (printf)++import           Histogram++printHistogram :: Int -> TestResult -> IO ()+printHistogram bins result@(TestResult _ score _) =+  case histogram bins result of+    Left err   -> hPutStrLn stderr err+    Right hist ->+      let bucketSize = (fst $ hist !! 1) - (fst $ head hist)+          bucketHalf = bucketSize / 2+          charsPerDot = (maximum $ map snd hist) `div` 50 in+            forM_ hist $ \(label, freq) -> do+              let blocks = freq `div` charsPerDot+              when (blocks > 0) $ do+                let lower = label - bucketHalf+                let barChar = if score >= lower && score < lower + bucketSize then+                            '✣'+                          else+                            '█'+                putStr $ printf "%12.3e | " $ lower + bucketHalf+                putStrLn $ replicate blocks barChar+
utils/approx-rand-test-paired.hs view
@@ -14,6 +14,7 @@ import           Control.Monad (liftM, when) import           Control.Monad.Error (runErrorT) import           Control.Monad.Mersenne.Random (evalRandom)+import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as V import           Data.Word (Word64) import           Statistics.Test.ApproxRand@@ -25,7 +26,9 @@ import           System.Random.Mersenne.Pure64 (PureMT, newPureMT, pureMT) import           Text.Printf (printf) +import           CairoHistogram import           SampleIO+import           TextHistogram  main :: IO () main = do@@ -43,8 +46,8 @@     Just seed -> return $ pureMT seed     Nothing   -> newPureMT -  if optPrintScores opts then-    printScores opts stat prng v1 v2+  if optPrintStats opts then+    printStats opts stat prng v1 v2   else     applyTest opts stat prng v1 v2 @@ -55,9 +58,10 @@   putStrLn $ printf "Iterations: %d" $ optIterations opts   putStrLn $ printf "Sample size: %d" $ V.length v1 -  -- Calculate test statistic for original score sets.-  let tOrig = stat v1 v2-  putStrLn $ printf "Test statistic: %f" tOrig+  let testOptions = TestOptions (optTestType opts)+                                stat+                                (optIterations opts)+                                (optSigP opts)    let testType = optTestType opts @@ -71,48 +75,81 @@   putStrLn $ printf "Test significance: %f" pTest   putStrLn $ printf "Tail significance: %f" pTail -  -- Approximate randomization testing.-  let test = runErrorT $ approxRandPairTest testType stat (optIterations opts) pTest v1 v2+  let test = runErrorT $ approxRandPairTest testOptions v1 v2   let result = evalRandom test prng+   case result of-    Left  err                -> putStrLn err-    Right (Significant    p) -> putStrLn $ printf "Significant: %f" p-    Right (NotSignificant p) -> putStrLn $ printf "Not significant: %f" p+    Left  err -> putStrLn err+    Right r   -> do+                   printResult r+                   when (optPrintHistogram opts) $ do+                     putStrLn ""+                     printHistogram 21 r+                   case (optWriteHistogram opts) of+                     Just fn ->+                       writeHistogram testOptions 31 r fn+                     Nothing ->+                       return () -printScores :: Options -> TestStatistic -> PureMT -> Sample ->+printResult :: TestResult -> IO ()+printResult result = do+  -- Print test statistic for the samples.+  putStrLn $ printf "Test statistic: %f" $ trStat result++  -- Print significance+  case trSignificance result of+    Significant    p -> putStrLn $ printf "Significant: %f" p+    NotSignificant p -> putStrLn $ printf "Not significant: %f" p++printStats :: Options -> TestStatistic -> PureMT -> Sample ->   Sample -> IO ()-printScores opts stat prng v1 v2 = do-  let test = runErrorT $ approxRandPairScores stat (optIterations opts) v1 v2+printStats opts stat prng v1 v2 = do+  let test = runErrorT $ approxRandPairStats stat (optIterations opts) v1 v2   case evalRandom test prng of     Left err     -> putStrLn err-    Right scores -> mapM_ (putStrLn . printf "%f") scores+    Right scores -> VG.mapM_ (putStrLn . printf "%f") scores  data Options = Options {-  optColumn        :: Int,-  optIterations    :: Int,-  optPRNGSeed      :: Maybe Word64,-  optPrintScores   :: Bool,-  optSigP          :: Double,-  optTestStatistic :: TestStatistic,-  optTestType      :: TestType+  optColumn         :: Int,+  optIterations     :: Int,+  optPRNGSeed       :: Maybe Word64,+  optPrintHistogram :: Bool,+  optPrintStats     :: Bool,+  optSigP           :: Double,+  optTestStatistic  :: TestStatistic,+  optTestType       :: TestType,+  optWriteHistogram :: Maybe String }  defaultOptions :: Options defaultOptions = Options {-  optColumn        = 1,-  optIterations    = 10000,-  optPRNGSeed      = Nothing,-  optPrintScores   = False,-  optSigP          = 0.01,-  optTestStatistic = differenceMean,-  optTestType      = TwoTailed+  optColumn         = 1,+  optIterations     = 10000,+  optPRNGSeed       = Nothing,+  optPrintHistogram = False,+  optPrintStats     = False,+  optSigP           = 0.01,+  optTestStatistic  = differenceMean,+  optTestType       = TwoTailed,+  optWriteHistogram = Nothing }  options :: [OptDescr (Options -> Options)] options =+  if hasCairoHistograms then+    cairoHistogramOption : mandatoryOptions+  else+    mandatoryOptions++-- Options that are always available, regardless of the compilation flags.+mandatoryOptions :: [OptDescr (Options -> Options)]+mandatoryOptions =   [ Option ['c'] ["column"]       (ReqArg (\arg opt -> opt { optColumn = read arg }) "NUMBER")       "column number (starting at 1)",+    Option ['h']    ["print-histogram"]+      (NoArg (\opt -> opt { optPrintHistogram = True }))+      "print a histogram of randomized sample statistics",     Option ['i'] ["iterations"]       (ReqArg (\arg opt -> opt { optIterations = read arg }) "NUMBER")       "number of iterations",@@ -122,9 +159,9 @@     Option ['p'] []       (ReqArg (\arg opt -> opt {optSigP = read arg }) "NUMBER")       "significant p-value",-    Option []    ["print-scores"]-      (NoArg (\opt -> opt { optPrintScores = True }))-      "output scores of permuted vectors",+    Option []    ["print-statistics"]+      (NoArg (\opt -> opt { optPrintStats = True }))+      "output test statistics of permuted vectors",     Option ['s'] ["seed"]       (ReqArg (\arg opt -> opt { optPRNGSeed = Just $ read arg}) "NUMBER")       "pseudorandom number generator seed",@@ -132,6 +169,12 @@       (ReqArg (\arg opt -> opt { optTestStatistic = parseStatistic arg}) "NAME")       "test statistic (mean_diff, var_ratio)"   ]++cairoHistogramOption :: OptDescr (Options -> Options)+cairoHistogramOption =+    Option ['w'] ["write-histogram"]+      (ReqArg (\arg opt -> opt { optWriteHistogram = Just arg}) "FILENAME")+      "write a histogram (supported file extensions: pdf, png, ps, and svg)"  getOptions :: IO (Options, [String]) getOptions = do
utils/approx-rand-test.hs view
@@ -13,6 +13,7 @@  import           Control.Monad (liftM, when) import           Control.Monad.Mersenne.Random (evalRandom)+import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as V import           Data.Word (Word64) import           Statistics.Test.ApproxRand@@ -24,7 +25,9 @@ import           System.Random.Mersenne.Pure64 (PureMT, newPureMT, pureMT) import           Text.Printf (printf) +import           CairoHistogram import           SampleIO+import           TextHistogram  main :: IO () main = do@@ -42,8 +45,8 @@     Just seed -> return $ pureMT seed     Nothing   -> newPureMT -  if optPrintScores opts then-    printScores opts stat prng v1 v2+  if optPrintStats opts then+    printStats opts stat prng v1 v2   else     applyTest opts stat prng v1 v2 @@ -54,9 +57,10 @@   putStrLn $ printf "Iterations: %d" $ optIterations opts   putStrLn $ printf "Sample sizes: %d %d" (V.length v1) (V.length v2) -  -- Calculate test statistic for original score sets.-  let tOrig = stat v1 v2-  putStrLn $ printf "Test statistic: %f" tOrig+  let testOptions = TestOptions (optTestType opts)+                                stat+                                (optIterations opts)+                                (optSigP opts)    let testType = optTestType opts @@ -71,44 +75,71 @@   putStrLn $ printf "Tail significance: %f" pTail    -- Approximate randomization testing.-  let test = approxRandTest testType stat (optIterations opts) pTest v1 v2+  let test = approxRandTest testOptions v1 v2   let result = evalRandom test prng-  case result of++  -- Print test statistic for the samples.+  putStrLn $ printf "Test statistic: %f" $ trStat result++  case trSignificance result of     Significant    p -> putStrLn $ printf "Significant: %f" p     NotSignificant p -> putStrLn $ printf "Not significant: %f" p -printScores :: Options -> TestStatistic -> PureMT -> Sample ->+  when (optPrintHistogram opts) $ do+    putStrLn ""+    printHistogram 21 result+  case (optWriteHistogram opts) of+    Just fn ->+      writeHistogram testOptions 31 result fn+    Nothing ->+      return ()++printStats :: Options -> TestStatistic -> PureMT -> Sample ->   Sample -> IO ()-printScores opts stat prng v1 v2 =-  mapM_ (putStrLn . printf "%f") $-    evalRandom (approxRandScores stat (optIterations opts) v1 v2) prng+printStats opts stat prng v1 v2 =+  VG.mapM_ (putStrLn . printf "%f") $+    evalRandom (approxRandStats stat (optIterations opts) v1 v2) prng  data Options = Options {-  optColumn        :: Int,-  optIterations    :: Int,-  optPRNGSeed      :: Maybe Word64,-  optPrintScores   :: Bool,-  optSigP          :: Double,-  optTestStatistic :: TestStatistic,-  optTestType      :: TestType+  optColumn         :: Int,+  optPrintHistogram :: Bool,+  optIterations     :: Int,+  optPRNGSeed       :: Maybe Word64,+  optPrintStats     :: Bool,+  optSigP           :: Double,+  optTestStatistic  :: TestStatistic,+  optTestType       :: TestType,+  optWriteHistogram :: Maybe String }  defaultOptions :: Options defaultOptions = Options {-  optColumn        = 1,-  optIterations    = 10000,-  optPRNGSeed      = Nothing,-  optPrintScores   = False,-  optSigP          = 0.01,-  optTestStatistic = meanDifference,-  optTestType      = TwoTailed+  optColumn         = 1,+  optIterations     = 10000,+  optPRNGSeed       = Nothing,+  optPrintHistogram = False,+  optPrintStats     = False,+  optSigP           = 0.01,+  optTestStatistic  = meanDifference,+  optTestType       = TwoTailed,+  optWriteHistogram = Nothing }  options :: [OptDescr (Options -> Options)]-options =+options = +  if hasCairoHistograms then+    cairoHistogramOption : mandatoryOptions+  else+    mandatoryOptions++mandatoryOptions :: [OptDescr (Options -> Options)]+mandatoryOptions =   [ Option ['c'] ["column"]       (ReqArg (\arg opt -> opt { optColumn = read arg }) "NUMBER")       "column number (starting at 1)",+    Option ['h']    ["print-histogram"]+      (NoArg (\opt -> opt { optPrintHistogram = True }))+      "print a histogram of randomized sample statistics",     Option ['i'] ["iterations"]       (ReqArg (\arg opt -> opt { optIterations = read arg }) "NUMBER")       "number of iterations",@@ -119,8 +150,8 @@       (ReqArg (\arg opt -> opt {optSigP = read arg }) "NUMBER")       "significant p-value",     Option []    ["print-scores"]-      (NoArg (\opt -> opt { optPrintScores = True }))-      "output scores of permuted vectors",+      (NoArg (\opt -> opt { optPrintStats = True }))+      "output statistics of permuted vectors",     Option ['s'] ["seed"]       (ReqArg (\arg opt -> opt { optPRNGSeed = Just $ read arg}) "NUMBER")       "pseudorandom number generator seed",@@ -128,6 +159,12 @@       (ReqArg (\arg opt -> opt { optTestStatistic = parseStatistic arg}) "NAME")       "test statistic (mean_diff, var_ratio)"   ]++cairoHistogramOption :: OptDescr (Options -> Options)+cairoHistogramOption =+    Option ['w'] ["write-histogram"]+      (ReqArg (\arg opt -> opt { optWriteHistogram = Just arg}) "FILENAME")+      "write a histogram (supported file extensions: pdf, png, ps, and svg)"  getOptions :: IO (Options, [String]) getOptions = do
+ utils/cairo/CairoHistogram.hs view
@@ -0,0 +1,104 @@+module CairoHistogram (+  hasCairoHistograms,+  writeHistogram+) where++import           Control.Monad.ST (runST)+import           Data.Accessor ((^=), (^:))+import qualified Data.Colour as Colour+import qualified Data.Colour.Names as ColourNames+import qualified Data.Vector.Algorithms.Intro as VI+import qualified Data.Vector.Generic as VG+import           Data.Vector.Unboxed ((!))+import qualified Graphics.Rendering.Chart as Chart+import           Statistics.Test.ApproxRand+import           Statistics.Test.Types (TestType(..))+import qualified System.FilePath.Posix as FP+import           System.IO (hPutStrLn, stderr)++import           Histogram++hasCairoHistograms :: Bool+hasCairoHistograms = True++writeHistogram :: TestOptions -> Int -> TestResult -> FP.FilePath -> IO ()+writeHistogram testOptions bins result path =+  case histogram bins result of+    Left err -> hPutStrLn stderr err+    Right  h ->+      let r = createHistogram testOptions result h in+        case snd $ FP.splitExtension path of+          ".pdf" -> Chart.renderableToPDFFile r 800 600 path+          ".png" -> do+            _ <- Chart.renderableToPNGFile r 800 600 path+            return ()+          ".ps"  -> Chart.renderableToPSFile  r 800 600 path+          ".svg" -> Chart.renderableToSVGFile r 800 600 path+          _      -> hPutStrLn stderr "Unknown output format!"++-- Creates a histogram. The histogram is stacked, but the second bar+-- is always empty, except for the bin of the original statistic (if any).+-- There, the first bar is empty and the second bar has the frequency.+-- Yes, this is cheating ;).+createHistogram :: TestOptions -> TestResult -> [(Double, Int)] ->+  Chart.Renderable ()+createHistogram testOptions result his =+  Chart.toRenderable layout+  where+    layout =+        Chart.layout1_background  ^= Chart.solidFillStyle opaqueWhite+      $ Chart.layout1_left_axis   ^: Chart.laxis_override ^= Chart.axisTicksHide+      $ Chart.layout1_right_axis  ^: Chart.laxis_title    ^= "Frequency"+      $ Chart.layout1_bottom_axis ^: Chart.laxis_title    ^= "Statistic"+      $ Chart.layout1_plots       ^= [ Right (Chart.plotBars randomizationBars),+                                       Right statisticLine, Right sigLines ]+      $ Chart.setLayout1Foreground   opaqueBlack+      $ Chart.defaultLayout1+    randomizationBars =+        Chart.plot_bars_style       ^= Chart.BarsStacked+      $ Chart.plot_bars_spacing     ^= Chart.BarsFixGap 6 2+      -- $ Chart.plot_bars_spacing     ^= Chart.BarsFixGap 0 0+      $ Chart.plot_bars_item_styles ^= [+          (Chart.solidFillStyle $ opaqueGreen, Nothing) ]+      $ Chart.plot_bars_values      ^= map (\(b, f) -> (b, [f])) his+      $ Chart.defaultPlotBars+    statisticLine =+      Chart.vlinePlot "Statistic for samples" (Chart.solidLine 2 (opaqueRed)) $ trStat result+    sigLines      =+      vlinesPlot "Significance" (Chart.dashedLine 2 [8, 4] opaqueBlack) $+        sigBounds testOptions result++-- Plot vertical lines, adapted from Chart.vlinePlot.+vlinesPlot :: String -> Chart.CairoLineStyle -> [a] -> Chart.Plot a b+vlinesPlot t ls xs = Chart.toPlot Chart.defaultPlotLines {+    Chart.plot_lines_title_        = t,+    Chart.plot_lines_style_        = ls,+    Chart.plot_lines_limit_values_ =+      [[(Chart.LValue v, Chart.LMin),(Chart.LValue v, Chart.LMax)] | v <- xs]+    }++-- Calculate the bounds of significance.+sigBounds :: TestOptions -> TestResult -> [Double]+sigBounds (TestOptions testType _ n pTest) (TestResult _ _ stats) =+  case testType of+    TwoTailed -> [sorted ! (nExtreme - 1), sorted ! (n - nExtreme)]+    OneTailed -> [sorted ! (n - nExtreme)]+  where+    sorted           = sortVector stats+    nExtreme         = floor $ (pVal testType pTest) * (fromIntegral n + 1) - 1+    pVal OneTailed p = p+    pVal TwoTailed p = p / 2+    -- XXX: Fix extreme cases: p-value of 0, small n.++sortVector :: (Ord a, VG.Vector v a) => v a -> v a+sortVector v = runST $ do+  s <- VG.thaw v+  VI.sort s+  VG.freeze s++-- Convenience...+opaqueBlack, opaqueGreen, opaqueRed, opaqueWhite :: Colour.AlphaColour Double+opaqueBlack = Colour.opaque ColourNames.black+opaqueGreen = Colour.opaque ColourNames.green+opaqueRed   = Colour.opaque ColourNames.red+opaqueWhite = Colour.opaque ColourNames.white
+ utils/nocairo/CairoHistogram.hs view
@@ -0,0 +1,15 @@+module CairoHistogram (+  hasCairoHistograms,+  writeHistogram+) where++import           System.IO (hPutStrLn, stderr)++hasCairoHistograms :: Bool+hasCairoHistograms = False++-- Stub function, for when we did not compile with Cairo support.+writeHistogram :: a -> Int -> b -> c -> IO ()+writeHistogram _ _ _ _ = do+  hPutStrLn stderr "This program was built without Cairo support, cannot make a histogram."+