{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

{- |
Internal shared helpers used by both the terminal and web plot backends.
Not part of the public API.
-}
module DataFrame.Display.Internal.Common (
    -- * Aggregation
    Agg (..),
    aggLabel,
    aggregateByGroup,

    -- * Column extraction
    extractStringColumn,
    extractNumericColumn,
    columnToStrings,
    columnToDoubles,

    -- * Type guards
    isNumericColumn,
    isNumericColumnCheck,

    -- * Categorical helpers
    getCategoricalCounts,

    -- * Top-N rollup
    groupWithOther,
    groupWithOtherForPie,
) where

import qualified Data.Bifunctor
import qualified Data.List as L
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl))
import qualified Data.Vector as V
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Unboxed as VU
import Data.Word (Word8)
import GHC.Stack (HasCallStack)
import Type.Reflection (TypeRep, typeRep)

import DataFrame.Internal.Column (
    Column (..),
    Columnable,
    isNumeric,
    materializeMerged,
    materializePacked,
 )
import DataFrame.Internal.DataFrame (DataFrame (..), getColumn)
import DataFrame.Internal.Types

-- | Aggregation strategy for grouped/categorical plots.
data Agg
    = -- | Count rows per group; ignores the value column.
      Count
    | -- | Sum of value column per group.
      Sum
    | -- | Arithmetic mean of value column per group.
      Mean
    | -- | Median of value column per group.
      Median
    | -- | Minimum of value column per group.
      Min
    | -- | Maximum of value column per group.
      Max
    deriving (Agg -> Agg -> Bool
(Agg -> Agg -> Bool) -> (Agg -> Agg -> Bool) -> Eq Agg
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Agg -> Agg -> Bool
== :: Agg -> Agg -> Bool
$c/= :: Agg -> Agg -> Bool
/= :: Agg -> Agg -> Bool
Eq, Int -> Agg -> ShowS
[Agg] -> ShowS
Agg -> String
(Int -> Agg -> ShowS)
-> (Agg -> String) -> ([Agg] -> ShowS) -> Show Agg
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Agg -> ShowS
showsPrec :: Int -> Agg -> ShowS
$cshow :: Agg -> String
show :: Agg -> String
$cshowList :: [Agg] -> ShowS
showList :: [Agg] -> ShowS
Show)

-- | Short label for an aggregation, used in auto-generated chart titles.
aggLabel :: Agg -> T.Text
aggLabel :: Agg -> Text
aggLabel Agg
Count = Text
"count"
aggLabel Agg
Sum = Text
"sum"
aggLabel Agg
Mean = Text
"mean"
aggLabel Agg
Median = Text
"median"
aggLabel Agg
Min = Text
"min"
aggLabel Agg
Max = Text
"max"

{- | Apply an aggregation across rows grouped by the given category column.

For 'Count', the value column is ignored. For all other aggregations, the
value column is extracted as numeric and folded per group. Group order is
the order in which categories first appear.
-}
aggregateByGroup ::
    (HasCallStack) =>
    Agg ->
    -- | Grouping column (categorical).
    T.Text ->
    -- | Value column; required for everything except 'Count'.
    Maybe T.Text ->
    DataFrame ->
    [(T.Text, Double)]
aggregateByGroup :: HasCallStack =>
Agg -> Text -> Maybe Text -> DataFrame -> [(Text, Double)]
aggregateByGroup Agg
Count Text
groupCol Maybe Text
_ DataFrame
df =
    let groups :: [Text]
groups = HasCallStack => Text -> DataFrame -> [Text]
Text -> DataFrame -> [Text]
extractStringColumn Text
groupCol DataFrame
df
        m :: Map Text Double
m = (Map Text Double -> Text -> Map Text Double)
-> Map Text Double -> [Text] -> Map Text Double
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
L.foldl' (\Map Text Double
acc Text
g -> (Double -> Double -> Double)
-> Text -> Double -> Map Text Double -> Map Text Double
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith Double -> Double -> Double
forall a. Num a => a -> a -> a
(+) Text
g Double
1 Map Text Double
acc) Map Text Double
forall k a. Map k a
M.empty [Text]
groups
        seen :: [Text]
