arrayfire-0.9.0.0: test/ArrayFire/StatisticsSpec.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module ArrayFire.StatisticsSpec where
import Data.Word (Word32)
import ArrayFire hiding (not, abs, isNaN)
import Data.Maybe
import Data.Complex
import Test.Hspec
import Test.Hspec.ApproxExpect
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (NonEmptyList (..), (==>))
spec :: Spec
spec =
describe "Statistics spec" $ do
it "Should find the mean" $ do
mean (vector @Double 10 [1..]) 0
`shouldBe`
5.5
it "Should find the weighted-mean" $ do
case listToMaybe (toList (meanWeighted (vector @Double 10 [1..]) (vector @Double 10 [1..]) 0)) of
Nothing -> expectationFailure "expected Just 7.0, got Nothing"
Just v -> v `shouldBeApprox` 7.0
it "Should find the variance" $ do
var (vector @Double 8 [1..8]) Population 0
`shouldBe`
5.25
it "Should find the weighted variance (equal weights)" $ do
case listToMaybe (toList (varWeighted (vector @Double 8 [1..]) (vector @Double 8 (repeat 1)) 0)) of
Nothing -> expectationFailure "expected a value, got empty array"
Just v -> v `shouldBeApprox` 5.25
it "Should find the weighted variance (increasing weights)" $ do
case listToMaybe (toList (varWeighted (vector @Double 10 [1..]) (vector @Double 10 [1..]) 0)) of
Nothing -> expectationFailure "expected a value, got empty array"
Just v -> v `shouldBeApprox` (21/11 :: Double)
it "Should find the standard deviation" $ do
case listToMaybe (toList (stdev (vector @Double 10 (cycle [1,-1])) 0)) of
Nothing -> expectationFailure "expected a value, got empty array"
Just v -> v `shouldBeApprox` 1.0
it "Should find the covariance" $ do
cov (vector @Double 10 (repeat 1)) (vector @Double 10 (repeat 1)) False
`shouldBe`
0.0
it "Should find the median" $ do
median (vector @Double 10 [1..]) 0
`shouldBe`
5.5
it "Should find the mean of all elements across all dimensions" $ do
meanAll (matrix @Double (2,2) [[10,10],[10,10]])
`shouldBe` 10
it "Should find the weighted mean of all elements across all dimensions" $ do
meanAllWeighted (matrix @Double (2,2) [[10,10],[10,10]]) (matrix @Double (2,2) [[10,10],[10,10]])
`shouldBe` 10
it "Should find the variance of all elements across all dimensions" $ do
varAll (vector @Double 10 (repeat 10)) Population
`shouldBe` 0
it "Should find the weighted variance of all elements across all dimensions" $ do
varAllWeighted (vector @Double 10 (repeat 10)) (vector @Double 10 (repeat 10))
`shouldBe` 0
it "Should find the stdev of all elements across all dimensions" $ do
stdevAll (vector @Double 10 (repeat 10))
`shouldBe` 0
it "Should find the median of all elements across all dimensions" $ do
medianAll (vector @Double 10 [1..])
`shouldBe` 5.5
it "Should find the correlation coefficient" $ do
corrCoef (vector @Int 10 [1..]) (vector @Int 10 [10,9..])
`shouldBeApprox` (-1.0)
it "Should find the top k elements" $ do
let (vals,indexes) = topk ( vector @Double 10 [1..] ) 3 TopKDefault
vals `shouldBe` vector @Double 3 [10,9,8]
indexes `shouldBe` vector @Word32 3 [9,8,7]
it "Should compute mean and variance together (population)" $ do
let (m, v) = meanVar (vector @Double 4 [1,2,3,4]) VariancePopulation 0
m `shouldBe` scalar @Double 2.5
v `shouldBe` scalar @Double 1.25
it "Should compute mean and variance together (sample)" $ do
let (m, v) = meanVar (vector @Double 4 [1,2,3,4]) VarianceSample 0
m `shouldBe` scalar @Double 2.5
-- sample variance of [1,2,3,4] = 5/3 ≈ 1.6667
case listToMaybe (toList v) of
Just k -> k `shouldBeApprox` (5.0/3.0)
_ -> error "failure"
it "Should compute weighted mean and variance together" $ do
let uniform = vector @Double 4 (repeat 1.0)
(m, v) = meanVarWeighted (vector @Double 4 [1,2,3,4]) uniform VariancePopulation 0
m `shouldBe` scalar @Double 2.5
v `shouldBe` scalar @Double 1.25
describe "statistical properties" $ do
-- mean(x + c) = mean(x) + c (translation equivariance)
prop "mean is translation-equivariant" $ \(NonEmpty xs) (c :: Double) ->
let n = length xs
arr = vector @Double n xs
lhs = meanAll (arr + scalar c)
rhs = meanAll arr + c
in abs (lhs - rhs) < 1e-9
-- var(x + c) = var(x) (translation invariance)
prop "variance is translation-invariant" $ \(NonEmpty xs) (c :: Double) ->
let n = length xs
arr = vector @Double n xs
lhs = varAll arr Population
rhs = varAll (arr + scalar c) Population
in abs (lhs - rhs) < 1e-6 * (1 + abs lhs)
-- stdev(x)^2 = var(x, Population) (consistency)
prop "stdev^2 equals population variance" $ \(NonEmpty xs) ->
let n = length xs
arr = vector @Double n xs
sd = stdevAll arr
v = varAll arr Population
in abs (sd * sd - v) < 1e-9 + 1e-6 * abs v
-- mean(c * x) = c * mean(x) (scale equivariance)
prop "mean scales linearly" $ \(NonEmpty xs) (c :: Double) ->
let n = length xs
arr = vector @Double n xs
lhs = meanAll (scalar c * arr)
rhs = c * meanAll arr
in abs (lhs - rhs) < 1e-9 + 1e-9 * abs rhs
-- corrCoef(x, y) is in [-1, 1] (Cauchy-Schwarz)
prop "corrCoef is in [-1, 1]" $ \(NonEmpty xs) (ys :: [Double]) ->
let n = length xs
arr1 = vector @Double n xs
arr2 = vector @Double n (take n (ys ++ repeat 0))
r = corrCoef arr1 arr2
in not (isNaN r) && not (isInfinite r) ==> r >= -1.0 - 1e-9 && r <= 1.0 + 1e-9
-- sumAll = n * meanAll (for any non-empty list)
prop "sumAll = n * meanAll" $ \(NonEmpty xs) ->
let n = length xs
arr = vector @Double n xs
s = sumAll arr
m = meanAll arr
in abs (s - fromIntegral n * m) < 1e-9 + 1e-6 * abs s