classify-frog-0.2.3: src/Spreadsheet/Palisade.hs
module Spreadsheet.Palisade (
FiveNumberSummary(..),
summary5Names,
summary5Number,
T(..),
summarySum5,
summary5String,
colNoSum,
colSum5,
colSum,
right,
pair,
triple,
maybe,
mapHeaders,
) where
import qualified Spreadsheet.Formula as CalcForm
import Spreadsheet.Formula (CellTracked, trackedNumber)
import Spreadsheet.Row
(Fraction, Precision, FieldTracked,
emptyField, fractionField, fracFieldFromTracked)
import qualified Quantile
import Control.Applicative (Applicative, pure, liftA2, (<*>))
import qualified Data.Foldable as Fold
import qualified Data.NonEmpty as NonEmpty
import Data.Tuple.HT (mapPair, mapTriple, mapFst)
import Data.Maybe (catMaybes)
import Data.Monoid ((<>))
import qualified Algebra.RealRing as Real
import qualified Algebra.Field as Field
import NumericPrelude.Numeric
import NumericPrelude.Base hiding (maybe)
import qualified Prelude as P
data FiveNumberSummary a =
FiveNumberSummary {
summaryMinimum, summaryQuartile1, summaryMedian,
summaryQuartile3, summaryMaximum :: a
}
percentages :: (Field.C a) => FiveNumberSummary a
percentages =
fmap (\k -> fromInteger k / fromInteger 4) $
FiveNumberSummary {
summaryMinimum = 0,
summaryQuartile1 = 1,
summaryMedian = 2,
summaryQuartile3 = 3,
summaryMaximum = 4
}
quartileAggs :: FiveNumberSummary CalcForm.Agg
quartileAggs =
FiveNumberSummary {
summaryMinimum = CalcForm.Minimum,
summaryQuartile1 = CalcForm.Quartile 1,
summaryMedian = CalcForm.Median,
summaryQuartile3 = CalcForm.Quartile 3,
summaryMaximum = CalcForm.Maximum
}
summary5 ::
(Field.C a, Real.C a) =>
NonEmpty.T [] (CellTracked a) ->
FiveNumberSummary (CalcForm.FormulaTracked a)
summary5 xs =
liftA2
(\op k -> CalcForm.aggregate op (flip Quantile.discrete k) xs)
quartileAggs percentages
summary5Number ::
(Field.C a, Real.C a) => NonEmpty.T [] a -> FiveNumberSummary a
summary5Number xs =
fmap (Quantile.discrete xs) percentages
summary5Names :: FiveNumberSummary String
summary5Names =
FiveNumberSummary {
summaryMinimum = "Minimum",
summaryQuartile1 = "Quartile 1",
summaryMedian = "Median",
summaryQuartile3 = "Quartile 3",
summaryMaximum = "Maximum"
}
instance Functor FiveNumberSummary where
fmap f (FiveNumberSummary q0 q1 q2 q3 q4) =
FiveNumberSummary (f q0) (f q1) (f q2) (f q3) (f q4)
instance Applicative FiveNumberSummary where
pure q = FiveNumberSummary q q q q q
FiveNumberSummary p0 p1 p2 p3 p4 <*> FiveNumberSummary q0 q1 q2 q3 q4 =
FiveNumberSummary (p0 q0) (p1 q1) (p2 q2) (p3 q3) (p4 q4)
instance Fold.Foldable FiveNumberSummary where
foldMap f (FiveNumberSummary q0 q1 q2 q3 q4) =
f q0 <> f q1 <> f q2 <> f q3 <> f q4
{- |
The 'T' type ensures that aggregations match the column headers.
Unfortunately, they do not assert that the aggregation values
are positioned below the aggregated data.
So far I have not found a way to achieve that.
It would certainly mean to give up the M monad.
For more clarity we could replace
the list types by two type constructor variables,
one for vertical and one for horizontal lists.
-}
data T a b =
Cons {
header :: [(String,Bool)],
aggregator :: Aggregator a,
selector :: a -> b,
selectedSummary5 :: [b] -> [FiveNumberSummary FieldTracked]
}
mapHeaders :: (String -> String) -> T a b -> T a b
mapHeaders f pal =
pal{header = map (mapFst f) $ header pal}
type Aggregator a = [a] -> [(FiveNumberSummary FieldTracked, FieldTracked)]
aggSummary5 ::
(Fraction a, Real.C a) =>
Precision -> [CellTracked a] -> FiveNumberSummary FieldTracked
aggSummary5 prec =
P.maybe (pure emptyField) (fmap (fracFieldFromTracked prec) . summary5) .
NonEmpty.fetch
summarySum :: (Fraction a, Real.C a) => Precision -> Aggregator (CellTracked a)
summarySum prec cells =
[(aggSummary5 prec cells, fracFieldFromTracked prec $ CalcForm.sum cells)]
summarySum5 :: (Fraction a, Real.C a) => Precision -> Aggregator (CellTracked a)
summarySum5 prec cells = [(aggSummary5 prec cells, emptyField)]
colNoSum :: String -> T () ()
colNoSum name =
Cons [(name,False)] (const [(pure emptyField, emptyField)]) id (const [])
summary5String ::
(Fraction a, Real.C a) =>
Precision -> [a] -> [FiveNumberSummary FieldTracked]
summary5String prec =
(:[]) .
P.maybe (pure emptyField) (fmap (fractionField prec) . summary5Number) .
NonEmpty.fetch
{-
The Precision given here should be the same as the one
given to the corresponding Row.putFraction,
but currently we cannot check that statically.
-}
colSum :: (Fraction a, Real.C a) => Precision -> String -> T (CellTracked a) a
colSum prec name =
Cons [(name,True)] (summarySum prec) trackedNumber (summary5String prec)
colSum5 :: (Fraction a, Real.C a) => Precision -> String -> T (CellTracked a) a
colSum5 prec name =
Cons [(name,True)] (summarySum5 prec) trackedNumber (summary5String prec)
summRight :: ([r0] -> [a]) -> ([r1] -> [a]) -> [r1] -> [a]
summRight f0 f1 cells = f0 [] ++ f1 cells
infixr 5 `right`
right :: T () () -> T a b -> T a b
right (Cons name0 agg0 _sel0 med0) (Cons name1 agg1 sel1 med1) =
Cons (name0++name1) (summRight agg0 agg1) sel1 (summRight med0 med1)
summPair :: ([r0] -> [a]) -> ([r1] -> [a]) -> [(r0,r1)] -> [a]
summPair f0 f1 cells =
let (cells0,cells1) = unzip cells
in f0 cells0 ++ f1 cells1
pair :: T a0 b0 -> T a1 b1 -> T (a0,a1) (b0,b1)
pair (Cons name0 agg0 sel0 med0) (Cons name1 agg1 sel1 med1) =
Cons (name0++name1)
(summPair agg0 agg1)
(mapPair (sel0, sel1))
(summPair med0 med1)
summTriple ::
([r0] -> [a]) -> ([r1] -> [a]) -> ([r2] -> [a]) -> [(r0,r1,r2)] -> [a]
summTriple f0 f1 f2 cells =
let (cells0,cells1,cells2) = unzip3 cells
in f0 cells0 ++ f1 cells1 ++ f2 cells2
triple :: T a0 b0 -> T a1 b1 -> T a2 b2 -> T (a0,a1,a2) (b0,b1,b2)
triple
(Cons name0 agg0 sel0 med0)
(Cons name1 agg1 sel1 med1)
(Cons name2 agg2 sel2 med2) =
Cons (name0++name1++name2)
(summTriple agg0 agg1 agg2)
(mapTriple (sel0, sel1, sel2))
(summTriple med0 med1 med2)
maybe :: T a b -> T (Maybe a) (Maybe b)
maybe (Cons name agg sel med) =
Cons name (agg . catMaybes) (fmap sel) (med . catMaybes)