seen = [Text] -> [Text]
forall a. Eq a => [a] -> [a]
L.nub [Text]
groups
     in [(Text
g, Double -> Text -> Map Text Double -> Double
forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault Double
0 Text
g Map Text Double
m) | Text
g <- [Text]
seen]
aggregateByGroup Agg
agg Text
groupCol Maybe Text
mValueCol DataFrame
df = case Maybe Text
mValueCol of
    Maybe Text
Nothing ->
        String -> [(Text, Double)]
forall a. HasCallStack => String -> a
error (String -> [(Text, Double)]) -> String -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$
            String
"Aggregation "
                String -> ShowS
forall a. [a] -> [a] -> [a]
++ Agg -> String
forall a. Show a => a -> String
show Agg
agg
                String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" requires a value column; only Count works without one."
    Just Text
valueCol ->
        let groups :: [Text]
groups = HasCallStack => Text -> DataFrame -> [Text]
Text -> DataFrame -> [Text]
extractStringColumn Text
groupCol DataFrame
df
            values :: [Double]
values = HasCallStack => Text -> DataFrame -> [Double]
Text -> DataFrame -> [Double]
extractNumericColumn Text
valueCol DataFrame
df
            seen :: [Text]
seen = [Text] -> [Text]
forall a. Eq a => [a] -> [a]
L.nub [Text]
groups
            pairs :: [(Text, Double)]
pairs = [Text] -> [Double] -> [(Text, Double)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Text]
groups [Double]
values
            byGroup :: Map Text [Double]
byGroup =
                (Map Text [Double] -> (Text, Double) -> Map Text [Double])
-> Map Text [Double] -> [(Text, Double)] -> Map Text [Double]
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
L.foldl'
                    (\Map Text [Double]
acc (Text
g, Double
v) -> ([Double] -> [Double] -> [Double])
-> Text -> [Double] -> Map Text [Double] -> Map Text [Double]
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith (([Double] -> [Double] -> [Double])
-> [Double] -> [Double] -> [Double]
forall a b c. (a -> b -> c) -> b -> a -> c
flip [Double] -> [Double] -> [Double]
forall a. [a] -> [a] -> [a]
(++)) Text
g [Double
v] Map Text [Double]
acc)
                    Map Text [Double]
forall k a. Map k a
M.empty
                    [(Text, Double)]
pairs
            reduce :: [Double] -> Double
reduce = Agg -> [Double] -> Double
numericReducer Agg
agg
         in [(Text
g, [Double] -> Double
reduce ([Double] -> Text -> Map Text [Double] -> [Double]
forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault [] Text
g Map Text [Double]
byGroup)) | Text
g <- [Text]
seen]

{- | The numeric reducers, broken out so the overlapping-Count case in
  'aggregateByGroup' can be dispatched at the head pattern without
  producing a redundant inner match.
-}
numericReducer :: Agg -> [Double] -> Double
numericReducer :: Agg -> [Double] -> Double
numericReducer Agg
Sum = [Double] -> Double
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum
numericReducer Agg
Mean = \[Double]
vs -> if [Double] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Double]
vs then Double
0 else [Double] -> Double
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum [Double]
vs Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral ([Double] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Double]
vs)
numericReducer Agg
Median = [Double] -> Double
medianD
numericReducer Agg
Min = [Double] -> Double
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum
numericReducer Agg
Max = [Double] -> Double
forall a. Ord a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum
numericReducer Agg
Count = Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Double) -> ([Double] -> Int) -> [Double] -> Double
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Double] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length

medianD :: [Double] -> Double
medianD :: [Double] -> Double
medianD [] = Double
0
medianD [Double]
xs =
    let sorted :: [Double]
sorted = [Double] -> [Double]
forall a. Ord a => [a] -> [a]
L.sort [Double]
xs
        n :: Int
n = [Double] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Double]
sorted
        mid :: Int
mid = Int
n Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2
     in if Int -> Bool
