packages feed

bins (empty) → 0.1.0.0

raw patch · 6 files changed

+572/−0 lines, 6 filesdep +basedep +containersdep +finite-typelitssetup-changed

Dependencies added: base, containers, finite-typelits, ghc-typelits-knownnat, ghc-typelits-natnormalise, math-functions, profunctors, reflection, tagged, vector-sized

Files

+ CHANGELOG.md view
@@ -0,0 +1,9 @@+# Changelog++Version 0.1.0.0+---------------++*August 20, 2018*++*   Initial release+
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Justin Le (c) 2018++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Justin Le nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,48 @@+# [bins][hackage]++Bin continuous values into discrete containers in an interval, useful for+histograms.++[hackage]: https://hackage.haskell.org/package/bins++## Usage++```haskell+-- divide into 10 bins between 5 and 50, logarithmically+ghci> withBinner (logBS @10 5 50) $ \toBin -> do+        print (toBin 1)+        print (toBin 30)+        print (binIx (toBin 30))+        print (toBin 100)+```++```+Bin (-inf .. 5.0)       -- 1 is outside of range+Bin [25.06 .. 31.55)    -- 30 is inside bin enclosed by 25.06 and 31.55+PElem (Finite 7)        -- 30 is in Bin #7 (indexed from 0)+Bin [50 .. -inf)        -- 100 is outside of range+```++```haskell+-- Generate a histogram based on the bins from valules in a list+ghci> xs = [1..100] :: [Double]+ghci> withBinner (logBS @10 5 50) $ \toBin -> do+         mapM_ (\(b, n) -> putStrLn (displayBinDouble 4 b ++ "\t" ++ show n))+       . M.toList+       $ binFreq toBin xs+```++```+(-inf .. 5.0000)        4+[5.0000 .. 6.2946)      2+[6.2946 .. 7.9245)      1+[7.9245 .. 9.9763)      2+[9.9763 .. 12.5594)     3+[12.5594 .. 15.8114)    3+[15.8114 .. 19.9054)    4+[19.9054 .. 25.0594)    6+[25.0594 .. 31.5479)    6+[31.5479 .. 39.7164)    8+[39.7164 .. 50.0000)    10+[50.0000 .. +inf)       51+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bins.cabal view
@@ -0,0 +1,48 @@+-- This file has been generated from package.yaml by hpack version 0.28.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: af7b58caad9e45647be51713c90572885560f4a41301b7384272bc6827c3264a++name:           bins+version:        0.1.0.0+synopsis:       Aggregate continuous values into discrete bins+description:    Please see the README on GitHub at <https://github.com/mstksg/bins#readme>+category:       Math+homepage:       https://github.com/mstksg/bins#readme+bug-reports:    https://github.com/mstksg/bins/issues+author:         Justin Le+maintainer:     justin@jle.im+copyright:      (c) Justin Le 2018+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10+extra-source-files:+    CHANGELOG.md+    README.md++source-repository head+  type: git+  location: https://github.com/mstksg/bins++library+  exposed-modules:+      Data.Bin+  other-modules:+      Paths_bins+  hs-source-dirs:+      src+  ghc-options: -Wall -Wredundant-constraints+  build-depends:+      base >=4.7 && <5+    , containers+    , finite-typelits+    , ghc-typelits-knownnat+    , ghc-typelits-natnormalise+    , math-functions+    , profunctors+    , reflection+    , tagged+    , vector-sized >=1.0+  default-language: Haskell2010
+ src/Data/Bin.hs view
@@ -0,0 +1,435 @@+{-# LANGUAGE ApplicativeDo                            #-}+{-# LANGUAGE DataKinds                                #-}+{-# LANGUAGE DeriveFunctor                            #-}+{-# LANGUAGE ExistentialQuantification                #-}+{-# LANGUAGE FlexibleContexts                         #-}+{-# LANGUAGE KindSignatures                           #-}+{-# LANGUAGE LambdaCase                               #-}+{-# LANGUAGE RankNTypes                               #-}+{-# LANGUAGE RecordWildCards                          #-}+{-# LANGUAGE ScopedTypeVariables                      #-}+{-# LANGUAGE StandaloneDeriving                       #-}+{-# LANGUAGE TypeApplications                         #-}+{-# LANGUAGE TypeOperators                            #-}+{-# LANGUAGE UndecidableInstances                     #-}+{-# LANGUAGE ViewPatterns                             #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}+{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise       #-}++-- |+-- Module      : Data.Bin+-- Copyright   : (c) Justin Le 2018+-- License     : BSD3+--+-- Maintainer  : justin@jle.im+-- Stability   : experimental+-- Portability : non-portable+--+-- Tools for aggregating numeric values into a set of discrete bins+-- according to some binning specification.+--+-- See 'withBinner' for main usage information, and 'Bin' for the main+-- binned data type, and 'binFreq' for a common usage example.+--++module Data.Bin (+  -- * Specifying the binning+    BinView, linView, logView, gaussView+  , BinSpec(..), linBS, logBS, gaussBS+  , binSpecIntervals+  -- * Creating and manipulating bins+  , Bin, Binner, withBinner, fromFin+  -- ** Inspecting bins+  , binFin, binRange, binMin, binMax+  -- ** Showing bins+  , displayBin, displayBinDouble+  -- *** In-depth inspection+  , Pointed(..), pElem, binIx, fromIx+  -- * Untyped+  , SomeBin(..), sameBinSpec+  -- * Handy use patterns+  , binFreq+  ) where++import           Control.Monad+import           Data.Finite+import           Data.Foldable+import           Data.Profunctor+import           Data.Proxy+import           Data.Reflection+import           Data.Tagged+import           Data.Type.Equality+import           GHC.TypeNats+import           Numeric.SpecFunctions+import           Text.Printf+import           Unsafe.Coerce+import qualified Data.Map              as M+import qualified Data.Vector.Sized     as SV++-- | A bidirectional "view" to transform the data type before binning.+--+-- See 'linView' for a linear binning, and 'logView' for a logarithmic+-- binning.+--+-- This type is essentially 'Iso' from the /lens/ library, and any 'Iso''+-- from lens can be used here.  However, it is important that all of these+-- represent /monotonic/ isomorphisms.+type BinView a b = forall p. Profunctor p => p b b -> p a a++-- | Construct a 'BinView' based on "to" and "from" functions+--+-- It is important that the "to" and "from" functions be /inverses/ of each+-- other.  Furthermore, both "to" and "from" should be __monotonic__.+binView+    :: (a -> b)       -- ^ "to"+    -> (b -> a)       -- ^ "from"+    -> BinView a b+binView = dimap++-- | Linear binning+linView :: BinView a a+linView = binView id id++-- | Logarithmic binning (smaller bins at lower levels, larger bins at+-- higher levels).+logView :: Floating a => BinView a a+logView = binView log exp++-- | Binning based on a Gaussian Distribution.  Bins "by standard+-- deviation"; there are more bins the closer to the mean you get, and less+-- bins the farther away.+gaussView+    :: RealFrac a+    => a           -- ^ center / mean+    -> a           -- ^ standard deviation+    -> BinView a Double+gaussView μ σ = binView to from+  where+    to   = erf . realToFrac . (/ σ) . subtract μ+    from = (+ μ) . (* σ) . realToFrac . invErf++view :: BinView a b -> a -> b+view v = runForget (v (Forget id))++review :: BinView a b -> b -> a+review v = unTagged . v . Tagged++-- | Specification of binning.+--+-- A @'BinSpec' n a b@ will bin values of type @a@ into @n@ bins, according+-- to a scaling in type @b@.+--+-- Constructor is meant to be used with type application syntax to indicate+-- @n@, like @'BinSpec' @5 0 10 'linView'@+data BinSpec (n :: Nat) a b =+        BS { bsMin  :: a             -- ^ lower bound of values+           , bsMax  :: a             -- ^ upper bound of values+           , bsView :: BinView a b   -- ^ binning view+           }++-- | Convenient constructor for a 'BinSpec' for a linear scaling.+--+-- Meant to be used with type application syntax:+--+-- @+-- 'linBS' @5 0 10+-- @+linBS :: forall n a. a -> a -> BinSpec n a a+linBS mn mx = BS mn mx linView++-- | Convenient constructor for a 'BinSpec' for a logarithmic scaling.+--+-- Meant to be used with type application syntax:+--+-- @+-- 'logBS' @5 0 10+-- @+logBS :: forall n a. Floating a => a -> a -> BinSpec n a a+logBS mn mx = BS mn mx logView++-- | Convenient constructor for a 'BinSpec' for a gaussian scaling.  Uses+-- the midpoint as the inferred mean.+--+-- Meant to be used with type application syntax:+--+-- @+-- 'gaussBS' @5 0 10+-- @+gaussBS+    :: forall n a. RealFrac a+    => a+    -> a+    -> a+    -> BinSpec n a Double+gaussBS σ mn mx = BS mn mx (gaussView ((mn + mx)/2) σ)++-- | Data type extending a value with an extra "minimum" and "maximum"+-- value.+data Pointed a = Bot+               | PElem !a+               | Top+  deriving (Show, Eq, Ord, Functor)++-- | Extract the item from a 'Pointed' if it is neither the extra minimum+-- or maximum.+pElem :: Pointed a -> Maybe a+pElem = \case+    Bot     -> Nothing+    PElem x -> Just x+    Top     -> Nothing++-- | A @'Bin' s n@ is a single bin index out of @n@ partitions of the+-- original data set, according to a 'BinSpec' represented by @s@.+--+-- All 'Bin's with the same @s@ follow the same 'BinSpec', so you can+-- safely use 'binRange' 'withBinner'.+--+-- It has useful 'Eq' and 'Ord' instances.+--+-- Actually has @n + 2@ partitions, since it also distinguishes values+-- that are outside the 'BinSpec' range.+newtype Bin s n = Bin { _binIx :: Pointed (Finite n) }+  deriving (Eq, Ord)++-- | A more specific version of 'binFin' that indicates whether or not the+-- value was too high or too low for the 'BinSpec' range.+binIx :: Bin s n -> Pointed (Finite n)+binIx = _binIx++-- | Extract, potentially, the 'Bin' index.  Will return 'Nothing' if the+-- original value was outside the 'BinSpec' range.+--+-- See 'binIx' for a more specific version, which indicates if the original+-- value was too high or too low.+binFin :: Bin s n -> Maybe (Finite n)+binFin = pElem . binIx++tick+    :: forall n a b. (KnownNat n, Fractional b)+    => BinSpec n a b+    -> b+tick BS{..} = totRange / fromIntegral (natVal (Proxy @n))+  where+    totRange = view bsView bsMax - view bsView bsMin++packExtFinite+    :: KnownNat n+    => Integer+    -> Pointed (Finite n)+packExtFinite n+    | n < 0     = Bot+    | otherwise = maybe Top PElem . packFinite $ n++mkBin_+    :: forall n a b. (KnownNat n, RealFrac b)+    => BinSpec n a b+    -> a+    -> Pointed (Finite n)+mkBin_ bs = packExtFinite+          . floor+          . (/ tick bs)+          . subtract (scaleIn (bsMin bs))+          . scaleIn+  where+    scaleIn = view (bsView bs)++mkBin+    :: forall n a b s. (KnownNat n, RealFrac b, Reifies s (BinSpec n a b))+    => a+    -> Bin s n+mkBin = Bin . mkBin_ (reflect (Proxy @s))++-- | The type of a "binning function", given by 'withBinner'.  See+-- 'withBinner' for information on how to use.+type Binner s n a = a -> Bin s n++-- | With a 'BinSpec', give a "binning function" that you can use to create+-- bins within a continuation.  The binning function is meant to be used+-- with TypeApplications to specify how many bins to use:+--+-- @+-- 'withBinner' myBinSpec $ \toBin ->+--     show (toBin 2.8523)+-- @+withBinner+    :: (KnownNat n, RealFrac b)+    => BinSpec n a b+    -> (forall s. Reifies s (BinSpec n a b) => Binner s n a -> r)+    -> r+withBinner bs f = reify bs $ \(_ :: Proxy s) -> f @s mkBin++-- | Generate a vector of the boundaries deriving the bins from+-- a 'BinSpec'.  Can be useful for debugging.+binSpecIntervals+    :: forall n a b. (KnownNat n, Fractional b)+    => BinSpec n a b+    -> SV.Vector (n + 1) a+binSpecIntervals bs = SV.generate $ \i ->+    case strengthen i of+      Just (fromIntegral->i') -> scaleOut $ i' * t + scaleIn (bsMin bs)+      Nothing                 -> bsMax bs+  where+    t        = tick bs+    scaleIn  = view (bsView bs)+    scaleOut = review (bsView bs)++binRange_+    :: forall n a b. (KnownNat n, Fractional b)+    => BinSpec n a b+    -> Pointed (Finite n)+    -> (Maybe a, Maybe a)+binRange_ bs = \case+    Bot     -> ( Nothing         , Just (SV.head v))+    PElem i -> ( Just (v `SV.index` weaken i)+               , Just (v `SV.index` shift i )+               )+    Top     -> ( Just (SV.last v), Nothing         )+  where+    v        = binSpecIntervals @n bs++-- | Extract the minimum and maximum of the range indicabed by a given+-- 'Bin'.+--+-- A 'Nothing' value indicates that we are outside of the normal range of+-- the 'BinSpec', so is "unbounded" in that direction.+binRange+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))+    => Bin s n+    -> (Maybe a, Maybe a)+binRange = binRange_ (reflect (Proxy @s)) . binIx++-- | Extract the minimum of the range indicabed by a given 'Bin'.+--+-- A 'Nothing' value means that the original value was below the minimum+-- limit of the 'BinSpec', so is "unbounded" in the lower direction.+binMin+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))+    => Bin s n+    -> Maybe a+binMin = fst . binRange++-- | Extract the maximum of the range indicabed by a given 'Bin'.+--+-- A 'Nothing' value means that the original value was above the maximum+-- limit of the 'BinSpec', so is "unbounded" in the upper direction.+binMax+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))+    => Bin s n+    -> Maybe a+binMax = snd . binRange++-- | Display the interval maintained by a 'Bin'.+displayBin+    :: forall n a b s. (KnownNat n, Fractional b, Reifies s (BinSpec n a b))+    => (a -> String)        -- ^ how to display a value+    -> Bin s n+    -> String+displayBin f b = printf "%s .. %s" mn' mx'+  where+    (mn, mx) = binRange b+    mn' = case mn of+            Nothing -> "(-inf"+            Just m  -> "[" ++ f m+    mx' = case mx of+            Nothing -> "+inf)"+            Just m  -> f m ++ ")"++-- | Display the interval maintained by a 'Bin', if the 'Bin' contains+-- a 'Double'.+displayBinDouble+    :: forall n b s. (KnownNat n, Fractional b, Reifies s (BinSpec n Double b))+    => Int                      -- ^ number of decimal places to round+    -> Bin s n+    -> String+displayBinDouble d = displayBin (printf ("%." ++ show d ++ "f"))++instance (KnownNat n, Show a, Fractional b, Reifies s (BinSpec n a b)) => Show (Bin s n) where+    showsPrec d b = showParen (d > 10) $+      showString "Bin " . showString (displayBin @n show b)++-- | Generate a histogram: given a container of @a@s, generate a frequency+-- map of how often values in a given discrete bin occurred.+--+-- @+-- xs :: [Double]+-- xs = [1..100]+--+-- main :: IO ()+-- main = withBinner (logBS @10 5 50) $ \toBin ->+--     mapM_ (\(b, n) -> putStrLn (displayBinDouble 4 b ++ "\t" ++ show n))+--   . M.toList+--   $ binFreq toBin xs+-- @+--+-- @+-- (-inf .. 5.0000)        4+-- [5.0000 .. 6.2946)      2+-- [6.2946 .. 7.9245)      1+-- [7.9245 .. 9.9763)      2+-- [9.9763 .. 12.5594)     3+-- [12.5594 .. 15.8114)    3+-- [15.8114 .. 19.9054)    4+-- [19.9054 .. 25.0594)    6+-- [25.0594 .. 31.5479)    6+-- [31.5479 .. 39.7164)    8+-- [39.7164 .. 50.0000)    10+-- [50.0000 .. +inf)       51+-- @+binFreq+    :: forall n t a s. Foldable t+    => Binner s n a+    -> t a+    -> M.Map (Bin s n) Int+binFreq toBin = M.unionsWith (+) . map go . toList+  where+    go :: a -> M.Map (Bin s n) Int+    go x = M.singleton (toBin x) 1++-- | Construct a 'Bin' if you know the bin number you want to specify, or+-- if the bin is over or under the maximum.+fromIx :: Pointed (Finite n) -> Bin s n+fromIx = Bin++-- | Construct a 'Bin' if you know the bin number you want to specify.  See+-- 'fromIx' if you want to specify bins that are over or under the maximum,+-- as well.+fromFin :: Finite n -> Bin s n+fromFin = Bin . PElem++-- | A @'SomeBin' a n@ is @'Bin' s n@, except with the 'BinSpec' s hidden.+-- It's useful for returning out of 'withBinner'.+--+-- It has useful 'Eq' and 'Ord' instances.+--+-- To be able to "unify" two 'Bin's inside a 'SomeBin', use 'sameBinSpec'+-- to verify that the two 'SomeBin's were created with the same 'BinSpec'.+data SomeBin a n = forall s b. (Fractional b, Reifies s (BinSpec n a b)) +    => SomeBin { getSomeBin :: Bin s n }++deriving instance (KnownNat n, Show a) => Show (SomeBin a n)++-- | Compares if the ranges match.  Note that this is less performant than+-- comparing the original 'Bin's, or extracting and using 'sameBinSpec'.+instance (KnownNat n, Eq a) => Eq (SomeBin a n) where+    SomeBin x == SomeBin y = binRange x == binRange y++-- | Lexicographical ordering -- compares the lower bound, then the upper+-- bounds.  Note that this is less performant than comparing the original+-- 'Bin's, or extracting and using 'sameBinSpec'+instance (KnownNat n, Ord a) => Ord (SomeBin a n) where+    compare (SomeBin x) (SomeBin y) = compare (binRange x) (binRange y)++-- | Verify that the two reified 'BinSpec' types refer to the same one,+-- allowing you to use functions like '==' and 'compare' on 'Bin's that you+-- get out of a 'SomeBin'.+sameBinSpec+    :: forall s t n a b p. (Reifies s (BinSpec n a b), Reifies t (BinSpec n a b), KnownNat n, Eq a, Fractional b)+    => p s+    -> p t+    -> Maybe (s :~: t)+sameBinSpec _ _ = do+    guard $ binSpecIntervals bs1 == binSpecIntervals bs2+    pure (unsafeCoerce Refl)+  where+    bs1 = reflect (Proxy @s)+    bs2 = reflect (Proxy @t)