packages feed

dataframe 0.3.3.3 → 0.3.3.4

raw patch · 21 files changed

+437/−205 lines, 21 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- DataFrame: [hasHeader] :: ReadOptions -> Bool
- DataFrame: [inferTypes] :: ReadOptions -> Bool
- DataFrame.Functions: [rankingFunction] :: BeamConfig -> Vector Double -> Vector Double -> Maybe Double
- DataFrame.IO.CSV: [hasHeader] :: ReadOptions -> Bool
- DataFrame.IO.CSV: [inferTypes] :: ReadOptions -> Bool
+ DataFrame: InferFromSample :: Int -> TypeSpec
+ DataFrame: NoHeader :: HeaderSpec
+ DataFrame: NoInference :: TypeSpec
+ DataFrame: ProvideNames :: [Text] -> HeaderSpec
+ DataFrame: SpecifyTypes :: [SchemaType] -> TypeSpec
+ DataFrame: UseFirstRow :: HeaderSpec
+ DataFrame: [headerSpec] :: ReadOptions -> HeaderSpec
+ DataFrame: [typeSpec] :: ReadOptions -> TypeSpec
+ DataFrame: data HeaderSpec
+ DataFrame: data TypeSpec
+ DataFrame: schemaType :: Columnable a => SchemaType
+ DataFrame.Display: DisplayOptions :: Int -> DisplayOptions
+ DataFrame.Display: [displayRows] :: DisplayOptions -> Int
+ DataFrame.Display: data DisplayOptions
+ DataFrame.Display: defaultDisplayOptions :: DisplayOptions
+ DataFrame.Display: display :: DisplayOptions -> DataFrame -> IO ()
+ DataFrame.Functions: F1 :: LossFunction
+ DataFrame.Functions: MeanSquaredError :: LossFunction
+ DataFrame.Functions: MutualInformation :: LossFunction
+ DataFrame.Functions: PearsonCorrelation :: LossFunction
+ DataFrame.Functions: [lossFunction] :: BeamConfig -> LossFunction
+ DataFrame.Functions: data LossFunction
+ DataFrame.Functions: defaultBeamConfig :: BeamConfig
+ DataFrame.Functions: div :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a
+ DataFrame.Functions: f1FromBinary :: Vector Double -> Vector Double -> Maybe Double
+ DataFrame.Functions: f1FromCounts :: Int -> Int -> Int -> Maybe Double
+ DataFrame.Functions: fitClassifier :: Text -> Int -> Int -> DataFrame -> Either String (Expr Int)
+ DataFrame.Functions: getLossFunction :: LossFunction -> Vector Double -> Vector Double -> Maybe Double
+ DataFrame.Functions: mod :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a
+ DataFrame.Functions: toDouble :: (Columnable a, Real a) => Expr a -> Expr Double
+ DataFrame.Functions: variance :: (Columnable a, Real a, Unbox a) => Expr a -> Expr Double
+ DataFrame.IO.CSV: InferFromSample :: Int -> TypeSpec
+ DataFrame.IO.CSV: NoHeader :: HeaderSpec
+ DataFrame.IO.CSV: NoInference :: TypeSpec
+ DataFrame.IO.CSV: ProvideNames :: [Text] -> HeaderSpec
+ DataFrame.IO.CSV: SpecifyTypes :: [SchemaType] -> TypeSpec
+ DataFrame.IO.CSV: UseFirstRow :: HeaderSpec
+ DataFrame.IO.CSV: [headerSpec] :: ReadOptions -> HeaderSpec
+ DataFrame.IO.CSV: [typeSpec] :: ReadOptions -> TypeSpec
+ DataFrame.IO.CSV: data HeaderSpec
+ DataFrame.IO.CSV: data TypeSpec
+ DataFrame.IO.CSV: instance GHC.Classes.Eq DataFrame.IO.CSV.HeaderSpec
+ DataFrame.IO.CSV: instance GHC.Show.Show DataFrame.IO.CSV.HeaderSpec
+ DataFrame.IO.CSV: shouldInferFromSample :: TypeSpec -> Bool
+ DataFrame.IO.CSV: typeInferenceSampleSize :: TypeSpec -> Int
+ DataFrame.Internal.Expression: instance (Data.String.IsString a, DataFrame.Internal.Column.Columnable a) => Data.String.IsString (DataFrame.Internal.Expression.Expr a)
+ DataFrame.Operations.Typing: emptyToNothing :: Text -> Maybe Text
+ DataFrame.Operations.Typing: parseFromExamples :: Int -> String -> Bool -> Vector Text -> Column
+ DataFrame.Operations.Typing: parseTimeOpt :: String -> Text -> Maybe Day
+ DataFrame.Operations.Typing: unsafeParseTime :: String -> Text -> Day
- DataFrame: ReadOptions :: Bool -> Bool -> Bool -> Int -> String -> ReadOptions
+ DataFrame: ReadOptions :: HeaderSpec -> TypeSpec -> Bool -> Int -> String -> ReadOptions
- DataFrame.Functions: BeamConfig :: Int -> Int -> (Vector Double -> Vector Double -> Maybe Double) -> BeamConfig
+ DataFrame.Functions: BeamConfig :: Int -> Int -> LossFunction -> BeamConfig
- DataFrame.Functions: median :: Expr Double -> Expr Double
+ DataFrame.Functions: median :: (Columnable a, Real a, Unbox a) => Expr a -> Expr Double
- DataFrame.Functions: synthesizeFeatureExpr :: Text -> Int -> Int -> DataFrame -> Either String (Expr Double)
+ DataFrame.Functions: synthesizeFeatureExpr :: Text -> BeamConfig -> DataFrame -> Either String (Expr Double)
- DataFrame.IO.CSV: ReadOptions :: Bool -> Bool -> Bool -> Int -> String -> ReadOptions
+ DataFrame.IO.CSV: ReadOptions :: HeaderSpec -> TypeSpec -> Bool -> Int -> String -> ReadOptions
- DataFrame.Internal.Statistics: correlation' :: Vector Double -> Vector Double -> Maybe Double
+ DataFrame.Internal.Statistics: correlation' :: (Real a, Unbox a, Real b, Unbox b) => Vector a -> Vector b -> Maybe Double
- DataFrame.Operations.Typing: parseDefault :: Bool -> String -> Column -> Column
+ DataFrame.Operations.Typing: parseDefault :: Int -> Bool -> String -> Column -> Column
- DataFrame.Operations.Typing: parseDefaults :: Bool -> String -> DataFrame -> DataFrame
+ DataFrame.Operations.Typing: parseDefaults :: Int -> Bool -> String -> DataFrame -> DataFrame

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Revision history for dataframe +## 0.3.3.4+* Add linting CI step + fix existing lint errors.+* Show now only prints 10 row. To print more you should use the new `display` function that takes the number of rows as a parameter in its configuration.+* Add `toDouble`, `div`, and, `mod` functions.+* Define an `IsString` instance for columns so you can use string literals without `F.lit`.+* Include variance expression.+* Improved filter performance.+* Make beam search loss function configurable for synthesizing features.+ ## 0.3.3.3 * Split `toMatrix` into more specific `to<Type>Matrix` functions. 
app/Main.hs view
@@ -12,12 +12,13 @@ main = do     let n = 100_000_000     g <- newIOGenM =<< newStdGen-    let range = (-20.0 :: Double, 20.0 :: Double)+    let range = (0 :: Double, 1 :: Double)     startGeneration <- getCurrentTime     ns <- VU.replicateM n (uniformRM range g)     xs <- VU.replicateM n (uniformRM range g)     ys <- VU.replicateM n (uniformRM range g)     let df = D.fromUnnamedColumns (map D.fromUnboxedVector [ns, xs, ys])+    print df     endGeneration <- getCurrentTime     let generationTime = diffUTCTime endGeneration startGeneration     putStrLn $ "Data generation Time: " ++ show generationTime@@ -29,7 +30,7 @@     let calculationTime = diffUTCTime endCalculation startCalculation     putStrLn $ "Calculation Time: " ++ show calculationTime     startFilter <- getCurrentTime-    print $ D.filter "0" (>= (19.9 :: Double)) df D.|> D.take 10+    print $ D.filter "0" (> (0.971 :: Double)) df D.|> D.take 10     endFilter <- getCurrentTime     let filterTime = diffUTCTime endFilter startFilter     putStrLn $ "Filter Time: " ++ show filterTime
dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               dataframe-version:            0.3.3.3+version:            0.3.3.4  synopsis: A fast, safe, and intuitive DataFrame library. @@ -57,6 +57,7 @@                     DataFrame.Operations.Transformations,                     DataFrame.Operations.Typing,                     DataFrame.Operations.Aggregation,+                    DataFrame.Display,                     DataFrame.Display.Terminal.Plot,                     DataFrame.IO.CSV,                     DataFrame.IO.Parquet,
src/DataFrame.hs view
@@ -197,9 +197,15 @@     module Row,     module Expression, +    -- * Display operations+    module Display,+     -- * Core dataframe operations     module Core, +    -- * Types+    module Schema,+     -- * I/O     module CSV,     module Parquet,@@ -224,10 +230,17 @@ ) where +import DataFrame.Display as Display (+    DisplayOptions (..),+    defaultDisplayOptions,+    display,+ ) import DataFrame.Display.Terminal.Plot as Plot import DataFrame.Errors as Errors import DataFrame.IO.CSV as CSV (+    HeaderSpec (..),     ReadOptions (..),+    TypeSpec (..),     defaultReadOptions,     readCsv,     readCsvWithOpts,@@ -269,6 +282,9 @@     toAny,     toRowList,     toRowVector,+ )+import DataFrame.Internal.Schema as Schema (+    schemaType,  ) import DataFrame.Operations.Aggregation as Aggregation (     aggregate,
+ src/DataFrame/Display.hs view
@@ -0,0 +1,26 @@+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++{-# HLINT ignore "Use newtype instead of data" #-}+module DataFrame.Display where++import qualified Data.Text.IO as T+import qualified DataFrame.Internal.DataFrame as D+import qualified DataFrame.Operations.Subset as D++import Data.Function++data DisplayOptions = DisplayOptions+    { {-- | Maximum number of rows to render.+      --+      --   * If this value is less than or equal to 0, no rows are printed.+      --   * If it is greater than the number of rows in the frame, all rows are printed.+      --}+      displayRows :: Int+    }++defaultDisplayOptions :: DisplayOptions+defaultDisplayOptions = DisplayOptions 10++-- | Render a 'DataFrame' to stdout according to 'DisplayOptions'.+display :: DisplayOptions -> D.DataFrame -> IO ()+display opts df = df & D.take (displayRows opts) & (`D.asText` False) & T.putStrLn
src/DataFrame/Functions.hs view
@@ -72,6 +72,15 @@     (c -> b -> a) -> Expr c -> Expr b -> Expr a lift2 = BinaryOp "udf" +toDouble :: (Columnable a, Real a) => Expr a -> Expr Double+toDouble = UnaryOp "toDouble" realToFrac++div :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a+div = BinaryOp "div" Prelude.div++mod :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a+mod = BinaryOp "mod" Prelude.mod+ (==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool (==) = BinaryOp "eq" (Prelude.==) @@ -126,9 +135,12 @@ mean :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double mean expr = AggNumericVector expr "mean" mean' -median :: Expr Double -> Expr Double-median expr = AggNumericVector expr "mean" median'+variance :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double+variance expr = AggNumericVector expr "variance" variance' +median :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double+median expr = AggNumericVector expr "median" median'+ percentile :: Int -> Expr Double -> Expr Double percentile n expr =     AggNumericVector@@ -312,13 +324,59 @@ synthesizeFeatureExpr ::     -- | Target expression     T.Text ->+    BeamConfig ->+    DataFrame ->+    Either String (Expr Double)+synthesizeFeatureExpr target cfg df =+    let+        df' = exclude [target] df+        t = case interpret df (Col target) of+            Left e -> throw e+            Right v -> v+     in+        case beamSearch+            df'+            cfg+            t+            [] of+            Nothing -> Left "No programs found"+            Just p -> Right p++f1FromBinary :: VU.Vector Double -> VU.Vector Double -> Maybe Double+f1FromBinary trues preds =+    let (!tp, !fp, !fn) =+            VU.foldl' step (0 :: Int, 0 :: Int, 0 :: Int) $+                VU.zip (VU.map (Prelude.> 0) preds) (VU.map (Prelude.> 0) trues)+     in f1FromCounts tp fp fn+  where+    step (!tp, !fp, !fn) (!p, !t) =+        case (p, t) of+            (True, True) -> (tp + 1, fp, fn)+            (True, False) -> (tp, fp + 1, fn)+            (False, True) -> (tp, fp, fn + 1)+            (False, False) -> (tp, fp, fn)++f1FromCounts :: Int -> Int -> Int -> Maybe Double+f1FromCounts tp fp fn =+    let tp' = fromIntegral tp+        fp' = fromIntegral fp+        fn' = fromIntegral fn+        precision = if tp' + fp' Prelude.== 0 then 0 else tp' / (tp' + fp')+        recall = if tp' + fn' Prelude.== 0 then 0 else tp' / (tp' + fn')+     in if precision + recall Prelude.== 0+            then Nothing+            else Just (2 * precision * recall / (precision + recall))++fitClassifier ::+    -- | Target expression+    T.Text ->     -- | Depth of search (Roughly, how many terms in the final expression)     Int ->     -- | Beam size - the number of candidate expressions to consider at a time.     Int ->     DataFrame ->-    Either String (Expr Double)-synthesizeFeatureExpr target d b df =+    Either String (Expr Int)+fitClassifier target d b df =     let         df' = exclude [target] df         t = case interpret df (Col target) of@@ -327,11 +385,11 @@      in         case beamSearch             df'-            (BeamConfig d b (\l r -> (^ 2) <$> correlation' l r))+            (BeamConfig d b F1)             t             [] of             Nothing -> Left "No programs found"-            Just p -> Right p+            Just p -> Right (ifThenElse (p DataFrame.Functions.> 0) 1 0)  fitRegression ::     -- | Target expression@@ -355,12 +413,7 @@             ( BeamConfig                 d                 b-                ( \l r ->-                    mutualInformationBinned-                        (Prelude.max 10 (ceiling (sqrt (fromIntegral (VU.length l)))))-                        l-                        r-                )+                MutualInformation             )             t             [] of@@ -372,18 +425,41 @@                             ( D.derive "_generated_regression_feature_" p df                                 & select ["_generated_regression_feature_"]                             )-                            (BeamConfig d b (\l r -> fmap negate (meanSquaredError l r)))+                            (BeamConfig d b MeanSquaredError)                             t                             [Col "_generated_regression_feature_", lit targetMean, lit 10] of                             Nothing -> Left "Could not find coefficients"                             Just p' -> Right (replaceExpr p (Col @Double "_generated_regression_feature_") p') +data LossFunction+    = PearsonCorrelation+    | MutualInformation+    | MeanSquaredError+    | F1++getLossFunction ::+    LossFunction -> (VU.Vector Double -> VU.Vector Double -> Maybe Double)+getLossFunction f = case f of+    MutualInformation ->+        ( \l r ->+            mutualInformationBinned+                (Prelude.max 10 (ceiling (sqrt (fromIntegral (VU.length l)))))+                l+                r+        )+    PearsonCorrelation -> (\l r -> (^ 2) <$> correlation' l r)+    MeanSquaredError -> (\l r -> fmap negate (meanSquaredError l r))+    F1 -> f1FromBinary+ data BeamConfig = BeamConfig     { searchDepth :: Int     , beamLength :: Int-    , rankingFunction :: VU.Vector Double -> VU.Vector Double -> Maybe Double+    , lossFunction :: LossFunction     } +defaultBeamConfig :: BeamConfig+defaultBeamConfig = BeamConfig 2 100 PearsonCorrelation+ beamSearch ::     DataFrame ->     -- | Parameters of the beam search.@@ -432,7 +508,10 @@                                 then LT                                 else compare c1 c2                         )-                        (map (\(e, res) -> (e, rankingFunction cfg l (asDoubleVector res))) ps)+                        ( map+                            (\(e, res) -> (e, getLossFunction (lossFunction cfg) l (asDoubleVector res)))+                            ps+                        )                 )         asDoubleVector c =             let@@ -454,7 +533,9 @@         trace             ( "Best loss: "                 ++ show-                    (rankingFunction cfg l <$> (interpretDoubleVector <$> (listToMaybe ordered)))+                    ( getLossFunction (lossFunction cfg) l . interpretDoubleVector+                        <$> listToMaybe ordered+                    )                 ++ " "                 ++ (if null ordered then "empty" else show (listToMaybe ordered))             )
src/DataFrame/IO/CSV.hs view
@@ -15,6 +15,7 @@ import qualified Data.ByteString.Char8 as C8 import qualified Data.List as L import qualified Data.Map.Strict as M+import qualified Data.Proxy as P import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Text.IO as TIO@@ -24,7 +25,7 @@ import qualified Data.Vector.Unboxed.Mutable as VUM  import Control.Applicative (many, (<|>))-import Control.Monad (forM_, unless, zipWithM_)+import Control.Monad (forM_, unless, zipWithM, zipWithM_) import Data.Attoparsec.ByteString.Char8 hiding (endOfLine) import Data.Bits (shiftL) import Data.Char@@ -37,6 +38,7 @@ import DataFrame.Internal.Column (Column (..), columnLength) import DataFrame.Internal.DataFrame (DataFrame (..)) import DataFrame.Internal.Parsing+import DataFrame.Internal.Schema import DataFrame.Operations.Typing import System.IO import Type.Reflection@@ -59,12 +61,34 @@     | GrowingDouble !(GrowingUnboxedVector Double) !(IORef [Int])     | GrowingText !(GrowingVector T.Text) !(IORef [Int]) +data HeaderSpec+    = -- | File has no header row+      NoHeader+    | -- | Use first row as column names+      UseFirstRow+    | -- | Supply names for a no-header file+      ProvideNames [T.Text]+    deriving (Eq, Show)++data TypeSpec+    = InferFromSample Int+    | SpecifyTypes [SchemaType]+    | NoInference++shouldInferFromSample :: TypeSpec -> Bool+shouldInferFromSample (InferFromSample _) = True+shouldInferFromSample _ = False++typeInferenceSampleSize :: TypeSpec -> Int+typeInferenceSampleSize (InferFromSample n) = n+typeInferenceSampleSize _ = 0+ -- | CSV read parameters. data ReadOptions = ReadOptions-    { hasHeader :: Bool-    -- ^ Whether or not the CSV file has a header. (default: True)-    , inferTypes :: Bool-    -- ^ Whether to try and infer types. (default: True)+    { headerSpec :: HeaderSpec+    -- ^ Where to get the headers from. (default: UseFirstRow)+    , typeSpec :: TypeSpec+    -- ^ Whether/how to infer types. (default: InferFromSample 100)     , safeRead :: Bool     -- ^ Whether to partially parse values into `Maybe`/`Either`. (default: True)     , chunkSize :: Int@@ -86,8 +110,8 @@ defaultReadOptions :: ReadOptions defaultReadOptions =     ReadOptions-        { hasHeader = True-        , inferTypes = True+        { headerSpec = UseFirstRow+        , typeSpec = InferFromSample 100         , safeRead = True         , chunkSize = 512_000         , dateFormat = "%Y-%m-%d"@@ -175,7 +199,7 @@ @ -} readCsvWithOpts :: ReadOptions -> FilePath -> IO DataFrame-readCsvWithOpts opts = readSeparated ',' opts+readCsvWithOpts = readSeparated ','  {- | Read TSV (tab separated) file from path and load it into a dataframe. @@ -202,12 +226,12 @@      firstLine <- C8.hGetLine handle     let firstRow = parseLine sep firstLine-        columnNames =-            if hasHeader opts-                then map (T.filter (/= '\"') . TE.decodeUtf8Lenient) firstRow-                else map (T.singleton . intToDigit) [0 .. length firstRow - 1]+        columnNames = case headerSpec opts of+            NoHeader -> map (T.pack . show) [0 .. length firstRow - 1]+            UseFirstRow -> map (T.filter (/= '\"') . TE.decodeUtf8Lenient) firstRow+            ProvideNames ns -> ns -    unless (hasHeader opts) $ hSeek handle AbsoluteSeek 0+    unless (headerSpec opts == UseFirstRow) $ hSeek handle AbsoluteSeek 0      dataLine <- C8.hGetLine handle     let dataRow = parseLine sep dataLine@@ -227,23 +251,31 @@                 , dataframeDimensions = (numRows, V.length frozenCols)                 }     return $-        if inferTypes opts-            then parseDefaults (safeRead opts) (dateFormat opts) df+        if shouldInferFromSample (typeSpec opts)+            then+                parseDefaults+                    (typeInferenceSampleSize (typeSpec opts))+                    (safeRead opts)+                    (dateFormat opts)+                    df             else df  initializeColumns :: [BS.ByteString] -> ReadOptions -> IO [GrowingColumn]-initializeColumns row opts = mapM initColumn row+initializeColumns row opts = case typeSpec opts of+    NoInference -> zipWithM initColumn row (expandTypes [])+    InferFromSample _ -> zipWithM initColumn row (expandTypes [])+    SpecifyTypes ts -> zipWithM initColumn row (expandTypes ts)   where-    initColumn :: BS.ByteString -> IO GrowingColumn-    initColumn bs = do+    expandTypes xs = xs ++ replicate (length row - length xs) (schemaType @T.Text)+    initColumn :: BS.ByteString -> SchemaType -> IO GrowingColumn+    initColumn bs t = do         nullsRef <- newIORef []-        let val = TE.decodeUtf8Lenient bs-        if inferTypes opts-            then case inferType val of-                IntType -> GrowingInt <$> newGrowingUnboxedVector 1_024 <*> pure nullsRef-                DoubleType -> GrowingDouble <$> newGrowingUnboxedVector 1_024 <*> pure nullsRef-                TextType -> GrowingText <$> newGrowingVector 1_024 <*> pure nullsRef-            else GrowingText <$> newGrowingVector 1_024 <*> pure nullsRef+        case t of+            SType (_ :: P.Proxy a) -> case testEquality (typeRep @a) (typeRep @Int) of+                Just Refl -> GrowingInt <$> newGrowingUnboxedVector 1_024 <*> pure nullsRef+                Nothing -> case testEquality (typeRep @a) (typeRep @Double) of+                    Just Refl -> GrowingDouble <$> newGrowingUnboxedVector 1_024 <*> pure nullsRef+                    Nothing -> GrowingText <$> newGrowingVector 1_024 <*> pure nullsRef  data InferredType = IntType | DoubleType | TextType 
src/DataFrame/IO/Parquet/Levels.hs view
@@ -54,7 +54,7 @@      in (map fromIntegral defLvlsU32, map fromIntegral repLvlsU32, afterDefBytes)  stitchNullable :: Int -> [Int] -> [a] -> [Maybe a]-stitchNullable maxDef defLvls vals = go defLvls vals+stitchNullable maxDef = go   where     go [] _ = []     go (d : ds) vs@@ -95,7 +95,7 @@ parseAll xs = let (n, xs') = parseOne xs in n : parseAll xs'  levelsForPath :: [SchemaElement] -> [String] -> (Int, Int)-levelsForPath schemaTail path = go 0 0 (parseAll schemaTail) path+levelsForPath schemaTail = go 0 0 (parseAll schemaTail)   where     go defC repC _ [] = (defC, repC)     go defC repC nodes (p : ps) =
src/DataFrame/IO/Parquet/Thrift.hs view
@@ -256,9 +256,7 @@     let lastFieldId = 0     let fieldStack = []     bufferPos <- newIORef (0 :: Int)-    metadata <--        readFileMetaData defaultMetadata metadataBytes bufferPos lastFieldId fieldStack-    return metadata+    readFileMetaData defaultMetadata metadataBytes bufferPos lastFieldId fieldStack  readFileMetaData ::     FileMetadata ->
src/DataFrame/Internal/Column.hs view
@@ -1064,11 +1064,11 @@             Nothing -> case sFloating @a of                 STrue ->                     Right-                        (VB.convert $ VB.map (fromMaybe (read @Double "NaN") . (fmap realToFrac)) f)+                        (VB.convert $ VB.map (maybe (read @Double "NaN") realToFrac) f)                 SFalse -> case sIntegral @a of                     STrue ->                         Right-                            (VB.convert $ VB.map (fromMaybe (read @Double "NaN") . (fmap fromIntegral)) f)+                            (VB.convert $ VB.map (maybe (read @Double "NaN") fromIntegral) f)                     SFalse ->                         Left $                             TypeMismatchException@@ -1139,11 +1139,11 @@             Nothing -> case sFloating @a of                 STrue ->                     Right-                        (VB.convert $ VB.map (fromMaybe (read @Float "NaN") . (fmap realToFrac)) f)+                        (VB.convert $ VB.map (maybe (read @Float "NaN") realToFrac) f)                 SFalse -> case sIntegral @a of                     STrue ->                         Right-                            (VB.convert $ VB.map (fromMaybe (read @Float "NaN") . (fmap fromIntegral)) f)+                            (VB.convert $ VB.map (maybe (read @Float "NaN") fromIntegral) f)                     SFalse ->                         Left $                             TypeMismatchException
src/DataFrame/Internal/DataFrame.hs view
@@ -68,7 +68,18 @@  instance Show DataFrame where     show :: DataFrame -> String-    show d = T.unpack (asText d False)+    show d =+        let+            d' =+                d+                    { columns = V.map (takeColumn 10) (columns d)+                    , dataframeDimensions = (10, snd (dataframeDimensions d))+                    }+         in+            T.unpack (asText d' False)+                ++ "\n"+                ++ "Showing 10 rows out of "+                ++ show (fst (dataframeDimensions d))  -- | For showing the dataframe as markdown in notebooks. toMarkdownTable :: DataFrame -> T.Text@@ -76,7 +87,7 @@  asText :: DataFrame -> Bool -> T.Text asText d properMarkdown =-    let header = "index" : map fst (sortBy (compare `on` snd) $ M.toList (columnIndices d))+    let header = map fst (sortBy (compare `on` snd) $ M.toList (columnIndices d))         types = V.toList $ V.filter (/= "") $ V.map getType (columns d)         getType :: Column -> T.Text         getType (BoxedColumn (column :: V.Vector a)) = T.pack $ show (typeRep @a)@@ -93,14 +104,11 @@         get (Just (UnboxedColumn column)) = V.map (T.pack . show) (V.convert column)         get (Just (OptionalColumn column)) = V.map (T.pack . show) column         get Nothing = V.empty-        getTextColumnFromFrame df (i, name) =-            if i == 0-                then V.fromList (map (T.pack . show) [0 .. (fst (dataframeDimensions df) - 1)])-                else get $ (V.!?) (columns d) ((M.!) (columnIndices d) name)+        getTextColumnFromFrame df (i, name) = get $ (V.!?) (columns d) ((M.!) (columnIndices d) name)         rows =             transpose $                 zipWith (curry (V.toList . getTextColumnFromFrame d)) [0 ..] header-     in showTable properMarkdown header ("Int" : types) rows+     in showTable properMarkdown header types rows  -- | O(1) Creates an empty dataframe empty :: DataFrame@@ -162,7 +170,7 @@ toFloatMatrix ::     DataFrame -> Either DataFrameException (V.Vector (VU.Vector Float)) toFloatMatrix df = case V.foldl'-    (\acc c -> V.snoc <$> acc <*> (toFloatVector c))+    (\acc c -> V.snoc <$> acc <*> toFloatVector c)     (Right V.empty :: Either DataFrameException (V.Vector (VU.Vector Float)))     (columns df) of     Left e -> Left e@@ -189,7 +197,7 @@ toDoubleMatrix ::     DataFrame -> Either DataFrameException (V.Vector (VU.Vector Double)) toDoubleMatrix df = case V.foldl'-    (\acc c -> V.snoc <$> acc <*> (toDoubleVector c))+    (\acc c -> V.snoc <$> acc <*> toDoubleVector c)     (Right V.empty :: Either DataFrameException (V.Vector (VU.Vector Double)))     (columns df) of     Left e -> Left e@@ -215,7 +223,7 @@ -} toIntMatrix :: DataFrame -> Either DataFrameException (V.Vector (VU.Vector Int)) toIntMatrix df = case V.foldl'-    (\acc c -> V.snoc <$> acc <*> (toIntVector c))+    (\acc c -> V.snoc <$> acc <*> toIntVector c)     (Right V.empty :: Either DataFrameException (V.Vector (VU.Vector Int)))     (columns df) of     Left e -> Left e
src/DataFrame/Internal/Expression.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}@@ -17,6 +16,7 @@  import qualified Data.Map as M import Data.Maybe (fromMaybe, isJust)+import Data.String import qualified Data.Text as T import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl)) import qualified Data.Vector as V@@ -425,12 +425,11 @@     V.generate         (VU.length os - 1)         ( \i ->-            ( VG.generate+            VG.generate                 (os `VG.unsafeIndex` (i + 1) - (os `VG.unsafeIndex` i))                 ( \j ->                     col `VG.unsafeIndex` (indices `VG.unsafeIndex` (j + (os `VG.unsafeIndex` i)))                 )-            )         )  nestedTypeException ::@@ -504,7 +503,7 @@                 Just Refl -> case testEquality (typeRep @n) (typeRep @(VU.Vector d)) of                     Just Refl -> case (sUnbox @c, sUnbox @d, sUnbox @e) of                         (STrue, STrue, STrue) ->-                            Right $ UnAggregated $ fromVector $ V.zipWith (\l' r' -> VU.zipWith f l' r') l r+                            Right $ UnAggregated $ fromVector $ V.zipWith (VU.zipWith f) l r                         (STrue, STrue, SFalse) ->                             Right $                                 UnAggregated $@@ -517,7 +516,7 @@                                 Right $                                     UnAggregated $                                         fromVector $-                                            V.zipWith (\l' r' -> V.zipWith f (V.convert l') r') l r+                                            V.zipWith (V.zipWith f . V.convert) l r                             SFalse -> Left $ InternalException "Unboxed vectors contain boxed types"                         Nothing -> Left $ nestedTypeException @n @d (show right)                 Nothing -> case testEquality (typeRep @m) (typeRep @(V.Vector c)) of@@ -545,7 +544,7 @@                                         Right $                                             UnAggregated $                                                 fromVector $-                                                    V.zipWith (\l' r' -> V.zipWith f (V.convert l') r') l r+                                                    V.zipWith (V.zipWith f . V.convert) l r                                     STrue ->                                         Right $                                             UnAggregated $@@ -849,11 +848,11 @@                     )         (Left e) -> Left e         Right (UnAggregated (BoxedColumn (col :: V.Vector d))) -> case testEquality (typeRep @(V.Vector b)) (typeRep @d) of-            Just Refl -> Right $ Aggregated $ TColumn $ fromVector $ V.map (\v -> V.foldl' f s v) col+            Just Refl -> Right $ Aggregated $ TColumn $ fromVector $ V.map (V.foldl' f s) col             Nothing -> case testEquality (typeRep @(VU.Vector b)) (typeRep @d) of                 Just Refl -> case sUnbox @b of                     STrue ->-                        Right $ Aggregated $ TColumn $ fromVector $ V.map (\v -> VU.foldl' f s v) col+                        Right $ Aggregated $ TColumn $ fromVector $ V.map (VU.foldl' f s) col                     SFalse -> Left $ InternalException "Boxed type inside an unboxed column"                 Nothing -> Left $ nestedTypeException @d @b (show expr)         Right (UnAggregated _) -> Left $ InternalException "Aggregated into non-boxed column"@@ -898,6 +897,10 @@  divide :: (Fractional a, Columnable a) => Expr a -> Expr a -> Expr a divide = (/)++instance (IsString a, Columnable a) => IsString (Expr a) where+    fromString :: String -> Expr a+    fromString s = Lit (fromString s)  instance (Floating a, Columnable a) => Floating (Expr a) where     pi :: (Floating a, Columnable a) => Expr a
src/DataFrame/Internal/Schema.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}@@ -12,24 +13,86 @@ import qualified Data.Proxy as P import qualified Data.Text as T -import Data.Maybe+import Data.Maybe (isJust) import Data.Type.Equality (TestEquality (..))-import DataFrame.Internal.Column+import DataFrame.Internal.Column (Columnable) import Type.Reflection (typeRep) +-- | A runtime tag for a column’s element type. data SchemaType where+    -- | Constructor carrying a 'Proxy' of the element type.     SType :: (Columnable a) => P.Proxy a -> SchemaType +{- | Show the underlying element type using 'typeRep'.++==== __Examples__+>>> :set -XTypeApplications+>>> show (schemaType @Bool)+"Bool"+-} instance Show SchemaType where+    show :: SchemaType -> String     show (SType (_ :: P.Proxy a)) = show (typeRep @a) +{- | Two 'SchemaType's are equal iff their element types are the same.++==== __Examples__+>>> :set -XTypeApplications+>>> schemaType @Int == schemaType @Int+True++>>> schemaType @Int == schemaType @Integer+False+-} instance Eq SchemaType where-    (==) (SType (_ :: P.Proxy a)) (SType (_ :: P.Proxy b)) = isJust (testEquality (typeRep @a) (typeRep @b))+    (==) :: SchemaType -> SchemaType -> Bool+    (==) (SType (_ :: P.Proxy a)) (SType (_ :: P.Proxy b)) =+        isJust (testEquality (typeRep @a) (typeRep @b)) +{- | Construct a 'SchemaType' for the given @a@.++==== __Examples__+>>> :set -XTypeApplications+>>> schemaType @T.Text == schemaType @T.Text+True++>>> show (schemaType @Double)+"Double"+-} schemaType :: forall a. (Columnable a) => SchemaType schemaType = SType (P.Proxy @a) +{- | Logical schema of a 'DataFrame': a mapping from column names to their+element types ('SchemaType').++==== __Examples__+Constructing and querying a schema:++>>> import qualified Data.Map as M+>>> import qualified Data.Text as T+>>> let s = Schema (M.fromList [("country", schemaType @T.Text), ("amount", schemaType @Double)])+>>> M.lookup "amount" (elements s) == Just (schemaType @Double)+True++Extending a schema:++>>> let s' = Schema (M.insert "discount" (schemaType @Double) (elements s))+>>> M.member "discount" (elements s')+True++Equality is structural over the map contents:++>>> let a = Schema (M.fromList [("x", schemaType @Int), ("y", schemaType @Double)])+>>> let b = Schema (M.fromList [("y", schemaType @Double), ("x", schemaType @Int)])+>>> a == b+True+-} newtype Schema = Schema     { elements :: M.Map T.Text SchemaType+    {- ^ Mapping from /column name/ to its 'SchemaType'.++    Invariant: keys are unique column names. A missing key means the column+    is not present in the schema.+    -}     }     deriving (Show, Eq)
src/DataFrame/Internal/Statistics.hs view
@@ -79,7 +79,9 @@ skewness' = computeSkewness . VU.foldl' skewnessStep (SkewAcc 0 0 0 0) {-# INLINE skewness' #-} -correlation' :: VU.Vector Double -> VU.Vector Double -> Maybe Double+correlation' ::+    (Real a, VU.Unbox a, Real b, VU.Unbox b) =>+    VU.Vector a -> VU.Vector b -> Maybe Double correlation' xs ys     | VU.length xs /= VU.length ys = Nothing     | nI < 2 = Nothing@@ -93,8 +95,8 @@     !nI = VU.length xs     go !i !sumX !sumY !sumSquaredX !sumSquaredY !sumXY         | i < nI =-            let !x = VU.unsafeIndex xs i-                !y = VU.unsafeIndex ys i+            let !x = realToFrac (VU.unsafeIndex xs i)+                !y = realToFrac (VU.unsafeIndex ys i)                 !sumX' = sumX + x                 !sumY' = sumY + y                 !sumSquaredX' = sumSquaredX + x * x@@ -187,7 +189,10 @@ bincount k bs = VU.create $ do     mv <- VU.thaw (VU.replicate k 0)     VU.forM_ bs $ \b -> do-        let i = if b < 0 then 0 else if b >= k then k - 1 else b+        let i+                | b < 0 = 0+                | b >= k = k - 1+                | otherwise = b         x <- VUM.read mv i         VUM.write mv i (x + 1)     pure mv
src/DataFrame/Operations/Aggregation.hs view
@@ -65,7 +65,7 @@         VG.unsafeFreeze withIndexes  changingPoints :: (Eq a, VU.Unbox a) => VU.Vector (Int, a) -> [Int]-changingPoints vs = VG.length vs : (fst (VU.ifoldl findChangePoints initialState vs))+changingPoints vs = VG.length vs : fst (VU.ifoldl findChangePoints initialState vs)   where     initialState = ([0], snd (VG.head vs))     findChangePoints (offsets, currentVal) index (_, newVal)@@ -74,10 +74,10 @@  mkRowRep :: [Int] -> DataFrame -> Int -> Int mkRowRep groupColumnIndices df i = case h of-    (x : []) -> x+    [x] -> x     xs -> hash h   where-    h = (map mkHash groupColumnIndices)+    h = map mkHash groupColumnIndices     getHashedElem :: Column -> Int -> Int     getHashedElem (BoxedColumn (c :: V.Vector a)) j = hash' @a (c V.! j)     getHashedElem (UnboxedColumn (c :: VU.Vector a)) j = hash' @a (c VU.! j)
src/DataFrame/Operations/Subset.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}@@ -15,11 +14,9 @@ import qualified Data.Vector as V import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as VU-import qualified Data.Vector.Unboxed.Mutable as VUM import qualified Prelude  import Control.Exception (throw)-import Control.Monad.ST import Data.Function ((&)) import Data.Maybe (fromJust, fromMaybe, isJust, isNothing) import Data.Type.Equality (TestEquality (..))@@ -131,23 +128,19 @@                 }  indexes :: (VG.Vector v a) => (a -> Bool) -> v a -> VU.Vector Int-indexes condition cols = runST $ do-    ixs <- VUM.new 8192-    (!icount, _, _, !ixs') <--        VG.foldM-            ( \(!icount, !vcount, !cap, mv) v -> do-                if not (condition v)-                    then-                        pure (icount, vcount + 1, cap, mv)-                    else do-                        let shouldGrow = icount == cap-                        mv' <- if shouldGrow then VUM.grow mv cap else pure mv-                        VUM.write mv' icount vcount-                        pure (icount + 1, vcount + 1, cap + (cap * fromEnum shouldGrow), mv')-            )-            (0, 0, 8192, ixs)-            cols-    VU.freeze (VUM.slice 0 icount ixs')+indexes condition cols =+    let+        (ixs, n) =+            VG.ifoldl'+                ( \(acc, sz) i v -> do+                    if not (condition v)+                        then (acc, sz)+                        else (i : acc, sz + 1)+                )+                ([], 0)+                cols+     in+        VU.fromListN n ixs  {- | O(k) a version of filter where the predicate comes first. @@ -166,7 +159,7 @@         (TColumn col) = case interpret @Bool df expr of             Left e -> throw e             Right c -> c-        indexes = case findIndices (== True) col of+        indexes = case findIndices id col of             Right ixs -> ixs             Left e -> throw e         c' = snd $ dataframeDimensions df@@ -368,7 +361,7 @@     let         rand = generateRandomVector pureGen (fst (dataframeDimensions df))         withRand = df & insertUnboxedVector "__rand__" rand-        partitionSize = 1 / (fromIntegral folds)+        partitionSize = 1 / fromIntegral folds         singleFold n d =             d                 & filterWhere
src/DataFrame/Operations/Typing.hs view
@@ -19,84 +19,78 @@ import DataFrame.Internal.Parsing import Type.Reflection (typeRep) -parseDefaults :: Bool -> String -> DataFrame -> DataFrame-parseDefaults safeRead dateFormat df = df{columns = V.map (parseDefault safeRead dateFormat) (columns df)}+parseDefaults :: Int -> Bool -> String -> DataFrame -> DataFrame+parseDefaults n safeRead dateFormat df = df{columns = V.map (parseDefault n safeRead dateFormat) (columns df)} -parseDefault :: Bool -> String -> Column -> Column-parseDefault safeRead dateFormat (BoxedColumn (c :: V.Vector a)) =-    let-        parseTimeOpt s =-            parseTimeM {- Accept leading/trailing whitespace -}-                True-                defaultTimeLocale-                dateFormat-                (T.unpack s) ::-                Maybe Day-        unsafeParseTime s =-            parseTimeOrError {- Accept leading/trailing whitespace -}-                True-                defaultTimeLocale-                dateFormat-                (T.unpack s) ::-                Day-     in-        case (typeRep @a) `testEquality` (typeRep @T.Text) of-            Nothing -> case (typeRep @a) `testEquality` (typeRep @String) of-                Just Refl ->-                    let-                        emptyToNothing v = if isNullish (T.pack v) then Nothing else Just v-                        safeVector = V.map emptyToNothing c-                        hasNulls =-                            V.foldl' (\acc v -> if isNothing v then acc || True else acc) False safeVector-                     in-                        if safeRead && hasNulls-                            then BoxedColumn safeVector-                            else BoxedColumn (V.map T.pack c)-                Nothing -> BoxedColumn c-            Just Refl ->-                let example = T.strip (V.head c)-                    emptyToNothing v = if isNullish v then Nothing else Just v-                 in case readInt example of-                        Just _ ->-                            let safeVector = V.map ((=<<) readInt . emptyToNothing) c-                                hasNulls = V.elem Nothing safeVector-                             in if safeRead && hasNulls-                                    then BoxedColumn safeVector-                                    else UnboxedColumn (VU.generate (V.length c) (fromMaybe 0 . (safeVector V.!)))-                        Nothing -> case readDouble example of-                            Just _ ->-                                let safeVector = V.map ((=<<) readDouble . emptyToNothing) c-                                    hasNulls = V.elem Nothing safeVector-                                 in if safeRead && hasNulls-                                        then BoxedColumn safeVector-                                        else UnboxedColumn (VU.generate (V.length c) (fromMaybe 0 . (safeVector V.!)))-                            Nothing -> case parseTimeOpt example of-                                Just d ->-                                    let-                                        -- failed parse should be Either, nullish should be Maybe-                                        emptyToNothing' v = if isNullish v then Left v else Right v-                                        parseTimeEither v = case parseTimeOpt v of-                                            Just v' -> Right v'-                                            Nothing -> Left v-                                        safeVector = V.map ((=<<) parseTimeEither . emptyToNothing') c-                                        toMaybe (Left _) = Nothing-                                        toMaybe (Right value) = Just value-                                        lefts = V.filter isLeft safeVector-                                        onlyNulls = (not (V.null lefts) && V.all (isNullish . fromLeft "non-null") lefts)-                                     in-                                        if safeRead-                                            then-                                                if onlyNulls-                                                    then BoxedColumn (V.map toMaybe safeVector)-                                                    else-                                                        if V.any isLeft safeVector-                                                            then BoxedColumn safeVector-                                                            else BoxedColumn (V.map unsafeParseTime c)-                                            else BoxedColumn (V.map unsafeParseTime c)-                                Nothing ->-                                    let-                                        safeVector = V.map emptyToNothing c-                                        hasNulls = V.any isNullish c-                                     in-                                        if safeRead && hasNulls then BoxedColumn safeVector else BoxedColumn c-parseDefault _ _ column = column+parseDefault :: Int -> Bool -> String -> Column -> Column+parseDefault n safeRead dateFormat (BoxedColumn (c :: V.Vector a)) =+    case (typeRep @a) `testEquality` (typeRep @T.Text) of+        Nothing -> case (typeRep @a) `testEquality` (typeRep @String) of+            Just Refl -> parseFromExamples n dateFormat safeRead (V.map T.pack c)+            Nothing -> BoxedColumn c+        Just Refl -> parseFromExamples n dateFormat safeRead c+parseDefault _ _ _ column = column++emptyToNothing :: T.Text -> Maybe T.Text+emptyToNothing v = if isNullish v then Nothing else Just v++parseTimeOpt :: String -> T.Text -> Maybe Day+parseTimeOpt dateFormat s =+    parseTimeM {- Accept leading/trailing whitespace -}+        True+        defaultTimeLocale+        dateFormat+        (T.unpack s)++unsafeParseTime :: String -> T.Text -> Day+unsafeParseTime dateFormat s =+    parseTimeOrError {- Accept leading/trailing whitespace -}+        True+        defaultTimeLocale+        dateFormat+        (T.unpack s)++parseFromExamples :: Int -> String -> Bool -> V.Vector T.Text -> Column+parseFromExamples n dateFormat safeRead c+    | V.all isJust (V.map readInt examples) =+        let safeVector = V.map ((=<<) readInt . emptyToNothing) c+            hasNulls = V.elem Nothing safeVector+         in if safeRead && hasNulls+                then BoxedColumn safeVector+                else UnboxedColumn (VU.generate (V.length c) (fromMaybe 0 . (safeVector V.!)))+    | V.all isJust (V.map readDouble examples) =+        let safeVector = V.map ((=<<) readDouble . emptyToNothing) c+            hasNulls = V.elem Nothing safeVector+         in if safeRead && hasNulls+                then BoxedColumn safeVector+                else UnboxedColumn (VU.generate (V.length c) (fromMaybe 0 . (safeVector V.!)))+    | V.any isJust (V.map (parseTimeOpt dateFormat) examples) =+        let+            -- failed parse should be Either, nullish should be Maybe+            emptyToNothing' v = if isNullish v then Left v else Right v+            parseTimeEither v = case parseTimeOpt dateFormat v of+                Just v' -> Right v'+                Nothing -> Left v+            safeVector = V.map ((=<<) parseTimeEither . emptyToNothing') c+            toMaybe (Left _) = Nothing+            toMaybe (Right value) = Just value+            lefts = V.filter isLeft safeVector+            onlyNulls = (not (V.null lefts) && V.all (isNullish . fromLeft "non-null") lefts)+         in+            if safeRead+                then+                    if onlyNulls+                        then BoxedColumn (V.map toMaybe safeVector)+                        else+                            if V.any isLeft safeVector+                                then BoxedColumn safeVector+                                else BoxedColumn (V.map (unsafeParseTime dateFormat) c)+                else BoxedColumn (V.map (unsafeParseTime dateFormat) c)+    | otherwise =+        let+            safeVector = V.map emptyToNothing c+            hasNulls = V.any isNullish c+         in+            if safeRead && hasNulls then BoxedColumn safeVector else BoxedColumn c+  where+    examples = V.take n c
tests/Functions.hs view
@@ -11,6 +11,7 @@     lit,     max,     mean,+    median,     min,     percentile,     pow,@@ -87,7 +88,7 @@             , log (add (col @Double "x") (lit 1.0))             , exp (col @Double "x")             , mean (col @Double "x")-            , mean (col @Double "x")+            , median (col @Double "x")             , stddev (col @Double "x")             , sin (col @Double "x")             , cos (col @Double "x")@@ -119,7 +120,7 @@             , log (add (col @Double "x") (lit 1.0))             , exp (col @Double "x")             , mean (col @Double "x")-            , mean (col @Double "x")+            , median (col @Double "x")             , stddev (col @Double "x")             , sin (col @Double "x")             , cos (col @Double "x")@@ -133,7 +134,7 @@             , log (add (col @Double "y") (lit 1.0))             , exp (col @Double "y")             , mean (col @Double "y")-            , mean (col @Double "y")+            , median (col @Double "y")             , stddev (col @Double "y")             , sin (col @Double "y")             , cos (col @Double "y")@@ -341,7 +342,7 @@             , log (add (col @Double "x") (lit 1.0))             , exp (col @Double "x")             , mean (col @Double "x")-            , mean (col @Double "x")+            , median (col @Double "x")             , stddev (col @Double "x")             , sin (col @Double "x")             , cos (col @Double "x")@@ -355,7 +356,7 @@             , log (add (col @Double "y") (lit 1.0))             , exp (col @Double "y")             , mean (col @Double "y")-            , mean (col @Double "y")+            , median (col @Double "y")             , stddev (col @Double "y")             , sin (col @Double "y")             , cos (col @Double "y")@@ -372,7 +373,7 @@             , log (add (add (lit 1.0) (col @Double "z")) (lit 1.0))             , exp (add (lit 1.0) (col @Double "z"))             , mean (add (lit 1.0) (col @Double "z"))-            , mean (add (lit 1.0) (col @Double "z"))+            , median (add (lit 1.0) (col @Double "z"))             , stddev (add (lit 1.0) (col @Double "z"))             , sin (add (lit 1.0) (col @Double "z"))             , cos (add (lit 1.0) (col @Double "z"))
tests/Main.hs view
@@ -58,6 +58,7 @@                 )         actual =             D.parseDefault+                10                 True                 "%Y-%m-%d"                 (DI.fromVector (V.fromList ["2020-02-14" :: T.Text, "2021-02-14", "2022-02-14"]))@@ -77,6 +78,7 @@                 )         actual =             D.parseDefault+                10                 True                 "%Y-%m-%d"                 (DI.fromVector (V.fromList ["2020-02-14" :: T.Text, "2021-02-", "2022-02-14"]))@@ -93,6 +95,7 @@                 )         actual =             D.parseDefault+                10                 True                 "%Y-%m-%d"                 (DI.fromVector (V.fromList ["2020-02-14" :: T.Text, "", "2022-02-14"]))
tests/Operations/Aggregations.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-}  module Operations.Aggregations where@@ -31,8 +30,8 @@         ( assertEqual             "Counting elements after grouping gives correct numbers"             ( D.fromNamedColumns-                [ ("test1", DI.fromList [(1 :: Int), 2, 3])-                , ("test2", DI.fromList [(6 :: Int), 3, 3])+                [ ("test1", DI.fromList [1 :: Int, 2, 3])+                , ("test2", DI.fromList [6 :: Int, 3, 3])                 ]             )             ( testData@@ -47,8 +46,8 @@         ( assertEqual             "Mean works for ints"             ( D.fromNamedColumns-                [ ("test1", DI.fromList [(1 :: Int), 2, 3])-                , ("test2", DI.fromList [(6.5 :: Double), 8.0, 5.0])+                [ ("test1", DI.fromList [1 :: Int, 2, 3])+                , ("test2", DI.fromList [6.5 :: Double, 8.0, 5.0])                 ]             )             ( testData@@ -63,8 +62,8 @@         ( assertEqual             "Mean works for ints"             ( D.fromNamedColumns-                [ ("test1", DI.fromList [(1 :: Int), 2, 3])-                , ("test2", DI.fromList [(6.5 :: Double), 8.0, 5.0])+                [ ("test1", DI.fromList [1 :: Int, 2, 3])+                , ("test2", DI.fromList [6.5 :: Double, 8.0, 5.0])                 ]             )             ( testData@@ -81,13 +80,13 @@         ( assertEqual             "Mean works for ints"             ( D.fromNamedColumns-                [ ("test1", DI.fromList [(1 :: Int), 2, 3])-                , ("test2", DI.fromList [(13 :: Double), 16, 10])+                [ ("test1", DI.fromList [1 :: Int, 2, 3])+                , ("test2", DI.fromList [13 :: Double, 16, 10])                 ]             )             ( testData                 & D.groupBy ["test1"]-                & D.aggregate [F.mean (F.col @Int "test2" + (F.col @Int "test2")) `F.as` "test2"]+                & D.aggregate [F.mean (F.col @Int "test2" + F.col @Int "test2") `F.as` "test2"]             )         ) @@ -97,8 +96,8 @@         ( assertEqual             "Mean works for ints"             ( D.fromNamedColumns-                [ ("test1", DI.fromList [(1 :: Int), 2, 3])-                , ("test2", DI.fromList [(12 :: Double), 9, 6])+                [ ("test1", DI.fromList [1 :: Int, 2, 3])+                , ("test2", DI.fromList [12 :: Double, 9, 6])                 ]             )             ( testData@@ -116,14 +115,14 @@         ( assertEqual             "Mean works for ints"             ( D.fromNamedColumns-                [ ("test1", DI.fromList [(1 :: Int), 2, 3])-                , ("test2", DI.fromList [(24 :: Int), 18, 12])+                [ ("test1", DI.fromList [1 :: Int, 2, 3])+                , ("test2", DI.fromList [24 :: Int, 18, 12])                 ]             )             ( testData                 & D.groupBy ["test1"]                 & D.aggregate-                    [F.maximum (F.col @Int "test2" + (F.col @Int "test2")) `F.as` "test2"]+                    [F.maximum (F.col @Int "test2" + F.col @Int "test2") `F.as` "test2"]             )         ) 
tests/Operations/Core.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}  module Operations.Core where