forall a. Integral a => a -> Bool
even Int
n
            then ([Double]
sorted [Double] -> Int -> Double
forall a. HasCallStack => [a] -> Int -> a
!! (Int
mid Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Double -> Double -> Double
forall a. Num a => a -> a -> a
+ [Double]
sorted [Double] -> Int -> Double
forall a. HasCallStack => [a] -> Int -> a
!! Int
mid) Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
2
            else [Double]
sorted [Double] -> Int -> Double
forall a. HasCallStack => [a] -> Int -> a
!! Int
mid

isNumericColumn :: DataFrame -> T.Text -> Bool
isNumericColumn :: DataFrame -> Text -> Bool
isNumericColumn DataFrame
df Text
colName = Bool -> (Column -> Bool) -> Maybe Column -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False Column -> Bool
isNumeric (Text -> DataFrame -> Maybe Column
getColumn Text
colName DataFrame
df)

isNumericColumnCheck :: T.Text -> DataFrame -> Bool
isNumericColumnCheck :: Text -> DataFrame -> Bool
isNumericColumnCheck Text
colName DataFrame
df = DataFrame -> Text -> Bool
isNumericColumn DataFrame
df Text
colName

extractStringColumn :: (HasCallStack) => T.Text -> DataFrame -> [T.Text]
extractStringColumn :: HasCallStack => Text -> DataFrame -> [Text]
extractStringColumn Text
colName DataFrame
df =
    case Text -> Map Text Int -> Maybe Int
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Text
colName (DataFrame -> Map Text Int
columnIndices DataFrame
df) of
        Maybe Int
Nothing -> String -> [Text]
forall a. HasCallStack => String -> a
error (String -> [Text]) -> String -> [Text]
forall a b. (a -> b) -> a -> b
$ String
"Column " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack Text
colName String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" not found"
        Just Int
idx -> HasCallStack => Column -> [Text]
Column -> [Text]
columnToStrings (DataFrame -> Vector Column
columns DataFrame
df Vector Column -> Int -> Column
forall a. Vector a -> Int -> a
V.! Int
idx)

extractNumericColumn :: (HasCallStack) => T.Text -> DataFrame -> [Double]
extractNumericColumn :: HasCallStack => Text -> DataFrame -> [Double]
extractNumericColumn Text
colName DataFrame
df =
    case Text -> Map Text Int -> Maybe Int
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Text
colName (DataFrame -> Map Text Int
columnIndices DataFrame
df) of
        Maybe Int
Nothing -> String -> [Double]
forall a. HasCallStack => String -> a
error (String -> [Double]) -> String -> [Double]
forall a b. (a -> b) -> a -> b
$ String
"Column " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack Text
colName String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" not found"
        Just Int
idx -> HasCallStack => Column -> [Double]
Column -> [Double]
columnToDoubles (DataFrame -> Vector Column
columns DataFrame
df Vector Column -> Int -> Column
forall a. Vector a -> Int -> a
V.! Int
idx)

-- | Render a column's values as strings (identity for @Text@, @show@ otherwise).
columnToStrings :: (HasCallStack) => Column -> [T.Text]
columnToStrings :: HasCallStack => Column -> [Text]
columnToStrings Column
col = case Column
col of
    BoxedColumn Maybe Bitmap
_ (Vector a
vec :: V.Vector a) ->
        case TypeRep a -> TypeRep Text -> Maybe (a :~: Text)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @T.Text) of
            Just a :~: Text
Refl -> Vector Text -> [Text]
forall a. Vector a -> [a]
V.toList Vector a
Vector Text
vec
            Maybe (a :~: Text)
Nothing -> Vector Text -> [Text]
forall a. Vector a -> [a]
V.toList (Vector Text -> [Text]) -> Vector Text -> [Text]
forall a b. (a -> b) -> a -> b
$ (a -> Text) -> Vector a -> Vector Text
forall a b. (a -> b) -> Vector a -> Vector b
V.map (String -> Text
T.pack (String -> Text) -> (a -> String) -> a -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show) Vector a
vec
    UnboxedColumn Maybe Bitmap
_ Vector a
vec ->
        Vector Text -> [Text]
forall a. Vector a -> [a]
V.toList (Vector Text -> [Text]) -> Vector Text -> [Text]
forall a b. (a -> b) -> a -> b
$ (a -> Text) -> Vector a -> Vector Text
forall (v :: * -> *) a b.
(Vector v a, Vector v b) =>
(a -> b) -> v a -> v b
VG.map (String -> Text
T.pack (String -> Text) -> (a -> String) -> a -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show) (Vector a -> Vector a
forall (v :: * -> *) a (w :: * -> *).
(Vector v a, Vector w a) =>
v a -> w a
VG.convert Vector a
vec)
    PackedText Maybe Bitmap
_ PackedTextData
_ -> HasCallStack => Column -> [Text]
Column -> [Text]
columnToStrings (Column -> Column
materializePacked Column
col)
    MergedColumn Column
_ Column
_ -> HasCallStack => Column -> [Text]
Column -> [Text]
columnToStrings (Column -> Column
materializeMerged Column
col)

-- | Coerce a numeric column to @[Double]@; errors if the element type is not numeric.
columnToDoubles :: (HasCallStack) => Column -> [Double]
columnToDoubles :: HasCallStack => Column -> [Double]
columnToDoubles Column
col = case Column
col of
    BoxedColumn Maybe Bitmap
_ Vector a
vec -> Vector a -> [Double]
forall a. (Columnable a, Show a) => Vector a -> [Double]
vectorToDoubles Vector a
vec
    UnboxedColumn Maybe Bitmap
_ Vector a
vec -> Vector a -> [Double]
forall a. (Columnable a, Unbox a, Show a) => Vector a -> [Double]
unboxedVectorToDoubles Vector a
vec
    PackedText Maybe Bitmap
_ PackedTextData
_ -> HasCallStack => Column -> [Double]
Column -> [Double]
columnToDoubles (Column -> Column
materializePacked Column
col)
    MergedColumn Column
_ Column
_ -> HasCallStack => Column -> [Double]
Column -> [Double]
columnToDoubles (Column -> Column
materializeMerged Column
col)

vectorToDoubles :: forall a. (Columnable a, Show a) => V.Vector a -> [Double]
vectorToDoubles :: forall a. (Columnable a, Show a) => Vector a -> [Double]
vectorToDoubles Vector a
vec =
    case TypeRep a -> TypeRep Double -> Maybe (a :~: Double)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Double) of
        Just a :~: Double
Refl -> Vector Double -> [Double]
forall a. Vector a -> [a]
V.toList Vector a
Vector Double
vec
        Maybe (a :~: Double)
Nothing -> case forall a. SBoolI (IntegralTypes a) => SBool (IntegralTypes a)
sIntegral @a of
            SBool (IntegralTypes a)
STrue -> Vector Double -> [Double]
forall a. Vector a -> [a]
V.toList (Vector Double -> [Double]) -> Vector Double -> [Double]
forall a b. (a -> b) -> a -> b
$ (a -> Double) -> Vector a -> Vector Double
forall a b. (a -> b) -> Vector a -> Vector b
V.map a -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Vector a
vec
            SBool (IntegralTypes a)
SFalse -> case forall a. SBoolI (FloatingTypes a) => SBool (FloatingTypes a)
sFloating @a of
                SBool (FloatingTypes a)
STrue -> Vector Double -> [Double]
forall a. Vector a -> [a]
V.toList (Vector Double -> [Double]) -> Vector Double -> [Double]
forall a b. (a -> b) -> a -> b
$ (a -> Double) -> Vector a -> Vector Double
forall a b. (a -> b) -> Vector a -> Vector b
V.map a -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac Vector a
vec
                SBool (FloatingTypes a)
SFalse ->
                    String -> [Double]
forall a. HasCallStack => String -> a
error (String -> [Double]) -> String -> [Double]
forall a b. (a -> b) -> a -> b
$ String
"Column is not numeric (type: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ TypeRep a -> String
forall a. Show a => a -> String
show (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"

unboxedVectorToDoubles ::
    forall a. (Columnable a, VU.Unbox a, Show a) => VU.Vector a -> [Double]
unboxedVectorToDoubles :: forall a. (Columnable a, Unbox a, Show a) => Vector a -> [Double]
unboxedVectorToDoubles Vector a
vec =
    case TypeRep a -> TypeRep Double -> Maybe (a :~: Double)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Double) of
        Just a :~: Double
Refl -> Vector Double -> [Double]
forall a. Unbox a => Vector a -> [a]
VU.toList Vector a
Vector Double
vec
        Maybe (a :~: Double)
Nothing -> case forall a. SBoolI (IntegralTypes a) => SBool (IntegralTypes a)
sIntegral @a of
            SBool (IntegralTypes a)
STrue -> Vector Double -> [Double]
forall a. Unbox a => Vector a -> [a]
VU.toList (Vector Double -> [Double]) -> Vector Double -> [Double]
forall a b. (a -> b) -> a -> b
$ (a -> Double) -> Vector a -> Vector Double
forall a b. (Unbox a, Unbox b) => (a -> b) -> Vector a -> Vector b
VU.map a -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Vector a
vec
            SBool (IntegralTypes a)
SFalse -> case forall a. SBoolI (FloatingTypes a) => SBool (FloatingTypes a)
sFloating @a of
                SBool (FloatingTypes a)
STrue -> Vector Double -> [Double]
forall a. Unbox a => Vector a -> [a]
VU.toList (Vector Double -> [Double]) -> Vector Double -> [Double]
forall a b. (a -> b) -> a -> b
$ (a -> Double) -> Vector a -> Vector Double
forall a b. (Unbox a, Unbox b) => (a -> b) -> Vector a -> Vector b
VU.map a -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac Vector a
vec
                SBool (FloatingTypes a)
SFalse ->
                    String -> [Double]
forall a. HasCallStack => String -> a
error (String -> [Double]) -> String -> [Double]
forall a b. (a -> b) -> a -> b
$ String
"Column is not numeric (type: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ TypeRep a -> String
forall a. Show a => a -> String
show (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"

getCategoricalCounts ::
    (HasCallStack) => T.Text -> DataFrame -> Maybe [(T.Text, Double)]
getCategoricalCounts :: HasCallStack => Text -> DataFrame -> Maybe [(Text, Double)]
getCategoricalCounts Text
colName DataFrame
df =
    case Text -> Map Text Int -> Maybe Int
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Text
colName (DataFrame -> Map Text Int
columnIndices DataFrame
df) of
        Maybe Int
Nothing -> String -> Maybe [(Text, Double)]
forall a. HasCallStack => String -> a
error (String -> Maybe [(Text, Double)])
-> String -> Maybe [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ String
"Column " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
T.unpack Text
colName String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" not found"
        Just Int
idx ->
            let col :: Column
col = DataFrame -> Vector Column
columns DataFrame
df Vector Column -> Int -> Column
forall a. Vector a -> Int -> a
V.! Int
idx
             in case Column
col of
                    BoxedColumn Maybe Bitmap
_ (Vector a
vec :: V.Vector a) ->
                        [(Text, Double)] -> Maybe [(Text, Double)]
forall a. a -> Maybe a
Just (TypeRep a -> Vector a -> [(Text, Double)]
forall a. Show a => TypeRep a -> Vector a -> [(Text, Double)]
countBoxed (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) Vector a
vec)
                    UnboxedColumn Maybe Bitmap
_ (Vector a
vec :: VU.Vector a) ->
                        [(Text, Double)] -> Maybe [(Text, Double)]
forall a. a -> Maybe a
Just (TypeRep a -> Vector a -> [(Text, Double)]
forall a.
(Show a, Unbox a) =>
TypeRep a -> Vector a -> [(Text, Double)]
countUnboxed (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) Vector a
vec)
                    PackedText Maybe Bitmap
_ PackedTextData
_ -> case Column -> Column
materializePacked Column
col of
                        BoxedColumn Maybe Bitmap
_ (Vector a
vec :: V.Vector a) -> [(Text, Double)] -> Maybe [(Text, Double)]
forall a. a -> Maybe a
Just (TypeRep a -> Vector a -> [(Text, Double)]
forall a. Show a => TypeRep a -> Vector a -> [(Text, Double)]
countBoxed (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) Vector a
vec)
                        Column
_ -> Maybe [(Text, Double)]
forall a. Maybe a
Nothing
                    MergedColumn Column
_ Column
_ -> case Column -> Column
materializeMerged Column
col of
                        BoxedColumn Maybe Bitmap
_ (Vector a
vec :: V.Vector a) -> [(Text, Double)] -> Maybe [(Text, Double)]
forall a. a -> Maybe a
Just (TypeRep a -> Vector a -> [(Text, Double)]
forall a. Show a => TypeRep a -> Vector a -> [(Text, Double)]
countBoxed (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @a) Vector a
vec)
                        Column
_ -> Maybe [(Text, Double)]
forall a. Maybe a
Nothing
  where
    countBoxed ::
        forall a. (Show a) => TypeRep a -> V.Vector a -> [(T.Text, Double)]
    countBoxed :: forall a. Show a => TypeRep a -> Vector a -> [(Text, Double)]
countBoxed TypeRep a
tr Vector a
vec
        | Just a :~: Text
Refl <- TypeRep a -> TypeRep Text -> Maybe (a :~: Text)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @T.Text) = [(Text, Int)] -> [(Text, Double)]
toPairsText ([(Text, Int)] -> [(Text, Double)])
-> [(Text, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector Text -> [(Text, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
Vector Text
vec
        | Just a :~: String
Refl <- TypeRep a -> TypeRep String -> Maybe (a :~: String)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @String) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Just a :~: Integer
Refl <- TypeRep a -> TypeRep Integer -> Maybe (a :~: Integer)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Integer) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Just a :~: Int
Refl <- TypeRep a -> TypeRep Int -> Maybe (a :~: Int)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Int) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Just a :~: Double
Refl <- TypeRep a -> TypeRep Double -> Maybe (a :~: Double)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Double) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Just a :~: Float
Refl <- TypeRep a -> TypeRep Float -> Maybe (a :~: Float)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Float) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Just a :~: Bool
Refl <- TypeRep a -> TypeRep Bool -> Maybe (a :~: Bool)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Bool) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Just a :~: Char
Refl <- TypeRep a -> TypeRep Char -> Maybe (a :~: Char)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Char) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec
        | Bool
otherwise = [a] -> [(Text, Double)]
forall a. Show a => [a] -> [(Text, Double)]
countByShow ([a] -> [(Text, Double)]) -> [a] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [a]
forall a. Vector a -> [a]
V.toList Vector a
vec

    countUnboxed ::
        forall a. (Show a, VU.Unbox a) => TypeRep a -> VU.Vector a -> [(T.Text, Double)]
    countUnboxed :: forall a.
(Show a, Unbox a) =>
TypeRep a -> Vector a -> [(Text, Double)]
countUnboxed TypeRep a
tr Vector a
vec
        | Just a :~: Int
Refl <- TypeRep a -> TypeRep Int -> Maybe (a :~: Int)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Int) = [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec
        | Just a :~: Double
Refl <- TypeRep a -> TypeRep Double -> Maybe (a :~: Double)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Double) =
            [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec
        | Just a :~: Float
Refl <- TypeRep a -> TypeRep Float -> Maybe (a :~: Float)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Float) =
            [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec
        | Just a :~: Bool
Refl <- TypeRep a -> TypeRep Bool -> Maybe (a :~: Bool)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Bool) =
            [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec
        | Just a :~: Char
Refl <- TypeRep a -> TypeRep Char -> Maybe (a :~: Char)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Char) =
            [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec
        | Just a :~: Word8
Refl <- TypeRep a -> TypeRep Word8 -> Maybe (a :~: Word8)
forall a b. TypeRep a -> TypeRep b -> Maybe (a :~: b)
forall {k} (f :: k -> *) (a :: k) (b :: k).
TestEquality f =>
f a -> f b -> Maybe (a :~: b)
testEquality TypeRep a
tr (forall a. Typeable a => TypeRep a
forall {k} (a :: k). Typeable a => TypeRep a
typeRep @Word8) =
            [(a, Int)] -> [(Text, Double)]
forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs ([(a, Int)] -> [(Text, Double)]) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [(a, Int)]
forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec
        | Bool
otherwise = [a] -> [(Text, Double)]
forall a. Show a => [a] -> [(Text, Double)]
countByShow ([a] -> [(Text, Double)]) -> [a] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$ Vector a -> [a]
forall a. Unbox a => Vector a -> [a]
VU.toList Vector a
vec

    toPairs :: (Show a) => [(a, Int)] -> [(T.Text, Double)]
    toPairs :: forall a. Show a => [(a, Int)] -> [(Text, Double)]
toPairs = ((a, Int) -> (Text, Double)) -> [(a, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> [a] -> [b]
map (\(a
k, Int
v) -> (String -> Text
T.pack (a -> String
forall a. Show a => a -> String
show a
k), Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
v))

    toPairsText :: [(T.Text, Int)] -> [(T.Text, Double)]
    toPairsText :: [(Text, Int)] -> [(Text, Double)]
toPairsText = ((Text, Int) -> (Text, Double))
-> [(Text, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> [a] -> [b]
map ((Int -> Double) -> (Text, Int) -> (Text, Double)
forall b c a. (b -> c) -> (a, b) -> (a, c)
forall (p :: * -> * -> *) b c a.
Bifunctor p =>
(b -> c) -> p a b -> p a c
Data.Bifunctor.second Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral)

    countValues :: (Ord a) => V.Vector a -> [(a, Int)]
    countValues :: forall a. Ord a => Vector a -> [(a, Int)]
countValues Vector a
vec = Map a Int -> [(a, Int)]
forall k a. Map k a -> [(k, a)]
M.toList (Map a Int -> [(a, Int)]) -> Map a Int -> [(a, Int)]
forall a b. (a -> b) -> a -> b
$ (a -> Map a Int -> Map a Int) -> Map a Int -> Vector a -> Map a Int
forall a b. (a -> b -> b) -> b -> Vector a -> b
V.foldr' (\a
x Map a Int
acc -> (Int -> Int -> Int) -> a -> Int -> Map a Int -> Map a Int
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) a
x Int
1 Map a Int
acc) Map a Int
forall k a. Map k a
M.empty Vector a
vec

    countValuesUnboxed :: (Ord a, VU.Unbox a) => VU.Vector a -> [(a, Int)]
    countValuesUnboxed :: forall a. (Ord a, Unbox a) => Vector a -> [(a, Int)]
countValuesUnboxed Vector a
vec = Map a Int -> [(a, Int)]
forall k a. Map k a -> [(k, a)]
M.toList (Map a Int -> [(a, Int)]) -> Map a Int -> [(a, Int)]
forall a b. (a -> b) -> a -> b
$ (a -> Map a Int -> Map a Int) -> Map a Int -> Vector a -> Map a Int
forall a b. Unbox a => (a -> b -> b) -> b -> Vector a -> b
VU.foldr' (\a
x Map a Int
acc -> (Int -> Int -> Int) -> a -> Int -> Map a Int -> Map a Int
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) a
x Int
1 Map a Int
acc) Map a Int
forall k a. Map k a
M.empty Vector a
vec

    countByShow :: (Show a) => [a] -> [(T.Text, Double)]
    countByShow :: forall a. Show a => [a] -> [(Text, Double)]
countByShow [a]
xs =
        ((String, Int) -> (Text, Double))
-> [(String, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> [a] -> [b]
map ((String -> Text)
-> (Int -> Double) -> (String, Int) -> (Text, Double)
forall a b c d. (a -> b) -> (c -> d) -> (a, c) -> (b, d)
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
Data.Bifunctor.bimap String -> Text
T.pack Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral) ([(String, Int)] -> [(Text, Double)])
-> [(String, Int)] -> [(Text, Double)]
forall a b. (a -> b) -> a -> b
$
            Map String Int -> [(String, Int)]
forall k a. Map k a -> [(k, a)]
M.toList (Map String Int -> [(String, Int)])
-> Map String Int -> [(String, Int)]
forall a b. (a -> b) -> a -> b
$
                (Map String Int -> a -> Map String Int)
-> Map String Int -> [a] -> Map String Int
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
L.foldl' (\Map String Int
acc a
x -> (Int -> Int -> Int)
-> String -> Int -> Map String Int -> Map String Int
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) (a -> String
forall a. Show a => a -> String
show a
x) (Int
1 :: Int) Map String Int
acc) Map String Int
forall k a. Map k a
M.empty [a]
xs

{- | Keep the top-N entries by value; fold the rest into a single "Other"
bucket. Used to limit bar/pie counts to a readable number.
-}
groupWithOther :: Int -> [(T.Text, Double)] -> [(T.Text, Double)]
groupWithOther :: Int -> [(Text, Double)] -> [(Text, Double)]
groupWithOther Int
n [(Text, Double)]
items =
    let sorted :: [(Text, Double)]
sorted = ((Text, Double) -> Double) -> [(Text, Double)] -> [(Text, Double)]
forall b a. Ord b => (a -> b) -> [a] -> [a]
L.sortOn (Double -> Double
forall a. Num a => a -> a
negate (Double -> Double)
-> ((Text, Double) -> Double) -> (Text, Double) -> Double
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, Double) -> Double
forall a b. (a, b) -> b
snd) [(Text, Double)]
items
        ([(Text, Double)]
topN, [(Text, Double)]
rest) = Int -> [(Text, Double)] -> ([(Text, Double)], [(Text, Double)])
forall a. Int -> [a] -> ([a], [a])
splitAt Int
n [(Text, Double)]
sorted
        otherSum :: Double
otherSum = [Double] -> Double
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum (((Text, Double) -> Double) -> [(Text, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Text, Double) -> Double
forall a b. (a, b) -> b
snd [(Text, Double)]
rest)
     in if [(Text, Double)] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [(Text, Double)]
rest Bool -> Bool -> Bool
|| Double
otherSum Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
            then [(Text, Double)]
topN
            else [(Text, Double)]
topN [(Text, Double)] -> [(Text, Double)] -> [(Text, Double)]
forall a. [a] -> [a] -> [a]
++ [(Text
"Other (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show ([(Text, Double)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(Text, Double)]
rest)) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" items)", Double
otherSum)]

-- | Like 'groupWithOther' but annotates the "Other" bucket with its share %.
groupWithOtherForPie :: Int -> [(T.Text, Double)] -> [(T.Text, Double)]
groupWithOtherForPie :: Int -> [(Text, Double)] -> [(Text, Double)]
groupWithOtherForPie Int
n [(Text, Double)]
items =
    let total :: Double
total = [Double] -> Double
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum (((Text, Double) -> Double) -> [(Text, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Text, Double) -> Double
forall a b. (a, b) -> b
snd [(Text, Double)]
items)
        sorted :: [(Text, Double)]
sorted = ((Text, Double) -> Double) -> [(Text, Double)] -> [(Text, Double)]
forall b a. Ord b => (a -> b) -> [a] -> [a]
L.sortOn (Double -> Double
forall a. Num a => a -> a
negate (Double -> Double)
-> ((Text, Double) -> Double) -> (Text, Double) -> Double
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text, Double) -> Double
forall a b. (a, b) -> b
snd) [(Text, Double)]
items
        ([(Text, Double)]
topN, [(Text, Double)]
rest) = Int -> [(Text, Double)] -> ([(Text, Double)], [(Text, Double)])
forall a. Int -> [a] -> ([a], [a])
splitAt Int
n [(Text, Double)]
sorted
        otherSum :: Double
otherSum = [Double] -> Double
forall a. Num a => [a] -> a
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum (((Text, Double) -> Double) -> [(Text, Double)] -> [Double]
forall a b. (a -> b) -> [a] -> [b]
map (Text, Double) -> Double
forall a b. (a, b) -> b
snd [(Text, Double)]
rest)
        otherPct :: Int
otherPct = if Double
total Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0 then Int
0 else Double -> Int
forall b. Integral b => Double -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (Double
100 Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
otherSum Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
total) :: Int
     in if [(Text, Double)] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [(Text, Double)]
rest Bool -> Bool -> Bool
|| Double
otherSum Double -> Double -> Bool
forall a. Eq a => a -> a -> Bool
== Double
0
            then [(Text, Double)]
topN
            else
                [(Text, Double)]
topN
                    [(Text, Double)] -> [(Text, Double)] -> [(Text, Double)]
forall a. [a] -> [a] -> [a]
++ [
                           ( Text
"Other ("
                                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show ([(Text, Double)] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [(Text, Double)]
rest))
                                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" items, "
                                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show Int
otherPct)
                                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"%)"
                           , Double
otherSum
                           )
                       ]