packages feed

dataframe 1.1.0.0 → 1.1.1.0

raw patch · 40 files changed

+1554/−620 lines, 40 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- DataFrame.DecisionTree: instance GHC.Classes.Eq DataFrame.DecisionTree.TreeConfig
- DataFrame.DecisionTree: instance GHC.Show.Show DataFrame.DecisionTree.TreeConfig
- DataFrame.Internal.Column: generalLEQ :: (Typeable a, Typeable b, Ord a, Ord b) => a -> b -> Bool
- DataFrame.Internal.Column: instance GHC.Classes.Ord DataFrame.Internal.Column.Column
- DataFrame.Internal.Column: instance GHC.Classes.Ord a => GHC.Classes.Ord (DataFrame.Internal.Column.TypedColumn a)
- DataFrame.Internal.Column: sortedIndexes :: Bool -> Column -> Vector Int
- DataFrame.Internal.Expression: exprComp :: (Columnable b, Columnable c) => Expr b -> Expr c -> Ordering
- DataFrame.Internal.Parsing: readValue :: (HasCallStack, Read a) => Text -> a
- DataFrame.Internal.Parsing: readWithDefault :: (HasCallStack, Read a) => a -> Text -> a
- DataFrame.Internal.Parsing: safeReadValue :: Read a => Text -> Maybe a
- DataFrame.Internal.Row: instance GHC.Classes.Ord DataFrame.Internal.Row.Any
- DataFrame.Internal.Row: instance GHC.Read.Read DataFrame.Internal.Row.Any
- DataFrame.Internal.Row: produceOrderingFromRow :: [Bool] -> Row -> Row -> Ordering
- DataFrame.Internal.Row: sortedIndexes' :: [Bool] -> Vector Row -> Vector Int
+ DataFrame: fromCsv :: String -> IO (Either String DataFrame)
+ DataFrame: fromCsvBytes :: ByteString -> IO DataFrame
+ DataFrame: toCsv' :: DataFrame -> String
+ DataFrame.DecisionTree: ColumnOrdering :: Map SomeTypeRep OrdDict -> ColumnOrdering
+ DataFrame.DecisionTree: [OrdDict] :: forall a. (Columnable a, Ord a) => Proxy a -> OrdDict
+ DataFrame.DecisionTree: [columnOrdering] :: TreeConfig -> ColumnOrdering
+ DataFrame.DecisionTree: data OrdDict
+ DataFrame.DecisionTree: defaultColumnOrdering :: ColumnOrdering
+ DataFrame.DecisionTree: instance GHC.Base.Monoid DataFrame.DecisionTree.ColumnOrdering
+ DataFrame.DecisionTree: instance GHC.Base.Semigroup DataFrame.DecisionTree.ColumnOrdering
+ DataFrame.DecisionTree: newtype ColumnOrdering
+ DataFrame.DecisionTree: orderable :: (Columnable a, Ord a) => ColumnOrdering
+ DataFrame.DecisionTree: withOrdFrom :: Columnable a => ColumnOrdering -> (Ord a => r) -> Maybe r
+ DataFrame.IO.CSV: fromCsv :: String -> IO (Either String DataFrame)
+ DataFrame.IO.CSV: fromCsvBytes :: ByteString -> IO DataFrame
+ DataFrame.Internal.DataFrame: toCsv' :: DataFrame -> String
+ DataFrame.Operations.Permutation: sortIndices :: (Int -> Int -> Ordering) -> Int -> Vector Int
+ DataFrame.Operations.Permutation: sortOrderComparator :: SortOrder -> DataFrame -> Int -> Int -> Ordering
+ DataFrame.Typed.Lazy: Ascending :: SortOrder
+ DataFrame.Typed.Lazy: Descending :: SortOrder
+ DataFrame.Typed.Lazy: aggregate :: forall (keys :: [Symbol]) (cols :: [Type]) (aggs :: [Type]). TAgg keys cols aggs -> TypedLazyGrouped keys cols -> TypedLazyDataFrame (Append (GroupKeyColumns keys cols) (Reverse aggs))
+ DataFrame.Typed.Lazy: data SortOrder
+ DataFrame.Typed.Lazy: data TypedLazyDataFrame (cols :: [Type])
+ DataFrame.Typed.Lazy: derive :: forall (name :: Symbol) a (cols :: [Type]). (KnownSymbol name, Columnable a, AssertAbsent name cols) => TExpr cols a -> TypedLazyDataFrame cols -> TypedLazyDataFrame (Snoc cols (Column name a))
+ DataFrame.Typed.Lazy: filter :: forall (cols :: [Type]). TExpr cols Bool -> TypedLazyDataFrame cols -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: fromDataFrame :: forall (cols :: [Type]). TypedDataFrame cols -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: fromTypedDataFrame :: forall (cols :: [Type]). TypedDataFrame cols -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: groupBy :: forall (keys :: [Symbol]) (cols :: [Type]). (AllKnownSymbol keys, AssertAllPresent keys cols) => TypedLazyDataFrame cols -> TypedLazyGrouped keys cols
+ DataFrame.Typed.Lazy: instance GHC.Show.Show (DataFrame.Typed.Lazy.TypedLazyDataFrame cols)
+ DataFrame.Typed.Lazy: join :: forall (left :: [Type]) (right :: [Type]). JoinType -> Text -> Text -> TypedLazyDataFrame left -> TypedLazyDataFrame right -> TypedLazyDataFrame left
+ DataFrame.Typed.Lazy: run :: forall (cols :: [Type]). KnownSchema cols => TypedLazyDataFrame cols -> IO (TypedDataFrame cols)
+ DataFrame.Typed.Lazy: scanCsv :: forall (cols :: [Type]). Schema -> Text -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: scanParquet :: forall (cols :: [Type]). Schema -> Text -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: scanSeparated :: forall (cols :: [Type]). Char -> Schema -> Text -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: select :: forall (names :: [Symbol]) (cols :: [Type]). (AllKnownSymbol names, AssertAllPresent names cols) => TypedLazyDataFrame cols -> TypedLazyDataFrame (SubsetSchema names cols)
+ DataFrame.Typed.Lazy: sortBy :: forall (cols :: [Type]). [(Text, SortOrder)] -> TypedLazyDataFrame cols -> TypedLazyDataFrame cols
+ DataFrame.Typed.Lazy: take :: forall (cols :: [Type]). Int -> TypedLazyDataFrame cols -> TypedLazyDataFrame cols
- DataFrame: [Asc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame: [Asc] :: forall a. (Columnable a, Ord a) => Expr a -> SortOrder
- DataFrame: [Desc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame: [Desc] :: forall a. (Columnable a, Ord a) => Expr a -> SortOrder
- DataFrame: frequencies :: Columnable a => Expr a -> DataFrame -> DataFrame
+ DataFrame: frequencies :: (Columnable a, Ord a) => Expr a -> DataFrame -> DataFrame
- DataFrame: schemaType :: Columnable a => SchemaType
+ DataFrame: schemaType :: (Columnable a, Read a) => SchemaType
- DataFrame.DecisionTree: TreeConfig :: Int -> Int -> Int -> [Int] -> Int -> SynthConfig -> Int -> Double -> TreeConfig
+ DataFrame.DecisionTree: TreeConfig :: Int -> Int -> Int -> [Int] -> Int -> SynthConfig -> Int -> Double -> ColumnOrdering -> TreeConfig
- DataFrame.DecisionTree: buildGreedyTree :: Columnable a => TreeConfig -> Int -> Text -> [Expr Bool] -> DataFrame -> Tree a
+ DataFrame.DecisionTree: buildGreedyTree :: (Columnable a, Ord a) => TreeConfig -> Int -> Text -> [Expr Bool] -> DataFrame -> Tree a
- DataFrame.DecisionTree: buildProbTree :: Columnable a => Tree a -> Text -> DataFrame -> Vector Int -> ProbTree a
+ DataFrame.DecisionTree: buildProbTree :: (Columnable a, Ord a) => Tree a -> Text -> DataFrame -> Vector Int -> ProbTree a
- DataFrame.DecisionTree: buildTree :: Columnable a => TreeConfig -> Int -> Text -> [Expr Bool] -> DataFrame -> Expr a
+ DataFrame.DecisionTree: buildTree :: (Columnable a, Ord a) => TreeConfig -> Int -> Text -> [Expr Bool] -> DataFrame -> Expr a
- DataFrame.DecisionTree: calculateGini :: Columnable a => Text -> DataFrame -> Double
+ DataFrame.DecisionTree: calculateGini :: (Columnable a, Ord a) => Text -> DataFrame -> Double
- DataFrame.DecisionTree: findBestGreedySplit :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Maybe (Expr Bool)
+ DataFrame.DecisionTree: findBestGreedySplit :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Maybe (Expr Bool)
- DataFrame.DecisionTree: findBestSplit :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Maybe (Expr Bool)
+ DataFrame.DecisionTree: findBestSplit :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Maybe (Expr Bool)
- DataFrame.DecisionTree: fitDecisionTree :: Columnable a => TreeConfig -> Expr a -> DataFrame -> Expr a
+ DataFrame.DecisionTree: fitDecisionTree :: (Columnable a, Ord a) => TreeConfig -> Expr a -> DataFrame -> Expr a
- DataFrame.DecisionTree: fitProbTree :: Columnable a => TreeConfig -> Expr a -> DataFrame -> Map a (Expr Double)
+ DataFrame.DecisionTree: fitProbTree :: (Columnable a, Ord a) => TreeConfig -> Expr a -> DataFrame -> Map a (Expr Double)
- DataFrame.DecisionTree: getCounts :: Columnable a => Text -> DataFrame -> Map a Int
+ DataFrame.DecisionTree: getCounts :: (Columnable a, Ord a) => Text -> DataFrame -> Map a Int
- DataFrame.DecisionTree: majorityValue :: Columnable a => Text -> DataFrame -> a
+ DataFrame.DecisionTree: majorityValue :: (Columnable a, Ord a) => Text -> DataFrame -> a
- DataFrame.DecisionTree: majorityValueFromIndices :: Columnable a => Text -> DataFrame -> Vector Int -> a
+ DataFrame.DecisionTree: majorityValueFromIndices :: (Columnable a, Ord a) => Text -> DataFrame -> Vector Int -> a
- DataFrame.DecisionTree: optimizeAtDepth :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Int -> Int -> Tree a
+ DataFrame.DecisionTree: optimizeAtDepth :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Int -> Int -> Tree a
- DataFrame.DecisionTree: optimizeDepthLevel :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Int -> Tree a
+ DataFrame.DecisionTree: optimizeDepthLevel :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Int -> Tree a
- DataFrame.DecisionTree: optimizeNode :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Tree a
+ DataFrame.DecisionTree: optimizeNode :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Tree a
- DataFrame.DecisionTree: probExprs :: Columnable a => ProbTree a -> Map a (Expr Double)
+ DataFrame.DecisionTree: probExprs :: (Columnable a, Ord a) => ProbTree a -> Map a (Expr Double)
- DataFrame.DecisionTree: probsFromIndices :: Columnable a => Text -> DataFrame -> Vector Int -> Map a Double
+ DataFrame.DecisionTree: probsFromIndices :: (Columnable a, Ord a) => Text -> DataFrame -> Vector Int -> Map a Double
- DataFrame.DecisionTree: taoIteration :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Tree a
+ DataFrame.DecisionTree: taoIteration :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Tree a
- DataFrame.DecisionTree: taoOptimize :: Columnable a => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Tree a
+ DataFrame.DecisionTree: taoOptimize :: (Columnable a, Ord a) => TreeConfig -> Text -> [Expr Bool] -> DataFrame -> Vector Int -> Tree a -> Tree a
- DataFrame.Functions: cast :: Columnable a => Text -> Expr (Maybe a)
+ DataFrame.Functions: cast :: (Columnable a, Read a) => Text -> Expr (Maybe a)
- DataFrame.Functions: castEither :: Columnable a => Text -> Expr (Either Text a)
+ DataFrame.Functions: castEither :: (Columnable a, Read a) => Text -> Expr (Either Text a)
- DataFrame.Functions: castExpr :: (Columnable b, Columnable src) => Expr src -> Expr (Maybe b)
+ DataFrame.Functions: castExpr :: (Columnable b, Columnable src, Read b) => Expr src -> Expr (Maybe b)
- DataFrame.Functions: castExprEither :: (Columnable b, Columnable src) => Expr src -> Expr (Either Text b)
+ DataFrame.Functions: castExprEither :: (Columnable b, Columnable src, Read b) => Expr src -> Expr (Either Text b)
- DataFrame.Functions: castExprWithDefault :: (Columnable b, Columnable src) => b -> Expr src -> Expr b
+ DataFrame.Functions: castExprWithDefault :: (Columnable b, Columnable src, Read b) => b -> Expr src -> Expr b
- DataFrame.Functions: castWithDefault :: Columnable a => a -> Text -> Expr a
+ DataFrame.Functions: castWithDefault :: (Columnable a, Read a) => a -> Text -> Expr a
- DataFrame.Functions: recode :: (Columnable a, Columnable b) => [(a, b)] -> Expr a -> Expr (Maybe b)
+ DataFrame.Functions: recode :: (Columnable a, Columnable b, Show (a, b)) => [(a, b)] -> Expr a -> Expr (Maybe b)
- DataFrame.Functions: recodeWithDefault :: (Columnable a, Columnable b) => b -> [(a, b)] -> Expr a -> Expr b
+ DataFrame.Functions: recodeWithDefault :: (Columnable a, Columnable b, Show (a, b)) => b -> [(a, b)] -> Expr a -> Expr b
- DataFrame.Functions: unsafeCast :: Columnable a => Text -> Expr a
+ DataFrame.Functions: unsafeCast :: (Columnable a, Read a) => Text -> Expr a
- DataFrame.Functions: unsafeCastExpr :: (Columnable b, Columnable src) => Expr src -> Expr b
+ DataFrame.Functions: unsafeCastExpr :: (Columnable b, Columnable src, Read b) => Expr src -> Expr b
- DataFrame.Internal.Expression: [CastExprWith] :: forall a1 a src. (Columnable a1, Columnable a, Columnable src) => Text -> (Either String a1 -> a) -> Expr src -> Expr a
+ DataFrame.Internal.Expression: [CastExprWith] :: forall a1 a src. (Columnable a1, Columnable a, Columnable src, Read a1) => Text -> (Either String a1 -> a) -> Expr src -> Expr a
- DataFrame.Internal.Expression: [CastWith] :: forall a1 a. (Columnable a1, Columnable a) => Text -> Text -> (Either String a1 -> a) -> Expr a
+ DataFrame.Internal.Expression: [CastWith] :: forall a1 a. (Columnable a1, Columnable a, Read a1) => Text -> Text -> (Either String a1 -> a) -> Expr a
- DataFrame.Internal.Expression: normalize :: (Eq a, Ord a, Show a, Typeable a) => Expr a -> Expr a
+ DataFrame.Internal.Expression: normalize :: (Show a, Typeable a) => Expr a -> Expr a
- DataFrame.Internal.Schema: [SType] :: forall a. Columnable a => Proxy a -> SchemaType
+ DataFrame.Internal.Schema: [SType] :: forall a. (Columnable a, Read a) => Proxy a -> SchemaType
- DataFrame.Internal.Schema: schemaType :: Columnable a => SchemaType
+ DataFrame.Internal.Schema: schemaType :: (Columnable a, Read a) => SchemaType
- DataFrame.Internal.Types: type Columnable' a = (Typeable a, Show a, Ord a, Eq a, Read a)
+ DataFrame.Internal.Types: type Columnable' a = (Typeable a, Show a, Eq a)
- DataFrame.Operations.Permutation: [Asc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame.Operations.Permutation: [Asc] :: forall a. (Columnable a, Ord a) => Expr a -> SortOrder
- DataFrame.Operations.Permutation: [Desc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame.Operations.Permutation: [Desc] :: forall a. (Columnable a, Ord a) => Expr a -> SortOrder
- DataFrame.Operations.Statistics: frequencies :: Columnable a => Expr a -> DataFrame -> DataFrame
+ DataFrame.Operations.Statistics: frequencies :: (Columnable a, Ord a) => Expr a -> DataFrame -> DataFrame
- DataFrame.Typed: [Asc] :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed: [Asc] :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed: [Desc] :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed: [Desc] :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed: asc :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed: asc :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed: desc :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed: desc :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed.Expr: asc :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed.Expr: asc :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed.Expr: castExpr :: forall b (cols :: [Type]) src. (Columnable b, Columnable src) => TExpr cols src -> TExpr cols (Maybe b)
+ DataFrame.Typed.Expr: castExpr :: forall b (cols :: [Type]) src. (Columnable b, Columnable src, Read b) => TExpr cols src -> TExpr cols (Maybe b)
- DataFrame.Typed.Expr: castExprEither :: forall b (cols :: [Type]) src. (Columnable b, Columnable src) => TExpr cols src -> TExpr cols (Either Text b)
+ DataFrame.Typed.Expr: castExprEither :: forall b (cols :: [Type]) src. (Columnable b, Columnable src, Read b) => TExpr cols src -> TExpr cols (Either Text b)
- DataFrame.Typed.Expr: castExprWithDefault :: forall b (cols :: [Type]) src. (Columnable b, Columnable src) => b -> TExpr cols src -> TExpr cols b
+ DataFrame.Typed.Expr: castExprWithDefault :: forall b (cols :: [Type]) src. (Columnable b, Columnable src, Read b) => b -> TExpr cols src -> TExpr cols b
- DataFrame.Typed.Expr: desc :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed.Expr: desc :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed.Expr: unsafeCastExpr :: forall b (cols :: [Type]) src. (Columnable b, Columnable src) => TExpr cols src -> TExpr cols b
+ DataFrame.Typed.Expr: unsafeCastExpr :: forall b (cols :: [Type]) src. (Columnable b, Columnable src, Read b) => TExpr cols src -> TExpr cols b
- DataFrame.Typed.Types: [Asc] :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed.Types: [Asc] :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols
- DataFrame.Typed.Types: [Desc] :: forall a (cols :: [Type]). Columnable a => TExpr cols a -> TSortOrder cols
+ DataFrame.Typed.Types: [Desc] :: forall a (cols :: [Type]). (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols

Files

CHANGELOG.md view
@@ -1,5 +1,20 @@ # Revision history for dataframe +## 1.1.1.0+### New features+* Add `DataFrame.Typed.Lazy` module — a type-safe lazy query pipeline combining compile-time schema tracking with deferred execution.+* Add `fromCsv` function for parsing a CSV string directly into a DataFrame.+* Add `DataKinds` extension and `DataFrame.Typed` import to the GHCi file for easier interactive typed dataframe workflows.++### Performance+* Specialize and inline aggregation functions (`sum`, `mean`, `variance`, `median`, `stddev`, etc.) to avoid expensive numeric conversions at runtime.+* Remove `Ord` constraint from `Columnable'` and move it to call sites, reducing unnecessary constraint propagation.+* Replace exponential type-level `If` nesting in typed schema families with linear helper type families, fixing slow compilation times.++### Bug fixes+* Fix Functions module compilation under GHC 9.10 (#194).+* Document and test `safeColumns` option in `ParquetReadOptions` (#190).+ ## 1.1.0.0 ### Breaking changes * Remove `OptionalColumn` constructor; fold nullability into `BoxedColumn`/`UnboxedColumn` via bit-packed bitmap.
dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               dataframe-version:            1.1.0.0+version:            1.1.1.0  synopsis: A fast, safe, and intuitive DataFrame library. @@ -114,6 +114,7 @@                     DataFrame.Typed.Aggregate,                     DataFrame.Typed.TH,                     DataFrame.Typed.Expr,+                    DataFrame.Typed.Lazy,                     DataFrame.Typed     build-depends:    base >= 4 && <5,                       deepseq >= 1 && < 2,@@ -248,6 +249,7 @@                    Functions,                    GenDataFrame,                    Internal.Parsing,+                   IO.CSV,                    IO.JSON,                    Operations.Aggregations,                    Operations.Apply,
ffi/DataFrame/IR.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -15,16 +16,19 @@  import Data.Aeson (FromJSON (..), withObject, (.:)) import Data.Aeson.Types (Parser)+import qualified Data.ByteString as BS+import Data.Int (Int16, Int32, Int64, Int8) import qualified Data.Text as T import Data.Type.Equality (     TestEquality (testEquality),     type (:~:) (Refl),+    type (:~~:) (HRefl),  ) import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU-import Data.Word (Word64)+import Data.Word (Word, Word16, Word32, Word64, Word8) import Foreign (wordPtrToPtr)-import Type.Reflection (typeRep)+import Type.Reflection (SomeTypeRep (..), eqTypeRep, typeRep)  import DataFrame.Functions (count, mean, meanMaybe, sumMaybe) import qualified DataFrame.Functions as Functions@@ -34,7 +38,7 @@     readSeparated,     readTsv,  )-import DataFrame.Internal.Column (Column (..))+import DataFrame.Internal.Column (Column (..), Columnable) import DataFrame.Internal.DataFrame (DataFrame, unsafeGetColumn) import DataFrame.Internal.Expression (Expr (..), NamedExpr) import DataFrame.Operations.Aggregation (aggregate, groupBy)@@ -111,16 +115,38 @@ executePlan (Limit k node) =     Subset.take k <$> executePlan node --- | Build a SortOrder from a column's runtime type.+{- | Build a SortOrder from a column's runtime type.+Uses type dispatch to recover Ord for known column types.+-} mkSortOrder :: Bool -> T.Text -> Column -> SortOrder-mkSortOrder True n (UnboxedColumn Nothing (_ :: VU.Vector a)) = Asc (Col @a n)-mkSortOrder False n (UnboxedColumn Nothing (_ :: VU.Vector a)) = Desc (Col @a n)-mkSortOrder True n (UnboxedColumn (Just _) (_ :: VU.Vector a)) = Asc (Col @(Maybe a) n)-mkSortOrder False n (UnboxedColumn (Just _) (_ :: VU.Vector a)) = Desc (Col @(Maybe a) n)-mkSortOrder True n (BoxedColumn Nothing (_ :: V.Vector a)) = Asc (Col @a n)-mkSortOrder False n (BoxedColumn Nothing (_ :: V.Vector a)) = Desc (Col @a n)-mkSortOrder True n (BoxedColumn (Just _) (_ :: V.Vector a)) = Asc (Col @(Maybe a) n)-mkSortOrder False n (BoxedColumn (Just _) (_ :: V.Vector a)) = Desc (Col @(Maybe a) n)+mkSortOrder isAsc name col = dispatchType (columnTypeRep col)+  where+    columnTypeRep :: Column -> SomeTypeRep+    columnTypeRep (UnboxedColumn _ (_ :: VU.Vector a)) = SomeTypeRep (typeRep @a)+    columnTypeRep (BoxedColumn _ (_ :: V.Vector a)) = SomeTypeRep (typeRep @a)+    mk :: (Columnable a, Ord a) => Expr a -> SortOrder+    mk = if isAsc then Asc else Desc+    dispatchType (SomeTypeRep tr)+        | Just HRefl <- eqTypeRep tr (typeRep @Int) = mk (Col @Int name)+        | Just HRefl <- eqTypeRep tr (typeRep @Int8) = mk (Col @Int8 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Int16) = mk (Col @Int16 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Int32) = mk (Col @Int32 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Int64) = mk (Col @Int64 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Word) = mk (Col @Word name)+        | Just HRefl <- eqTypeRep tr (typeRep @Word8) = mk (Col @Word8 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Word16) = mk (Col @Word16 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Word32) = mk (Col @Word32 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Word64) = mk (Col @Word64 name)+        | Just HRefl <- eqTypeRep tr (typeRep @Integer) = mk (Col @Integer name)+        | Just HRefl <- eqTypeRep tr (typeRep @Double) = mk (Col @Double name)+        | Just HRefl <- eqTypeRep tr (typeRep @Float) = mk (Col @Float name)+        | Just HRefl <- eqTypeRep tr (typeRep @Bool) = mk (Col @Bool name)+        | Just HRefl <- eqTypeRep tr (typeRep @Char) = mk (Col @Char name)+        | Just HRefl <- eqTypeRep tr (typeRep @T.Text) = mk (Col @T.Text name)+        | Just HRefl <- eqTypeRep tr (typeRep @String) = mk (Col @String name)+        | Just HRefl <- eqTypeRep tr (typeRep @BS.ByteString) =+            mk (Col @BS.ByteString name)+        | otherwise = error $ "mkSortOrder: unsupported column type: " ++ show tr  -- | Dispatch aggregation by fn name and runtime column type. buildNamedExpr :: DataFrame -> AggSpec -> IO NamedExpr
src/DataFrame.hs view
@@ -250,6 +250,8 @@     ReadOptions (..),     TypeSpec (..),     defaultReadOptions,+    fromCsv,+    fromCsvBytes,     readCsv,     readCsvWithOpts,     readSeparated,@@ -282,6 +284,7 @@     empty,     null,     toCsv,+    toCsv',     toMarkdown,     toMarkdown',     toSeparated,
src/DataFrame/DecisionTree.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -20,22 +21,82 @@  import Control.Exception (throw) import Control.Monad (guard)-import Data.Containers.ListUtils (nubOrd) import Data.Function (on) #if MIN_VERSION_base(4,20,0)-import Data.List (maximumBy, minimumBy, sort, sortBy)+import Data.List (maximumBy, minimumBy, nub, sort, sortBy) #else-import Data.List (foldl', maximumBy, minimumBy, sort, sortBy)+import Data.List (foldl', maximumBy, minimumBy, nub, sort, sortBy) #endif+import Data.Int (Int16, Int32, Int64, Int8) import qualified Data.Map.Strict as M+import Data.Proxy (Proxy (..)) import qualified Data.Text as T import Data.Type.Equality import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU-import Type.Reflection (typeRep)+import Data.Word (Word16, Word32, Word64, Word8)+import Type.Reflection (SomeTypeRep (..), typeRep)  import DataFrame.Operators +{- | Declares which column types support ordering for decision tree splits.++Use 'orderable' to register a type, and '<>' to combine:++@+defaultTreeConfig+    { columnOrdering = defaultColumnOrdering <> orderable \@MyCustomType+    }+@+-}+newtype ColumnOrdering = ColumnOrdering (M.Map SomeTypeRep OrdDict)++instance Semigroup ColumnOrdering where+    ColumnOrdering a <> ColumnOrdering b = ColumnOrdering (a <> b)++instance Monoid ColumnOrdering where+    mempty = ColumnOrdering M.empty++-- | Register a type as orderable for decision tree splits.+orderable :: forall a. (Columnable a, Ord a) => ColumnOrdering+orderable = ColumnOrdering (M.singleton (SomeTypeRep (typeRep @a)) (OrdDict (Proxy @a)))++-- | All standard numeric, text, and primitive types.+defaultColumnOrdering :: ColumnOrdering+defaultColumnOrdering =+    mconcat+        [ orderable @Int+        , orderable @Int8+        , orderable @Int16+        , orderable @Int32+        , orderable @Int64+        , orderable @Word+        , orderable @Word8+        , orderable @Word16+        , orderable @Word32+        , orderable @Word64+        , orderable @Integer+        , orderable @Double+        , orderable @Float+        , orderable @Bool+        , orderable @Char+        , orderable @T.Text+        , orderable @String+        ]++-- Internal: existential Ord dictionary.+data OrdDict where+    OrdDict :: (Columnable a, Ord a) => Proxy a -> OrdDict++-- Internal: look up Ord for type @a@.+withOrdFrom ::+    forall a r. (Columnable a) => ColumnOrdering -> ((Ord a) => r) -> Maybe r+withOrdFrom (ColumnOrdering m) k = case M.lookup (SomeTypeRep (typeRep @a)) m of+    Just (OrdDict (_ :: Proxy b)) -> case testEquality (typeRep @a) (typeRep @b) of+        Just Refl -> Just k+        Nothing -> Nothing+    Nothing -> Nothing+ data TreeConfig = TreeConfig     { maxTreeDepth :: Int     , minSamplesSplit :: Int@@ -45,8 +106,8 @@     , synthConfig :: SynthConfig     , taoIterations :: Int     , taoConvergenceTol :: Double+    , columnOrdering :: ColumnOrdering     }-    deriving (Eq, Show)  data SynthConfig = SynthConfig     { maxExprDepth :: Int@@ -82,6 +143,7 @@         , synthConfig = defaultSynthConfig         , taoIterations = 10         , taoConvergenceTol = 1e-6+        , columnOrdering = defaultColumnOrdering         }  data Tree a@@ -101,7 +163,7 @@ -- | Fit a TAO decision tree fitDecisionTree ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     Expr a ->     DataFrame ->@@ -109,7 +171,7 @@ fitDecisionTree cfg (Col target) df =     let         conds =-            nubOrd $+            nub $                 numericConditions cfg (exclude [target] df)                     ++ generateConditionsOld cfg (exclude [target] df) @@ -124,7 +186,7 @@  taoOptimize ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     T.Text -> -- Target column name     [Expr Bool] -> -- Candidate conditions@@ -151,7 +213,7 @@  taoIteration ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     T.Text ->     [Expr Bool] ->@@ -168,7 +230,7 @@  optimizeDepthLevel ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     T.Text ->     [Expr Bool] ->@@ -181,7 +243,7 @@  optimizeAtDepth ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     T.Text ->     [Expr Bool] ->@@ -224,7 +286,7 @@  optimizeNode ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     T.Text ->     [Expr Bool] ->@@ -333,8 +395,8 @@ identifyCarePoints target df indices leftTree rightTree =     case interpret @a df (Col target) of         Left _ -> []-        Right (TColumn col) ->-            case toVector @a col of+        Right (TColumn column) ->+            case toVector @a column of                 Left _ -> []                 Right targetVals ->                     V.toList $ V.mapMaybe (checkPoint targetVals) indices@@ -366,8 +428,8 @@ predictWithTree target df idx (Branch cond left right) =     case interpret @Bool df cond of         Left _ -> predictWithTree @a target df idx left -- Default to left on error-        Right (TColumn col) ->-            case toVector @Bool col of+        Right (TColumn column) ->+            case toVector @Bool column of                 Left _ -> predictWithTree @a target df idx left                 Right boolVals ->                     if boolVals V.! idx@@ -378,8 +440,8 @@ countCarePointErrors cond df carePoints =     case interpret @Bool df cond of         Left _ -> length carePoints-        Right (TColumn col) ->-            case toVector @Bool col of+        Right (TColumn column) ->+            case toVector @Bool column of                 Left _ -> length carePoints                 Right boolVals ->                     length $ filter (isMisclassified boolVals) carePoints@@ -395,15 +457,15 @@ partitionIndices cond df indices =     case interpret @Bool df cond of         Left _ -> (indices, V.empty)-        Right (TColumn col) ->-            case toVector @Bool col of+        Right (TColumn column) ->+            case toVector @Bool column of                 Left _ -> (indices, V.empty)                 Right boolVals ->                     V.partition (boolVals V.!) indices  majorityValueFromIndices ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     T.Text ->     DataFrame ->     V.Vector Int ->@@ -437,8 +499,8 @@     | otherwise =         case interpret @a df (Col target) of             Left _ -> 1.0-            Right (TColumn col) ->-                case toVector @a col of+            Right (TColumn column) ->+                case toVector @a column of                     Left _ -> 1.0                     Right targetVals ->                         let@@ -476,7 +538,7 @@  buildGreedyTree ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     Int ->     T.Text ->@@ -501,7 +563,7 @@  findBestGreedySplit ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig -> T.Text -> [Expr Bool] -> DataFrame -> Maybe (Expr Bool) findBestGreedySplit cfg target conds df =     let@@ -637,24 +699,24 @@ numericCols :: DataFrame -> [NumExpr] numericCols df = concatMap extract (columnNames df)   where-    extract col = case unsafeGetColumn col df of+    extract colName = case unsafeGetColumn colName df of         UnboxedColumn Nothing (_ :: VU.Vector b) ->             case testEquality (typeRep @b) (typeRep @Double) of-                Just Refl -> [NDouble (Col col)]+                Just Refl -> [NDouble (Col colName)]                 Nothing -> case sIntegral @b of-                    STrue -> [NDouble (F.toDouble (Col @b col))]+                    STrue -> [NDouble (F.toDouble (Col @b colName))]                     SFalse -> []         BoxedColumn (Just _) (_ :: V.Vector b) ->             case testEquality (typeRep @b) (typeRep @Double) of-                Just Refl -> [NMaybeDouble (Col @(Maybe b) col)]+                Just Refl -> [NMaybeDouble (Col @(Maybe b) colName)]                 Nothing -> case sIntegral @b of-                    STrue -> [NMaybeDouble (F.whenPresent (realToFrac @b @Double) (Col @(Maybe b) col))]+                    STrue -> [NMaybeDouble (F.whenPresent (realToFrac @b @Double) (Col @(Maybe b) colName))]                     SFalse -> []         UnboxedColumn (Just _) (_ :: VU.Vector b) ->             case testEquality (typeRep @b) (typeRep @Double) of-                Just Refl -> [NMaybeDouble (Col @(Maybe b) col)]+                Just Refl -> [NMaybeDouble (Col @(Maybe b) colName)]                 Nothing -> case sIntegral @b of-                    STrue -> [NMaybeDouble (F.whenPresent (realToFrac @b @Double) (Col @(Maybe b) col))]+                    STrue -> [NMaybeDouble (F.whenPresent (realToFrac @b @Double) (Col @(Maybe b) colName))]                     SFalse -> []         _ -> [] @@ -701,19 +763,21 @@ generateConditionsOld :: TreeConfig -> DataFrame -> [Expr Bool] generateConditionsOld cfg df =     let+        ords = columnOrdering cfg         genConds :: T.Text -> [Expr Bool]         genConds colName = case unsafeGetColumn colName df of-            (BoxedColumn Nothing (col :: V.Vector a)) ->-                let ps = map (Lit . (`percentileOrd'` col)) [1, 25, 75, 99]-                 in map (F.lift2 (==) (Col @a colName)) ps-            (BoxedColumn (Just _) (col :: V.Vector a)) -> case sFloating @a of+            (BoxedColumn Nothing (column :: V.Vector a)) ->+                case withOrdFrom @a ords (map (Lit . (`percentileOrd'` column)) [1, 25, 75, 99]) of+                    Just ps -> map (F.lift2 (==) (Col @a colName)) ps+                    Nothing -> []+            (BoxedColumn (Just _) (column :: V.Vector a)) -> case sFloating @a of                 STrue -> [] -- handled by numericCols / numericExprs                 SFalse -> case sIntegral @a of                     STrue -> [] -- handled by numericCols / numericExprs                     SFalse ->-                        map-                            (F.lift2 (==) (Col @(Maybe a) colName) . Lit . Just . (`percentileOrd'` col))-                            [1, 25, 75, 99]+                        case withOrdFrom @a ords (map (Lit . Just . (`percentileOrd'` column)) [1, 25, 75, 99]) of+                            Just ps -> map (F.lift2 (==) (Col @(Maybe a) colName)) ps+                            Nothing -> []             (UnboxedColumn _ (_ :: VU.Vector a)) -> []          columnConds =@@ -743,9 +807,10 @@                         Nothing -> []                         Just Refl -> case testEquality (typeRep @a) (typeRep @T.Text) of                             Nothing ->-                                [ F.lift2 (<=) (Col @(Maybe a) l) (Col r)-                                , F.lift2 (==) (Col @(Maybe a) l) (Col r)-                                ]+                                case withOrdFrom @a ords [F.lift2 (<=) (Col @(Maybe a) l) (Col r)] of+                                    Just leExprs ->+                                        leExprs ++ [F.lift2 (==) (Col @(Maybe a) l) (Col r)]+                                    Nothing -> [F.lift2 (==) (Col @(Maybe a) l) (Col r)]                             Just Refl -> [F.lift2 (==) (Col @(Maybe a) l) (Col r)]                 _ -> []      in@@ -754,7 +819,8 @@ partitionDataFrame :: Expr Bool -> DataFrame -> (DataFrame, DataFrame) partitionDataFrame cond df = (filterWhere cond df, filterWhere (F.not cond) df) -calculateGini :: forall a. (Columnable a) => T.Text -> DataFrame -> Double+calculateGini ::+    forall a. (Columnable a, Ord a) => T.Text -> DataFrame -> Double calculateGini target df =     let n = fromIntegral $ nRows df         counts = getCounts @a target df@@ -762,19 +828,20 @@         probs = map (\c -> (fromIntegral c + 1) / (n + numClasses)) (M.elems counts)      in if n == 0 then 0 else 1 - sum (map (^ 2) probs) -majorityValue :: forall a. (Columnable a) => T.Text -> DataFrame -> a+majorityValue :: forall a. (Columnable a, Ord a) => T.Text -> DataFrame -> a majorityValue target df =     let counts = getCounts @a target df      in if M.null counts             then error "Empty DataFrame in leaf"             else fst $ maximumBy (compare `on` snd) (M.toList counts) -getCounts :: forall a. (Columnable a) => T.Text -> DataFrame -> M.Map a Int+getCounts ::+    forall a. (Columnable a, Ord a) => T.Text -> DataFrame -> M.Map a Int getCounts target df =     case interpret @a df (Col target) of         Left e -> throw e-        Right (TColumn col) ->-            case toVector @a col of+        Right (TColumn column) ->+            case toVector @a column of                 Left e -> throw e                 Right vals -> foldl' (\acc x -> M.insertWith (+) x 1 acc) M.empty (V.toList vals) @@ -782,8 +849,8 @@ percentile p expr df =     case interpret @Double df expr of         Left _ -> 0-        Right (TColumn col) ->-            case toVector @Double col of+        Right (TColumn column) ->+            case toVector @Double column of                 Left _ -> 0                 Right vals ->                     let sorted = V.fromList $ sort $ V.toList vals@@ -793,7 +860,7 @@  buildTree ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     Int ->     T.Text ->@@ -810,7 +877,7 @@  findBestSplit ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig -> T.Text -> [Expr Bool] -> DataFrame -> Maybe (Expr Bool) findBestSplit = findBestGreedySplit @a @@ -823,7 +890,7 @@ -- | Compute normalised class probabilities from a subset of training rows. probsFromIndices ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     T.Text ->     DataFrame ->     V.Vector Int ->@@ -831,8 +898,8 @@ probsFromIndices target df indices =     case interpret @a df (Col target) of         Left _ -> M.empty-        Right (TColumn col) ->-            case toVector @a col of+        Right (TColumn column) ->+            case toVector @a column of                 Left _ -> M.empty                 Right vals ->                     let counts =@@ -849,7 +916,7 @@ -} buildProbTree ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     Tree a ->     T.Text ->     DataFrame ->@@ -879,7 +946,7 @@ -} fitProbTree ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     TreeConfig ->     Expr a -> -- target column, e.g. @Col \"label\"@     DataFrame ->@@ -887,7 +954,7 @@ fitProbTree cfg (Col target) df =     let         conds =-            nubOrd $+            nub $                 numericConditions cfg (exclude [target] df)                     ++ generateConditionsOld cfg (exclude [target] df)         initialTree = buildGreedyTree @a cfg (maxTreeDepth cfg) target conds df@@ -915,11 +982,11 @@ -} probExprs ::     forall a.-    (Columnable a) =>+    (Columnable a, Ord a) =>     ProbTree a ->     M.Map a (Expr Double) probExprs tree =-    let classes = nubOrd (allClasses tree)+    let classes = nub (allClasses tree)      in M.fromList [(c, classExpr c tree) | c <- classes]   where     allClasses :: ProbTree a -> [a]
src/DataFrame/Display/Terminal/Plot.hs view
@@ -10,6 +10,7 @@ module DataFrame.Display.Terminal.Plot where  import Control.Monad+import qualified Data.Bifunctor import qualified Data.List as L import qualified Data.Map as M import qualified Data.Text as T@@ -18,9 +19,10 @@ 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 DataFrame.Internal.Types import GHC.Stack (HasCallStack)-import Type.Reflection (typeRep)+import Type.Reflection (TypeRep, typeRep)  import DataFrame.Internal.Column (Column (..), Columnable, isNumeric) import qualified DataFrame.Internal.Column as D@@ -339,18 +341,54 @@         Just idx ->             let col = columns df V.! idx              in case col of-                    BoxedColumn _ vec ->-                        let counts = countValues vec-                         in Just [(T.pack (show k), fromIntegral v) | (k, v) <- counts]-                    UnboxedColumn _ vec ->-                        let counts = countValuesUnboxed vec-                         in Just [(T.pack (show k), fromIntegral v) | (k, v) <- counts]+                    BoxedColumn _ (vec :: V.Vector a) ->+                        Just (countBoxed (typeRep @a) vec)+                    UnboxedColumn _ (vec :: VU.Vector a) ->+                        Just (countUnboxed (typeRep @a) vec)   where-    countValues :: (Ord a, Show a) => V.Vector a -> [(a, Int)]+    countBoxed ::+        forall a. (Show a) => TypeRep a -> V.Vector a -> [(T.Text, Double)]+    countBoxed tr vec+        | Just Refl <- testEquality tr (typeRep @T.Text) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @String) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Integer) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Int) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Double) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Float) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Bool) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Char) = toPairs $ countValues vec+        | otherwise = countByShow $ V.toList vec++    countUnboxed ::+        forall a. (Show a, VU.Unbox a) => TypeRep a -> VU.Vector a -> [(T.Text, Double)]+    countUnboxed tr vec+        | Just Refl <- testEquality tr (typeRep @Int) = toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Double) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Float) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Bool) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Char) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Word8) =+            toPairs $ countValuesUnboxed vec+        | otherwise = countByShow $ VU.toList vec++    toPairs :: (Show a) => [(a, Int)] -> [(T.Text, Double)]+    toPairs = map (\(k, v) -> (T.pack (show k), fromIntegral v))++    countValues :: (Ord a) => V.Vector a -> [(a, Int)]     countValues vec = M.toList $ V.foldr' (\x acc -> M.insertWith (+) x 1 acc) M.empty vec -    countValuesUnboxed :: (Ord a, Show a, VU.Unbox a) => VU.Vector a -> [(a, Int)]+    countValuesUnboxed :: (Ord a, VU.Unbox a) => VU.Vector a -> [(a, Int)]     countValuesUnboxed vec = M.toList $ VU.foldr' (\x acc -> M.insertWith (+) x 1 acc) M.empty vec++    countByShow :: (Show a) => [a] -> [(T.Text, Double)]+    countByShow xs =+        map (Data.Bifunctor.bimap T.pack fromIntegral) $+            M.toList $+                L.foldl' (\acc x -> M.insertWith (+) (show x) 1 acc) M.empty xs  isNumericColumnCheck :: T.Text -> DataFrame -> Bool isNumericColumnCheck colName df = isNumericColumn df colName
src/DataFrame/Display/Terminal/PrettyPrint.hs view
@@ -62,7 +62,7 @@         separator = T.intercalate "-|-" [T.replicate width (T.singleton '-') | width <- widths]         fillCols fill cols =             T.intercalate " | " [fill c width col | (c, width, col) <- zip3 cs widths cols]-        lines =+        outputLines =             if properMarkdown                 then                     T.concat ["| ", fillCols colTitleFill consolidatedHeader, " |"]@@ -75,4 +75,4 @@                         : fillCols colTitleFill types                         : separator                         : map (fillCols colValueFill) rows-     in T.unlines lines+     in T.unlines outputLines
src/DataFrame/Display/Web/Plot.hs view
@@ -10,6 +10,7 @@ module DataFrame.Display.Web.Plot where  import Control.Monad+import qualified Data.Bifunctor import Data.Char import qualified Data.List as L import qualified Data.Map as M@@ -19,9 +20,10 @@ 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 System.Random (newStdGen, randomRs)-import Type.Reflection (typeRep)+import Type.Reflection (TypeRep, typeRep)  import DataFrame.Internal.Column (Column (..), Columnable, isNumeric) import qualified DataFrame.Internal.Column as D@@ -895,19 +897,56 @@             let col = columns df V.! idx              in case col of                     BoxedColumn _ (vec :: V.Vector a) ->-                        let counts = countValues vec-                         in case testEquality (typeRep @a) (typeRep @T.Text) of-                                Nothing -> Just [(T.pack (show k), fromIntegral v) | (k, v) <- counts]-                                Just Refl -> Just [(k, fromIntegral v) | (k, v) <- counts]-                    UnboxedColumn _ vec ->-                        let counts = countValuesUnboxed vec-                         in Just [(T.pack (show k), fromIntegral v) | (k, v) <- counts]+                        Just (countBoxed (typeRep @a) vec)+                    UnboxedColumn _ (vec :: VU.Vector a) ->+                        Just (countUnboxed (typeRep @a) vec)   where-    countValues :: (Ord a, Show a) => V.Vector a -> [(a, Int)]+    countBoxed ::+        forall a. (Show a) => TypeRep a -> V.Vector a -> [(T.Text, Double)]+    countBoxed tr vec+        | Just Refl <- testEquality tr (typeRep @T.Text) = toPairsText $ countValues vec+        | Just Refl <- testEquality tr (typeRep @String) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Integer) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Int) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Double) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Float) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Bool) = toPairs $ countValues vec+        | Just Refl <- testEquality tr (typeRep @Char) = toPairs $ countValues vec+        | otherwise = countByShow $ V.toList vec++    countUnboxed ::+        forall a. (Show a, VU.Unbox a) => TypeRep a -> VU.Vector a -> [(T.Text, Double)]+    countUnboxed tr vec+        | Just Refl <- testEquality tr (typeRep @Int) = toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Double) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Float) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Bool) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Char) =+            toPairs $ countValuesUnboxed vec+        | Just Refl <- testEquality tr (typeRep @Word8) =+            toPairs $ countValuesUnboxed vec+        | otherwise = countByShow $ VU.toList vec++    toPairs :: (Show a) => [(a, Int)] -> [(T.Text, Double)]+    toPairs = map (\(k, v) -> (T.pack (show k), fromIntegral v))++    toPairsText :: [(T.Text, Int)] -> [(T.Text, Double)]+    toPairsText = map (Data.Bifunctor.second fromIntegral)++    countValues :: (Ord a) => V.Vector a -> [(a, Int)]     countValues vec = M.toList $ V.foldr' (\x acc -> M.insertWith (+) x 1 acc) M.empty vec -    countValuesUnboxed :: (Ord a, Show a, VU.Unbox a) => VU.Vector a -> [(a, Int)]+    countValuesUnboxed :: (Ord a, VU.Unbox a) => VU.Vector a -> [(a, Int)]     countValuesUnboxed vec = M.toList $ VU.foldr' (\x acc -> M.insertWith (+) x 1 acc) M.empty vec++    countByShow :: (Show a) => [a] -> [(T.Text, Double)]+    countByShow xs =+        map (Data.Bifunctor.bimap T.pack fromIntegral) $+            M.toList $+                L.foldl' (\acc x -> M.insertWith (+) (show x) 1 acc) M.empty xs  groupWithOther :: Int -> [(T.Text, Double)] -> [(T.Text, Double)] groupWithOther n items =
src/DataFrame/Errors.hs view
@@ -108,14 +108,14 @@             ++ "]"  typeMismatchError :: String -> String -> String-typeMismatchError givenType expectedType =+typeMismatchError givenType expType =     red $         red "\n\n[Error]: Type Mismatch"             ++ "\n\tWhile running your code I tried to "             ++ "get a column of type: "             ++ red (show givenType)             ++ " but the column in the dataframe was actually of type: "-            ++ green (show expectedType)+            ++ green (show expType)  emptyDataSetError :: T.Text -> String emptyDataSetError callPoint =
src/DataFrame/Functions.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE IncoherentInstances #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -125,16 +126,16 @@ values are wrapped in 'Just'.  Existing 'Nothing' in optional source columns stays as 'Nothing'. -}-cast :: forall a. (Columnable a) => T.Text -> Expr (Maybe a)-cast name = CastWith name "cast" (either (const Nothing) Just)+cast :: forall a. (Columnable a, Read a) => T.Text -> Expr (Maybe a)+cast colName = CastWith colName "cast" (either (const Nothing) Just)  {- | Lenient coercion that substitutes a default for unconvertible values. Looks up column @name@, coerces its values to @a@, and uses @def@ wherever conversion fails or the source value is 'Nothing'. -}-castWithDefault :: forall a. (Columnable a) => a -> T.Text -> Expr a-castWithDefault def name =-    CastWith name ("castWithDefault:" <> T.pack (show def)) (fromRight def)+castWithDefault :: forall a. (Columnable a, Read a) => a -> T.Text -> Expr a+castWithDefault def colName =+    CastWith colName ("castWithDefault:" <> T.pack (show def)) (fromRight def)  {- | Lenient coercion returning @Either T.Text a@.  Successfully converted values are 'Right'; values that cannot be parsed are kept as 'Left' with@@ -142,27 +143,29 @@ them downstream.  Existing 'Nothing' in optional source columns becomes @Left \"null\"@. -}-castEither :: forall a. (Columnable a) => T.Text -> Expr (Either T.Text a)-castEither name = CastWith name "castEither" (either (Left . T.pack) Right)+castEither ::+    forall a. (Columnable a, Read a) => T.Text -> Expr (Either T.Text a)+castEither colName = CastWith colName "castEither" (either (Left . T.pack) Right)  {- | Lenient coercion for assertedly non-nullable columns. Substitutes @error@ for @Nothing@, so it will crash at evaluation time if any @Nothing@ is actually encountered.  For non-nullable and fully-populated nullable columns no cost is paid. -}-unsafeCast :: forall a. (Columnable a) => T.Text -> Expr a-unsafeCast name =+unsafeCast :: forall a. (Columnable a, Read a) => T.Text -> Expr a+unsafeCast colName =     CastWith-        name+        colName         "unsafeCast"         (fromRight (error "unsafeCast: unexpected Nothing in column"))  castExpr ::-    forall b src. (Columnable b, Columnable src) => Expr src -> Expr (Maybe b)+    forall b src.+    (Columnable b, Columnable src, Read b) => Expr src -> Expr (Maybe b) castExpr = CastExprWith @b @(Maybe b) @src "castExpr" (either (const Nothing) Just)  castExprWithDefault ::-    forall b src. (Columnable b, Columnable src) => b -> Expr src -> Expr b+    forall b src. (Columnable b, Columnable src, Read b) => b -> Expr src -> Expr b castExprWithDefault def =     CastExprWith @b @b @src         ("castExprWithDefault:" <> T.pack (show def))@@ -170,14 +173,14 @@  castExprEither ::     forall b src.-    (Columnable b, Columnable src) => Expr src -> Expr (Either T.Text b)+    (Columnable b, Columnable src, Read b) => Expr src -> Expr (Either T.Text b) castExprEither =     CastExprWith @b @(Either T.Text b) @src         "castExprEither"         (either (Left . T.pack) Right)  unsafeCastExpr ::-    forall b src. (Columnable b, Columnable src) => Expr src -> Expr b+    forall b src. (Columnable b, Columnable src, Read b) => Expr src -> Expr b unsafeCastExpr =     CastExprWith @b @b @src         "unsafeCastExpr"@@ -230,9 +233,21 @@  count :: (Columnable a) => Expr a -> Expr Int count = Agg (MergeAgg "count" (0 :: Int) (\c _ -> c + 1) (+) id)+{-# SPECIALIZE count :: Expr Double -> Expr Int #-}+{-# SPECIALIZE count :: Expr Float -> Expr Int #-}+{-# SPECIALIZE count :: Expr Int -> Expr Int #-}+{-# SPECIALIZE count :: Expr Int8 -> Expr Int #-}+{-# SPECIALIZE count :: Expr Int16 -> Expr Int #-}+{-# SPECIALIZE count :: Expr Int32 -> Expr Int #-}+{-# SPECIALIZE count :: Expr Int64 -> Expr Int #-}+{-# INLINEABLE count #-}  collect :: (Columnable a) => Expr a -> Expr [a] collect = Agg (FoldAgg "collect" (Just []) (flip (:)))+{-# SPECIALIZE collect :: Expr Double -> Expr [Double] #-}+{-# SPECIALIZE collect :: Expr Float -> Expr [Float] #-}+{-# SPECIALIZE collect :: Expr Int -> Expr [Int] #-}+{-# INLINEABLE collect #-}  mode :: (Ord a, Columnable a, Eq a) => Expr a -> Expr a mode =@@ -245,21 +260,58 @@                 . V.foldl' (\m e -> M.insertWith (+) e 1 m) M.empty             )         )+{-# SPECIALIZE mode :: Expr Double -> Expr Double #-}+{-# SPECIALIZE mode :: Expr Float -> Expr Float #-}+{-# SPECIALIZE mode :: Expr Int -> Expr Int #-}+{-# SPECIALIZE mode :: Expr Int8 -> Expr Int8 #-}+{-# SPECIALIZE mode :: Expr Int16 -> Expr Int16 #-}+{-# SPECIALIZE mode :: Expr Int32 -> Expr Int32 #-}+{-# SPECIALIZE mode :: Expr Int64 -> Expr Int64 #-}+{-# INLINEABLE mode #-}  minimum :: (Columnable a, Ord a) => Expr a -> Expr a minimum = Agg (FoldAgg "minimum" Nothing Prelude.min)+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Double -> Expr Double #-}+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Float -> Expr Float #-}+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Int -> Expr Int #-}+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Int8 -> Expr Int8 #-}+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Int16 -> Expr Int16 #-}+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Int32 -> Expr Int32 #-}+{-# SPECIALIZE DataFrame.Functions.minimum :: Expr Int64 -> Expr Int64 #-}+{-# INLINEABLE DataFrame.Functions.minimum #-}  maximum :: (Columnable a, Ord a) => Expr a -> Expr a maximum = Agg (FoldAgg "maximum" Nothing Prelude.max)+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Double -> Expr Double #-}+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Float -> Expr Float #-}+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Int -> Expr Int #-}+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Int8 -> Expr Int8 #-}+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Int16 -> Expr Int16 #-}+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Int32 -> Expr Int32 #-}+{-# SPECIALIZE DataFrame.Functions.maximum :: Expr Int64 -> Expr Int64 #-}+{-# INLINEABLE DataFrame.Functions.maximum #-}  sum :: forall a. (Columnable a, Num a) => Expr a -> Expr a sum = Agg (FoldAgg "sum" Nothing (+)) {-# SPECIALIZE DataFrame.Functions.sum :: Expr Double -> Expr Double #-}+{-# SPECIALIZE DataFrame.Functions.sum :: Expr Float -> Expr Float #-} {-# SPECIALIZE DataFrame.Functions.sum :: Expr Int -> Expr Int #-}+{-# SPECIALIZE DataFrame.Functions.sum :: Expr Int8 -> Expr Int8 #-}+{-# SPECIALIZE DataFrame.Functions.sum :: Expr Int16 -> Expr Int16 #-}+{-# SPECIALIZE DataFrame.Functions.sum :: Expr Int32 -> Expr Int32 #-}+{-# SPECIALIZE DataFrame.Functions.sum :: Expr Int64 -> Expr Int64 #-} {-# INLINEABLE DataFrame.Functions.sum #-}  sumMaybe :: forall a. (Columnable a, Num a) => Expr (Maybe a) -> Expr a sumMaybe = Agg (CollectAgg "sumMaybe" (P.sum . Maybe.catMaybes . V.toList))+{-# SPECIALIZE sumMaybe :: Expr (Maybe Double) -> Expr Double #-}+{-# SPECIALIZE sumMaybe :: Expr (Maybe Float) -> Expr Float #-}+{-# SPECIALIZE sumMaybe :: Expr (Maybe Int) -> Expr Int #-}+{-# SPECIALIZE sumMaybe :: Expr (Maybe Int8) -> Expr Int8 #-}+{-# SPECIALIZE sumMaybe :: Expr (Maybe Int16) -> Expr Int16 #-}+{-# SPECIALIZE sumMaybe :: Expr (Maybe Int32) -> Expr Int32 #-}+{-# SPECIALIZE sumMaybe :: Expr (Maybe Int64) -> Expr Int64 #-}+{-# INLINEABLE sumMaybe #-}  mean :: (Columnable a, Real a) => Expr a -> Expr Double mean =@@ -271,18 +323,58 @@             (\(MeanAcc s1 c1) (MeanAcc s2 c2) -> MeanAcc (s1 + s2) (c1 + c2))             (\(MeanAcc s c) -> if c == 0 then 0 / 0 else s / fromIntegral c)         )+{-# SPECIALIZE mean :: Expr Double -> Expr Double #-}+{-# SPECIALIZE mean :: Expr Float -> Expr Double #-}+{-# SPECIALIZE mean :: Expr Int -> Expr Double #-}+{-# SPECIALIZE mean :: Expr Int8 -> Expr Double #-}+{-# SPECIALIZE mean :: Expr Int16 -> Expr Double #-}+{-# SPECIALIZE mean :: Expr Int32 -> Expr Double #-}+{-# SPECIALIZE mean :: Expr Int64 -> Expr Double #-}+{-# INLINEABLE mean #-}  meanMaybe :: forall a. (Columnable a, Real a) => Expr (Maybe a) -> Expr Double meanMaybe = Agg (CollectAgg "meanMaybe" (mean' . optionalToDoubleVector))+{-# SPECIALIZE meanMaybe :: Expr (Maybe Double) -> Expr Double #-}+{-# SPECIALIZE meanMaybe :: Expr (Maybe Float) -> Expr Double #-}+{-# SPECIALIZE meanMaybe :: Expr (Maybe Int) -> Expr Double #-}+{-# SPECIALIZE meanMaybe :: Expr (Maybe Int8) -> Expr Double #-}+{-# SPECIALIZE meanMaybe :: Expr (Maybe Int16) -> Expr Double #-}+{-# SPECIALIZE meanMaybe :: Expr (Maybe Int32) -> Expr Double #-}+{-# SPECIALIZE meanMaybe :: Expr (Maybe Int64) -> Expr Double #-}+{-# INLINEABLE meanMaybe #-}  variance :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double variance = Agg (CollectAgg "variance" variance')+{-# SPECIALIZE variance :: Expr Double -> Expr Double #-}+{-# SPECIALIZE variance :: Expr Float -> Expr Double #-}+{-# SPECIALIZE variance :: Expr Int -> Expr Double #-}+{-# SPECIALIZE variance :: Expr Int8 -> Expr Double #-}+{-# SPECIALIZE variance :: Expr Int16 -> Expr Double #-}+{-# SPECIALIZE variance :: Expr Int32 -> Expr Double #-}+{-# SPECIALIZE variance :: Expr Int64 -> Expr Double #-}+{-# INLINEABLE variance #-}  median :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double median = Agg (CollectAgg "median" median')+{-# SPECIALIZE median :: Expr Double -> Expr Double #-}+{-# SPECIALIZE median :: Expr Float -> Expr Double #-}+{-# SPECIALIZE median :: Expr Int -> Expr Double #-}+{-# SPECIALIZE median :: Expr Int8 -> Expr Double #-}+{-# SPECIALIZE median :: Expr Int16 -> Expr Double #-}+{-# SPECIALIZE median :: Expr Int32 -> Expr Double #-}+{-# SPECIALIZE median :: Expr Int64 -> Expr Double #-}+{-# INLINEABLE median #-}  medianMaybe :: (Columnable a, Real a) => Expr (Maybe a) -> Expr Double medianMaybe = Agg (CollectAgg "meanMaybe" (median' . optionalToDoubleVector))+{-# SPECIALIZE medianMaybe :: Expr (Maybe Double) -> Expr Double #-}+{-# SPECIALIZE medianMaybe :: Expr (Maybe Float) -> Expr Double #-}+{-# SPECIALIZE medianMaybe :: Expr (Maybe Int) -> Expr Double #-}+{-# SPECIALIZE medianMaybe :: Expr (Maybe Int8) -> Expr Double #-}+{-# SPECIALIZE medianMaybe :: Expr (Maybe Int16) -> Expr Double #-}+{-# SPECIALIZE medianMaybe :: Expr (Maybe Int32) -> Expr Double #-}+{-# SPECIALIZE medianMaybe :: Expr (Maybe Int64) -> Expr Double #-}+{-# INLINEABLE medianMaybe #-}  optionalToDoubleVector :: (Real a) => V.Vector (Maybe a) -> VU.Vector Double optionalToDoubleVector =@@ -301,59 +393,120 @@  stddev :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double stddev = Agg (CollectAgg "stddev" (sqrt . variance'))+{-# SPECIALIZE stddev :: Expr Double -> Expr Double #-}+{-# SPECIALIZE stddev :: Expr Float -> Expr Double #-}+{-# SPECIALIZE stddev :: Expr Int -> Expr Double #-}+{-# SPECIALIZE stddev :: Expr Int8 -> Expr Double #-}+{-# SPECIALIZE stddev :: Expr Int16 -> Expr Double #-}+{-# SPECIALIZE stddev :: Expr Int32 -> Expr Double #-}+{-# SPECIALIZE stddev :: Expr Int64 -> Expr Double #-}+{-# INLINEABLE stddev #-}  stddevMaybe :: forall a. (Columnable a, Real a) => Expr (Maybe a) -> Expr Double stddevMaybe = Agg (CollectAgg "stddevMaybe" (sqrt . variance' . optionalToDoubleVector))+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Double) -> Expr Double #-}+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Float) -> Expr Double #-}+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Int) -> Expr Double #-}+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Int8) -> Expr Double #-}+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Int16) -> Expr Double #-}+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Int32) -> Expr Double #-}+{-# SPECIALIZE stddevMaybe :: Expr (Maybe Int64) -> Expr Double #-}+{-# INLINEABLE stddevMaybe #-}  zScore :: Expr Double -> Expr Double zScore c = (c - mean c) / stddev c  pow :: (Columnable a, Num a) => Expr a -> Int -> Expr a pow expr i = lift2Decorated (^) "pow" (Just "^") True 8 expr (Lit i)+{-# SPECIALIZE pow :: Expr Double -> Int -> Expr Double #-}+{-# SPECIALIZE pow :: Expr Float -> Int -> Expr Float #-}+{-# SPECIALIZE pow :: Expr Int -> Int -> Expr Int #-}+{-# INLINEABLE pow #-}  relu :: (Columnable a, Num a, Ord a) => Expr a -> Expr a relu = liftDecorated (Prelude.max 0) "relu" Nothing+{-# SPECIALIZE relu :: Expr Double -> Expr Double #-}+{-# SPECIALIZE relu :: Expr Float -> Expr Float #-}+{-# SPECIALIZE relu :: Expr Int -> Expr Int #-}+{-# INLINEABLE relu #-}  min :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr a min = lift2Decorated Prelude.min "min" Nothing True 1+{-# SPECIALIZE DataFrame.Functions.min ::+    Expr Double -> Expr Double -> Expr Double+    #-}+{-# SPECIALIZE DataFrame.Functions.min ::+    Expr Float -> Expr Float -> Expr Float+    #-}+{-# SPECIALIZE DataFrame.Functions.min :: Expr Int -> Expr Int -> Expr Int #-}+{-# INLINEABLE DataFrame.Functions.min #-}  max :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr a max = lift2Decorated Prelude.max "max" Nothing True 1+{-# SPECIALIZE DataFrame.Functions.max ::+    Expr Double -> Expr Double -> Expr Double+    #-}+{-# SPECIALIZE DataFrame.Functions.max ::+    Expr Float -> Expr Float -> Expr Float+    #-}+{-# SPECIALIZE DataFrame.Functions.max :: Expr Int -> Expr Int -> Expr Int #-}+{-# INLINEABLE DataFrame.Functions.max #-}  reduce ::     forall a b.     (Columnable a, Columnable b) => Expr b -> a -> (a -> b -> a) -> Expr a reduce expr start f = Agg (FoldAgg "foldUdf" (Just start) f) expr+{-# INLINEABLE reduce #-}  toMaybe :: (Columnable a) => Expr a -> Expr (Maybe a) toMaybe = liftDecorated Just "toMaybe" Nothing+{-# SPECIALIZE toMaybe :: Expr Double -> Expr (Maybe Double) #-}+{-# SPECIALIZE toMaybe :: Expr Float -> Expr (Maybe Float) #-}+{-# SPECIALIZE toMaybe :: Expr Int -> Expr (Maybe Int) #-}+{-# INLINEABLE toMaybe #-}  fromMaybe :: (Columnable a) => a -> Expr (Maybe a) -> Expr a fromMaybe d = liftDecorated (Maybe.fromMaybe d) "fromMaybe" Nothing+{-# SPECIALIZE fromMaybe :: Double -> Expr (Maybe Double) -> Expr Double #-}+{-# SPECIALIZE fromMaybe :: Float -> Expr (Maybe Float) -> Expr Float #-}+{-# SPECIALIZE fromMaybe :: Int -> Expr (Maybe Int) -> Expr Int #-}+{-# INLINEABLE fromMaybe #-}  isJust :: (Columnable a) => Expr (Maybe a) -> Expr Bool isJust = liftDecorated Maybe.isJust "isJust" Nothing+{-# SPECIALIZE isJust :: Expr (Maybe Double) -> Expr Bool #-}+{-# SPECIALIZE isJust :: Expr (Maybe Int) -> Expr Bool #-}+{-# INLINEABLE isJust #-}  isNothing :: (Columnable a) => Expr (Maybe a) -> Expr Bool isNothing = liftDecorated Maybe.isNothing "isNothing" Nothing+{-# SPECIALIZE isNothing :: Expr (Maybe Double) -> Expr Bool #-}+{-# SPECIALIZE isNothing :: Expr (Maybe Int) -> Expr Bool #-}+{-# INLINEABLE isNothing #-}  fromJust :: (Columnable a) => Expr (Maybe a) -> Expr a fromJust = liftDecorated Maybe.fromJust "fromJust" Nothing+{-# SPECIALIZE fromJust :: Expr (Maybe Double) -> Expr Double #-}+{-# SPECIALIZE fromJust :: Expr (Maybe Int) -> Expr Int #-}+{-# INLINEABLE fromJust #-}  whenPresent ::     forall a b.     (Columnable a, Columnable b) => (a -> b) -> Expr (Maybe a) -> Expr (Maybe b) whenPresent f = liftDecorated (fmap f) "whenPresent" Nothing+{-# INLINEABLE whenPresent #-}  whenBothPresent ::     forall a b c.     (Columnable a, Columnable b, Columnable c) =>     (a -> b -> c) -> Expr (Maybe a) -> Expr (Maybe b) -> Expr (Maybe c) whenBothPresent f = lift2Decorated (\l r -> f <$> l <*> r) "whenBothPresent" Nothing False 0+{-# INLINEABLE whenBothPresent #-}  recode ::     forall a b.-    (Columnable a, Columnable b) => [(a, b)] -> Expr a -> Expr (Maybe b)+    (Columnable a, Columnable b, Show (a, b)) =>+    [(a, b)] -> Expr a -> Expr (Maybe b) recode mapping =     Unary         ( MkUnaryOp@@ -367,12 +520,12 @@     forall a b.     (Columnable a, Columnable b) =>     Expr b -> [(Expr a -> Expr Bool, b)] -> Expr a -> Expr b-recodeWithCondition fallback [] value = fallback-recodeWithCondition fallback ((cond, value) : rest) expr = ifThenElse (cond expr) (lit value) (recodeWithCondition fallback rest expr)+recodeWithCondition fallback [] _val = fallback+recodeWithCondition fallback ((cond, val) : rest) expr = ifThenElse (cond expr) (lit val) (recodeWithCondition fallback rest expr)  recodeWithDefault ::     forall a b.-    (Columnable a, Columnable b) => b -> [(a, b)] -> Expr a -> Expr b+    (Columnable a, Columnable b, Show (a, b)) => b -> [(a, b)] -> Expr a -> Expr b recodeWithDefault d mapping =     Unary         ( MkUnaryOp@@ -508,7 +661,7 @@             | otherwise -> do                 m <- lookupTypeName t                 case m of-                    Just name -> pure (ConT name)+                    Just tyName -> pure (ConT tyName)                     Nothing -> fail $ "Unsupported type: " ++ t0 typeFromString [tycon, t1] = AppT <$> typeFromString [tycon] <*> typeFromString [t1] typeFromString [tycon, t1, t2] =@@ -568,14 +721,14 @@      in fromNamedColumns (map (schemaElemToColumn nullableCols) leafElems)  schemaElemToColumn :: S.Set T.Text -> SchemaElement -> (T.Text, Column)-schemaElemToColumn nullableCols elem =-    let name = elementName elem-        isNull = name `S.member` nullableCols-        col =+schemaElemToColumn nullableCols element =+    let colName = elementName element+        isNull = colName `S.member` nullableCols+        column =             if isNull-                then emptyNullableColumnForType (elementType elem)-                else emptyColumnForType (elementType elem)-     in (name, col)+                then emptyNullableColumnForType (elementType element)+                else emptyColumnForType (elementType element)+     in (colName, column)  emptyColumnForType :: TType -> Column emptyColumnForType = \case@@ -621,7 +774,7 @@         types = map (columnTypeString . (`unsafeGetColumn` df)) names         specs =             zipWith-                ( \name type_ -> (name, maybe "" (sanitize . (<> "_")) prefix <> sanitize name, type_)+                ( \colName type_ -> (colName, maybe "" (sanitize . (<> "_")) prefix <> sanitize colName, type_)                 )                 names                 types
src/DataFrame/IO/CSV.hs view
@@ -13,7 +13,6 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Lazy as BL-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@@ -28,11 +27,11 @@ import qualified Data.Csv.Streaming as CsvStream  import Control.DeepSeq+import Control.Exception (SomeException, catch) import Control.Monad import Data.Char import qualified Data.Csv as Csv import Data.Either-import Data.Function (on) import Data.Functor import Data.IORef import Data.Maybe@@ -548,6 +547,17 @@     DataFrame ->     IO () writeSeparated c filepath df = TIO.writeFile filepath (toSeparated c df)++-- | Parse a CSV string into a DataFrame using default options.+fromCsv :: String -> IO (Either String DataFrame)+fromCsv s = do+    let bs = BL.fromStrict (TE.encodeUtf8 (T.pack s))+    (Right <$> decodeSeparated defaultReadOptions bs)+        `catch` (\(e :: SomeException) -> pure (Left (show e)))++-- | Parse a lazy 'ByteString' containing CSV data into a DataFrame using default options.+fromCsvBytes :: BL.ByteString -> IO DataFrame+fromCsvBytes = decodeSeparated defaultReadOptions  stripQuotes :: T.Text -> T.Text stripQuotes txt =
src/DataFrame/IO/Parquet.hs view
@@ -64,6 +64,7 @@ 1. predicate filtering 2. column projection 3. row range+4. safe column promotion  Column selection for @selectedColumns@ uses leaf column names only. -}@@ -198,7 +199,7 @@     let schemaElements = schema fileMetadata     let sNodes = parseAll (drop 1 schemaElements)     let getTypeLength :: [String] -> Maybe Int32-        getTypeLength path = findTypeLength schemaElements path 0+        getTypeLength colPath = findTypeLength schemaElements colPath 0           where             findTypeLength [] _ _ = Nothing             findTypeLength (s : ss) targetPath depth
src/DataFrame/IO/Parquet/Binary.hs view
@@ -26,37 +26,37 @@     -}     loop :: BS.ByteString -> Word64 -> Int -> Int -> (Word64, BS.ByteString)     loop bs result _ 10 = (result, bs)-    loop xs result shift i = case BS.uncons xs of+    loop xs' result shiftAmt i = case BS.uncons xs' of         Nothing -> error "readUVarInt: not enough input bytes"-        Just (b, bs) ->+        Just (b, bs') ->             if b < 0x80-                then (result .|. (fromIntegral b `shiftL` shift), bs)+                then (result .|. (fromIntegral b `shiftL` shiftAmt), bs')                 else                     let payloadBits = fromIntegral (b .&. 0x7f) :: Word64-                     in loop bs (result .|. (payloadBits `shiftL` shift)) (shift + 7) (i + 1)+                     in loop bs' (result .|. (payloadBits `shiftL` shiftAmt)) (shiftAmt + 7) (i + 1)  readVarIntFromBytes :: (Integral a) => BS.ByteString -> (a, BS.ByteString)-readVarIntFromBytes bs = (fromIntegral n, rem)+readVarIntFromBytes bs = (fromIntegral n, remainder)   where-    (n, rem) = loop 0 0 bs-    loop shift result bs = case BS.uncons bs of+    (n, remainder) = loop 0 0 bs+    loop shiftAmt result bs' = case BS.uncons bs' of         Nothing -> (result, BS.empty)         Just (x, xs) ->-            let res = result .|. (fromIntegral (x .&. 0x7f) :: Integer) `shiftL` shift-             in if x .&. 0x80 /= 0x80 then (res, xs) else loop (shift + 7) res xs+            let res = result .|. (fromIntegral (x .&. 0x7f) :: Integer) `shiftL` shiftAmt+             in if x .&. 0x80 /= 0x80 then (res, xs) else loop (shiftAmt + 7) res xs  readIntFromBytes :: (Integral a) => BS.ByteString -> (a, BS.ByteString) readIntFromBytes bs =-    let (n, rem) = readVarIntFromBytes bs+    let (n, remainder) = readVarIntFromBytes bs         u = fromIntegral n :: Word32-     in (fromIntegral $ (fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), rem)+     in (fromIntegral $ (fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), remainder)  readInt32FromBytes :: BS.ByteString -> (Int32, BS.ByteString) readInt32FromBytes bs =-    let (n', rem) = readVarIntFromBytes @Int64 bs+    let (n', remainder) = readVarIntFromBytes @Int64 bs         n = fromIntegral n' :: Int32         u = fromIntegral n :: Word32-     in ((fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), rem)+     in ((fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), remainder)  readAndAdvance :: IORef Int -> BS.ByteString -> IO Word8 readAndAdvance bufferPos buffer = do@@ -68,12 +68,12 @@ readVarIntFromBuffer :: (Integral a) => BS.ByteString -> IORef Int -> IO a readVarIntFromBuffer buf bufferPos = do     start <- readIORef bufferPos-    let loop i shift result = do+    let loop i shiftAmt result = do             b <- readAndAdvance bufferPos buf-            let res = result .|. (fromIntegral (b .&. 0x7f) :: Integer) `shiftL` shift+            let res = result .|. (fromIntegral (b .&. 0x7f) :: Integer) `shiftL` shiftAmt             if b .&. 0x80 /= 0x80                 then return res-                else loop (i + 1) (shift + 7) res+                else loop (i + 1) (shiftAmt + 7) res     fromIntegral <$> loop start 0 0  readIntFromBuffer :: (Integral a) => BS.ByteString -> IORef Int -> IO a@@ -96,9 +96,9 @@ readByteStringFromBytes :: BS.ByteString -> (BS.ByteString, BS.ByteString) readByteStringFromBytes xs =     let-        (size, rem) = readVarIntFromBytes @Int xs+        (size, remainder) = readVarIntFromBytes @Int xs      in-        BS.splitAt size rem+        BS.splitAt size remainder  readByteString :: BS.ByteString -> IORef Int -> IO BS.ByteString readByteString buf pos = do
src/DataFrame/IO/Parquet/Page.hs view
@@ -38,9 +38,9 @@     if BS.null columnBytes         then pure (Nothing, BS.empty)         else do-            let (hdr, rem) = readPageHeader emptyPageHeader columnBytes 0+            let (hdr, remainder) = readPageHeader emptyPageHeader columnBytes 0 -            let compressed = BS.take (fromIntegral $ compressedPageSize hdr) rem+            let compressed = BS.take (fromIntegral $ compressedPageSize hdr) remainder              fullData <- case c of                 ZSTD -> do@@ -65,7 +65,7 @@                 other -> error ("Unsupported compression type: " ++ show other)             pure                 ( Just $ Page hdr fullData-                , BS.drop (fromIntegral $ compressedPageSize hdr) rem+                , BS.drop (fromIntegral $ compressedPageSize hdr) remainder                 )  readPageHeader ::@@ -79,46 +79,46 @@              in                 case fieldContents of                     Nothing -> (hdr, BS.drop 1 xs)-                    Just (rem, elemType, identifier) -> case identifier of+                    Just (remainder, elemType, identifier) -> case identifier of                         1 ->                             let-                                (pType, rem') = readInt32FromBytes rem+                                (pType, remainder') = readInt32FromBytes remainder                              in-                                readPageHeader (hdr{pageHeaderPageType = pageTypeFromInt pType}) rem' identifier+                                readPageHeader (hdr{pageHeaderPageType = pageTypeFromInt pType}) remainder' identifier                         2 ->                             let-                                (uncompressedPageSize, rem') = readInt32FromBytes rem+                                (uncompressedPageSize, remainder') = readInt32FromBytes remainder                              in                                 readPageHeader                                     (hdr{uncompressedPageSize = uncompressedPageSize})-                                    rem'+                                    remainder'                                     identifier                         3 ->                             let-                                (compressedPageSize, rem') = readInt32FromBytes rem+                                (compressedPageSize, remainder') = readInt32FromBytes remainder                              in-                                readPageHeader (hdr{compressedPageSize = compressedPageSize}) rem' identifier+                                readPageHeader (hdr{compressedPageSize = compressedPageSize}) remainder' identifier                         4 ->                             let-                                (crc, rem') = readInt32FromBytes rem+                                (crc, remainder') = readInt32FromBytes remainder                              in-                                readPageHeader (hdr{pageHeaderCrcChecksum = crc}) rem' identifier+                                readPageHeader (hdr{pageHeaderCrcChecksum = crc}) remainder' identifier                         5 ->                             let-                                (dataPageHeader, rem') = readPageTypeHeader emptyDataPageHeader rem 0+                                (dataPageHeader, remainder') = readPageTypeHeader emptyDataPageHeader remainder 0                              in-                                readPageHeader (hdr{pageTypeHeader = dataPageHeader}) rem' identifier+                                readPageHeader (hdr{pageTypeHeader = dataPageHeader}) remainder' identifier                         6 -> error "Index page header not supported"                         7 ->                             let-                                (dictionaryPageHeader, rem') = readPageTypeHeader emptyDictionaryPageHeader rem 0+                                (dictionaryPageHeader, remainder') = readPageTypeHeader emptyDictionaryPageHeader remainder 0                              in-                                readPageHeader (hdr{pageTypeHeader = dictionaryPageHeader}) rem' identifier+                                readPageHeader (hdr{pageTypeHeader = dictionaryPageHeader}) remainder' identifier                         8 ->                             let-                                (dataPageHeaderV2, rem') = readPageTypeHeader emptyDataPageHeaderV2 rem 0+                                (dataPageHeaderV2, remainder') = readPageTypeHeader emptyDataPageHeaderV2 remainder 0                              in-                                readPageHeader (hdr{pageTypeHeader = dataPageHeaderV2}) rem' identifier+                                readPageHeader (hdr{pageTypeHeader = dataPageHeaderV2}) remainder' identifier                         n -> error $ "Unknown page header field " ++ show n  readPageTypeHeader ::@@ -134,26 +134,26 @@              in                 case fieldContents of                     Nothing -> (hdr, BS.drop 1 xs)-                    Just (rem, elemType, identifier) -> case identifier of+                    Just (remainder, elemType, identifier) -> case identifier of                         1 ->                             let-                                (numValues, rem') = readInt32FromBytes rem+                                (numValues, remainder') = readInt32FromBytes remainder                              in                                 readPageTypeHeader                                     (hdr{dictionaryPageHeaderNumValues = numValues})-                                    rem'+                                    remainder'                                     identifier                         2 ->                             let-                                (enc, rem') = readInt32FromBytes rem+                                (enc, remainder') = readInt32FromBytes remainder                              in                                 readPageTypeHeader                                     (hdr{dictionaryPageHeaderEncoding = parquetEncodingFromInt enc})-                                    rem'+                                    remainder'                                     identifier                         3 ->                             let-                                isSorted = fromMaybe (error "readPageTypeHeader: not enough bytes") (rem BS.!? 0)+                                isSorted = fromMaybe (error "readPageTypeHeader: not enough bytes") (remainder BS.!? 0)                              in                                 readPageTypeHeader                                     (hdr{dictionaryPageIsSorted = isSorted == compactBooleanTrue})@@ -163,7 +163,7 @@                                     -- But in other cases you do.                                     -- This might become a problem later but in the mean                                     -- time I'm not dropping (this assumes this is the common case).-                                    rem+                                    remainder                                     identifier                         n ->                             error $ "readPageTypeHeader: unsupported identifier " ++ show n@@ -176,41 +176,41 @@              in                 case fieldContents of                     Nothing -> (hdr, BS.drop 1 xs)-                    Just (rem, elemType, identifier) -> case identifier of+                    Just (remainder, elemType, identifier) -> case identifier of                         1 ->                             let-                                (numValues, rem') = readInt32FromBytes rem+                                (numValues, remainder') = readInt32FromBytes remainder                              in-                                readPageTypeHeader (hdr{dataPageHeaderNumValues = numValues}) rem' identifier+                                readPageTypeHeader (hdr{dataPageHeaderNumValues = numValues}) remainder' identifier                         2 ->                             let-                                (enc, rem') = readInt32FromBytes rem+                                (enc, remainder') = readInt32FromBytes remainder                              in                                 readPageTypeHeader                                     (hdr{dataPageHeaderEncoding = parquetEncodingFromInt enc})-                                    rem'+                                    remainder'                                     identifier                         3 ->                             let-                                (enc, rem') = readInt32FromBytes rem+                                (enc, remainder') = readInt32FromBytes remainder                              in                                 readPageTypeHeader                                     (hdr{definitionLevelEncoding = parquetEncodingFromInt enc})-                                    rem'+                                    remainder'                                     identifier                         4 ->                             let-                                (enc, rem') = readInt32FromBytes rem+                                (enc, remainder') = readInt32FromBytes remainder                              in                                 readPageTypeHeader                                     (hdr{repetitionLevelEncoding = parquetEncodingFromInt enc})-                                    rem'+                                    remainder'                                     identifier                         5 ->                             let-                                (stats, rem') = readStatisticsFromBytes emptyColumnStatistics rem 0+                                (stats, remainder') = readStatisticsFromBytes emptyColumnStatistics remainder 0                              in-                                readPageTypeHeader (hdr{dataPageHeaderStatistics = stats}) rem' identifier+                                readPageTypeHeader (hdr{dataPageHeaderStatistics = stats}) remainder' identifier                         n -> error $ show n readPageTypeHeader hdr@(DataPageHeaderV2{..}) xs lastFieldId =     if BS.null xs@@ -221,57 +221,57 @@              in                 case fieldContents of                     Nothing -> (hdr, BS.drop 1 xs)-                    Just (rem, elemType, identifier) -> case identifier of+                    Just (remainder, elemType, identifier) -> case identifier of                         1 ->                             let-                                (numValues, rem') = readInt32FromBytes rem+                                (numValues, remainder') = readInt32FromBytes remainder                              in-                                readPageTypeHeader (hdr{dataPageHeaderV2NumValues = numValues}) rem' identifier+                                readPageTypeHeader (hdr{dataPageHeaderV2NumValues = numValues}) remainder' identifier                         2 ->                             let-                                (numNulls, rem') = readInt32FromBytes rem+                                (numNulls, remainder') = readInt32FromBytes remainder                              in-                                readPageTypeHeader (hdr{dataPageHeaderV2NumNulls = numNulls}) rem' identifier+                                readPageTypeHeader (hdr{dataPageHeaderV2NumNulls = numNulls}) remainder' identifier                         3 ->                             let-                                (numRows, rem') = readInt32FromBytes rem+                                (numRows, remainder') = readInt32FromBytes remainder                              in-                                readPageTypeHeader (hdr{dataPageHeaderV2NumRows = numRows}) rem' identifier+                                readPageTypeHeader (hdr{dataPageHeaderV2NumRows = numRows}) remainder' identifier                         4 ->                             let-                                (enc, rem') = readInt32FromBytes rem+                                (enc, remainder') = readInt32FromBytes remainder                              in                                 readPageTypeHeader                                     (hdr{dataPageHeaderV2Encoding = parquetEncodingFromInt enc})-                                    rem'+                                    remainder'                                     identifier                         5 ->                             let-                                (n, rem') = readInt32FromBytes rem+                                (n, remainder') = readInt32FromBytes remainder                              in-                                readPageTypeHeader (hdr{definitionLevelByteLength = n}) rem' identifier+                                readPageTypeHeader (hdr{definitionLevelByteLength = n}) remainder' identifier                         6 ->                             let-                                (n, rem') = readInt32FromBytes rem+                                (n, remainder') = readInt32FromBytes remainder                              in-                                readPageTypeHeader (hdr{repetitionLevelByteLength = n}) rem' identifier+                                readPageTypeHeader (hdr{repetitionLevelByteLength = n}) remainder' identifier                         7 ->                             let-                                (isCompressed, rem') = case BS.uncons rem of+                                (isCompressed, remainder') = case BS.uncons remainder of                                     Just (b, bytes) -> ((b .&. 0x0f) == compactBooleanTrue, bytes)                                     Nothing -> (True, BS.empty)                              in                                 readPageTypeHeader                                     (hdr{dataPageHeaderV2IsCompressed = isCompressed})-                                    rem'+                                    remainder'                                     identifier                         8 ->                             let-                                (stats, rem') = readStatisticsFromBytes emptyColumnStatistics rem 0+                                (stats, remainder') = readStatisticsFromBytes emptyColumnStatistics remainder 0                              in                                 readPageTypeHeader                                     (hdr{dataPageHeaderV2Statistics = stats})-                                    rem'+                                    remainder'                                     identifier                         n -> error $ show n @@ -283,12 +283,12 @@             then Nothing             else                 let modifier = fromIntegral ((x .&. 0xf0) `shiftR` 4) :: Int16-                    (identifier, rem) =+                    (identifier, remainder) =                         if modifier == 0                             then readIntFromBytes @Int16 xs                             else (lastFieldId + modifier, xs)                     elemType = toTType (x .&. 0x0f)-                 in Just (rem, elemType, identifier)+                 in Just (remainder, elemType, identifier)  readAllPages :: CompressionCodec -> BS.ByteString -> IO [Page] readAllPages codec bytes = go bytes []@@ -297,10 +297,10 @@         if BS.null bs             then return (reverse acc)             else do-                (maybePage, remaining) <- readPage codec bs+                (maybePage, remainderaining) <- readPage codec bs                 case maybePage of                     Nothing -> return (reverse acc)-                    Just page -> go remaining (page : acc)+                    Just page -> go remainderaining (page : acc)  -- | Read n Int32 values directly into an unboxed vector (no intermediate list). readNInt32Vec :: Int -> BS.ByteString -> VU.Vector Int32@@ -394,53 +394,53 @@      in         case fieldContents of             Nothing -> (cs, BS.drop 1 xs)-            Just (rem, elemType, identifier) -> case identifier of+            Just (remainder, elemType, identifier) -> case identifier of                 1 ->                     let-                        (maxInBytes, rem') = readByteStringFromBytes rem+                        (maxInBytes, remainder') = readByteStringFromBytes remainder                      in-                        readStatisticsFromBytes (cs{columnMax = maxInBytes}) rem' identifier+                        readStatisticsFromBytes (cs{columnMax = maxInBytes}) remainder' identifier                 2 ->                     let-                        (minInBytes, rem') = readByteStringFromBytes rem+                        (minInBytes, remainder') = readByteStringFromBytes remainder                      in-                        readStatisticsFromBytes (cs{columnMin = minInBytes}) rem' identifier+                        readStatisticsFromBytes (cs{columnMin = minInBytes}) remainder' identifier                 3 ->                     let-                        (nullCount, rem') = readIntFromBytes @Int64 rem+                        (nullCount, remainder') = readIntFromBytes @Int64 remainder                      in-                        readStatisticsFromBytes (cs{columnNullCount = nullCount}) rem' identifier+                        readStatisticsFromBytes (cs{columnNullCount = nullCount}) remainder' identifier                 4 ->                     let-                        (distinctCount, rem') = readIntFromBytes @Int64 rem+                        (distinctCount, remainder') = readIntFromBytes @Int64 remainder                      in-                        readStatisticsFromBytes (cs{columnDistictCount = distinctCount}) rem' identifier+                        readStatisticsFromBytes (cs{columnDistictCount = distinctCount}) remainder' identifier                 5 ->                     let-                        (maxInBytes, rem') = readByteStringFromBytes rem+                        (maxInBytes, remainder') = readByteStringFromBytes remainder                      in-                        readStatisticsFromBytes (cs{columnMaxValue = maxInBytes}) rem' identifier+                        readStatisticsFromBytes (cs{columnMaxValue = maxInBytes}) remainder' identifier                 6 ->                     let-                        (minInBytes, rem') = readByteStringFromBytes rem+                        (minInBytes, remainder') = readByteStringFromBytes remainder                      in-                        readStatisticsFromBytes (cs{columnMinValue = minInBytes}) rem' identifier+                        readStatisticsFromBytes (cs{columnMinValue = minInBytes}) remainder' identifier                 7 ->-                    case BS.uncons rem of+                    case BS.uncons remainder of                         Nothing ->                             error "readStatisticsFromBytes: not enough bytes"-                        Just (isMaxValueExact, rem') ->+                        Just (isMaxValueExact, remainder') ->                             readStatisticsFromBytes                                 (cs{isColumnMaxValueExact = isMaxValueExact == compactBooleanTrue})-                                rem'+                                remainder'                                 identifier                 8 ->-                    case BS.uncons rem of+                    case BS.uncons remainder of                         Nothing ->                             error "readStatisticsFromBytes: not enough bytes"-                        Just (isMinValueExact, rem') ->+                        Just (isMinValueExact, remainder') ->                             readStatisticsFromBytes                                 (cs{isColumnMinValueExact = isMinValueExact == compactBooleanTrue})-                                rem'+                                remainder'                                 identifier                 n -> error $ show n
src/DataFrame/IO/Parquet/Thrift.hs view
@@ -362,9 +362,9 @@         Nothing -> return metadata         Just (elemType, identifier) -> case identifier of             1 -> do-                version <- readIntFromBuffer @Int32 metaDataBuf bufferPos+                parsedVersion <- readIntFromBuffer @Int32 metaDataBuf bufferPos                 readFileMetaData-                    (metadata{version = version})+                    (metadata{version = parsedVersion})                     metaDataBuf                     bufferPos                     identifier@@ -385,9 +385,9 @@                     bufferPos                     identifier             3 -> do-                numRows <- readIntFromBuffer @Int64 metaDataBuf bufferPos+                parsedNumRows <- readIntFromBuffer @Int64 metaDataBuf bufferPos                 readFileMetaData-                    (metadata{numRows = fromIntegral numRows})+                    (metadata{numRows = fromIntegral parsedNumRows})                     metaDataBuf                     bufferPos                     identifier@@ -400,10 +400,10 @@                  -- TODO actually check elemType agrees (also for all the other underscored _elemType in this module)                 let _elemType = toTType sizeAndType-                rowGroups <-+                parsedRowGroups <-                     replicateM listSize (readRowGroup emptyRowGroup metaDataBuf bufferPos 0)                 readFileMetaData-                    (metadata{rowGroups = rowGroups})+                    (metadata{rowGroups = parsedRowGroups})                     metaDataBuf                     bufferPos                     identifier@@ -415,17 +415,17 @@                         else return $ fromIntegral ((sizeAndType `shiftR` 4) .&. 0x0f)                  let _elemType = toTType sizeAndType-                keyValueMetadata <-+                parsedKeyValueMetadata <-                     replicateM listSize (readKeyValue emptyKeyValue metaDataBuf bufferPos 0)                 readFileMetaData-                    (metadata{keyValueMetadata = keyValueMetadata})+                    (metadata{keyValueMetadata = parsedKeyValueMetadata})                     metaDataBuf                     bufferPos                     identifier             6 -> do-                createdBy <- readString metaDataBuf bufferPos+                parsedCreatedBy <- readString metaDataBuf bufferPos                 readFileMetaData-                    (metadata{createdBy = Just createdBy})+                    (metadata{createdBy = Just parsedCreatedBy})                     metaDataBuf                     bufferPos                     identifier@@ -437,23 +437,23 @@                         else return $ fromIntegral ((sizeAndType `shiftR` 4) .&. 0x0f)                  let _elemType = toTType sizeAndType-                columnOrders <- replicateM listSize (readColumnOrder metaDataBuf bufferPos 0)+                parsedColumnOrders <- replicateM listSize (readColumnOrder metaDataBuf bufferPos 0)                 readFileMetaData-                    (metadata{columnOrders = columnOrders})+                    (metadata{columnOrders = parsedColumnOrders})                     metaDataBuf                     bufferPos                     identifier             8 -> do-                encryptionAlgorithm <- readEncryptionAlgorithm metaDataBuf bufferPos 0+                parsedEncryptionAlgorithm <- readEncryptionAlgorithm metaDataBuf bufferPos 0                 readFileMetaData-                    (metadata{encryptionAlgorithm = encryptionAlgorithm})+                    (metadata{encryptionAlgorithm = parsedEncryptionAlgorithm})                     metaDataBuf                     bufferPos                     identifier             9 -> do-                footerSigningKeyMetadata <- readByteString metaDataBuf bufferPos+                parsedFooterSigningKeyMetadata <- readByteString metaDataBuf bufferPos                 readFileMetaData-                    (metadata{footerSigningKeyMetadata = footerSigningKeyMetadata})+                    (metadata{footerSigningKeyMetadata = parsedFooterSigningKeyMetadata})                     metaDataBuf                     bufferPos                     identifier@@ -478,9 +478,9 @@                     pos                     identifier             2 -> do-                typeLength <- readInt32FromBuffer buf pos+                parsedTypeLength <- readInt32FromBuffer buf pos                 readSchemaElement-                    (schemaElement{typeLength = typeLength})+                    (schemaElement{typeLength = parsedTypeLength})                     buf                     pos                     identifier@@ -503,40 +503,40 @@                             pos                             identifier             5 -> do-                numChildren <- readInt32FromBuffer buf pos+                parsedNumChildren <- readInt32FromBuffer buf pos                 readSchemaElement-                    (schemaElement{numChildren = numChildren})+                    (schemaElement{numChildren = parsedNumChildren})                     buf                     pos                     identifier             6 -> do-                convertedType <- readInt32FromBuffer buf pos+                parsedConvertedType <- readInt32FromBuffer buf pos                 readSchemaElement-                    (schemaElement{convertedType = convertedType})+                    (schemaElement{convertedType = parsedConvertedType})                     buf                     pos                     identifier             7 -> do-                scale <- readInt32FromBuffer buf pos-                readSchemaElement (schemaElement{scale = scale}) buf pos identifier+                parsedScale <- readInt32FromBuffer buf pos+                readSchemaElement (schemaElement{scale = parsedScale}) buf pos identifier             8 -> do-                precision <- readInt32FromBuffer buf pos+                parsedPrecision <- readInt32FromBuffer buf pos                 readSchemaElement-                    (schemaElement{precision = precision})+                    (schemaElement{precision = parsedPrecision})                     buf                     pos                     identifier             9 -> do-                fieldId <- readInt32FromBuffer buf pos+                parsedFieldId <- readInt32FromBuffer buf pos                 readSchemaElement-                    (schemaElement{fieldId = fieldId})+                    (schemaElement{fieldId = parsedFieldId})                     buf                     pos                     identifier             10 -> do-                logicalType <- readLogicalType LOGICAL_TYPE_UNKNOWN buf pos 0+                parsedLogicalType <- readLogicalType LOGICAL_TYPE_UNKNOWN buf pos 0                 readSchemaElement-                    (schemaElement{logicalType = logicalType})+                    (schemaElement{logicalType = parsedLogicalType})                     buf                     pos                     identifier@@ -577,8 +577,8 @@                     pos                     identifier             7 -> do-                ordinal <- readIntFromBuffer @Int16 buf pos-                readRowGroup (r{ordinal = ordinal}) buf pos identifier+                parsedOrdinal <- readIntFromBuffer @Int16 buf pos+                readRowGroup (r{ordinal = parsedOrdinal}) buf pos identifier             _ -> error $ "Unknown row group field: " ++ show identifier  readColumnChunk ::@@ -598,9 +598,9 @@                     pos                     identifier             2 -> do-                columnChunkMetadataFileOffset <- readIntFromBuffer @Int64 buf pos+                parsedMetadataFileOffset <- readIntFromBuffer @Int64 buf pos                 readColumnChunk-                    (c{columnChunkMetadataFileOffset = columnChunkMetadataFileOffset})+                    (c{columnChunkMetadataFileOffset = parsedMetadataFileOffset})                     buf                     pos                     identifier@@ -626,16 +626,16 @@                     pos                     identifier             6 -> do-                columnChunkColumnIndexOffset <- readIntFromBuffer @Int64 buf pos+                parsedColumnIndexOffset <- readIntFromBuffer @Int64 buf pos                 readColumnChunk-                    (c{columnChunkColumnIndexOffset = columnChunkColumnIndexOffset})+                    (c{columnChunkColumnIndexOffset = parsedColumnIndexOffset})                     buf                     pos                     identifier             7 -> do-                columnChunkColumnIndexLength <- readInt32FromBuffer buf pos+                parsedColumnIndexLength <- readInt32FromBuffer buf pos                 readColumnChunk-                    (c{columnChunkColumnIndexLength = columnChunkColumnIndexLength})+                    (c{columnChunkColumnIndexLength = parsedColumnIndexLength})                     buf                     pos                     identifier@@ -682,16 +682,16 @@                 numValues <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata (cm{columnNumValues = numValues}) buf pos identifier             6 -> do-                columnTotalUncompressedSize <- readIntFromBuffer @Int64 buf pos+                parsedTotalUncompressedSize <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata-                    (cm{columnTotalUncompressedSize = columnTotalUncompressedSize})+                    (cm{columnTotalUncompressedSize = parsedTotalUncompressedSize})                     buf                     pos                     identifier             7 -> do-                columnTotalCompressedSize <- readIntFromBuffer @Int64 buf pos+                parsedTotalCompressedSize <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata-                    (cm{columnTotalCompressedSize = columnTotalCompressedSize})+                    (cm{columnTotalCompressedSize = parsedTotalCompressedSize})                     buf                     pos                     identifier@@ -699,31 +699,31 @@                 sizeAndType <- readAndAdvance pos buf                 let sizeOnly = fromIntegral ((sizeAndType `shiftR` 4) .&. 0x0f) :: Int                 let _elemType = toTType sizeAndType-                columnKeyValueMetadata <-+                parsedKeyValueMeta <-                     replicateM sizeOnly (readKeyValue emptyKeyValue buf pos 0)                 readColumnMetadata-                    (cm{columnKeyValueMetadata = columnKeyValueMetadata})+                    (cm{columnKeyValueMetadata = parsedKeyValueMeta})                     buf                     pos                     identifier             9 -> do-                columnDataPageOffset <- readIntFromBuffer @Int64 buf pos+                parsedDataPageOffset <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata-                    (cm{columnDataPageOffset = columnDataPageOffset})+                    (cm{columnDataPageOffset = parsedDataPageOffset})                     buf                     pos                     identifier             10 -> do-                columnIndexPageOffset <- readIntFromBuffer @Int64 buf pos+                parsedIndexPageOffset <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata-                    (cm{columnIndexPageOffset = columnIndexPageOffset})+                    (cm{columnIndexPageOffset = parsedIndexPageOffset})                     buf                     pos                     identifier             11 -> do-                columnDictionaryPageOffset <- readIntFromBuffer @Int64 buf pos+                parsedDictionaryPageOffset <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata-                    (cm{columnDictionaryPageOffset = columnDictionaryPageOffset})+                    (cm{columnDictionaryPageOffset = parsedDictionaryPageOffset})                     buf                     pos                     identifier@@ -742,16 +742,16 @@                     pos                     identifier             14 -> do-                bloomFilterOffset <- readIntFromBuffer @Int64 buf pos+                parsedBloomFilterOffset <- readIntFromBuffer @Int64 buf pos                 readColumnMetadata-                    (cm{bloomFilterOffset = bloomFilterOffset})+                    (cm{bloomFilterOffset = parsedBloomFilterOffset})                     buf                     pos                     identifier             15 -> do-                bloomFilterLength <- readInt32FromBuffer buf pos+                parsedBloomFilterLength <- readInt32FromBuffer buf pos                 readColumnMetadata-                    (cm{bloomFilterLength = bloomFilterLength})+                    (cm{bloomFilterLength = parsedBloomFilterLength})                     buf                     pos                     identifier@@ -815,25 +815,25 @@     IORef Int ->     Int16 ->     IO EncryptionAlgorithm-readAesGcmCtrV1 v@(AesGcmCtrV1 aadPrefix aadFileUnique supplyAadPrefix) buf pos lastFieldId = do+readAesGcmCtrV1 v@(AesGcmCtrV1 _aadPrefix _aadFileUnique _supplyAadPrefix) buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of         Nothing -> return v         Just (elemType, identifier) -> case identifier of             1 -> do-                aadPrefix <- readByteString buf pos-                readAesGcmCtrV1 (v{aadPrefix = aadPrefix}) buf pos identifier+                parsedAadPrefix <- readByteString buf pos+                readAesGcmCtrV1 (v{aadPrefix = parsedAadPrefix}) buf pos identifier             2 -> do-                aadFileUnique <- readByteString buf pos+                parsedAadFileUnique <- readByteString buf pos                 readAesGcmCtrV1-                    (v{aadFileUnique = aadFileUnique})+                    (v{aadFileUnique = parsedAadFileUnique})                     buf                     pos                     identifier             3 -> do-                supplyAadPrefix <- readAndAdvance pos buf+                parsedSupplyAadPrefix <- readAndAdvance pos buf                 readAesGcmCtrV1-                    (v{supplyAadPrefix = supplyAadPrefix == compactBooleanTrue})+                    (v{supplyAadPrefix = parsedSupplyAadPrefix == compactBooleanTrue})                     buf                     pos                     identifier@@ -847,21 +847,21 @@     IORef Int ->     Int16 ->     IO EncryptionAlgorithm-readAesGcmV1 v@(AesGcmV1 aadPrefix aadFileUnique supplyAadPrefix) buf pos lastFieldId = do+readAesGcmV1 v@(AesGcmV1 _aadPrefix _aadFileUnique _supplyAadPrefix) buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of         Nothing -> return v         Just (elemType, identifier) -> case identifier of             1 -> do-                aadPrefix <- readByteString buf pos-                readAesGcmV1 (v{aadPrefix = aadPrefix}) buf pos identifier+                parsedAadPrefix <- readByteString buf pos+                readAesGcmV1 (v{aadPrefix = parsedAadPrefix}) buf pos identifier             2 -> do-                aadFileUnique <- readByteString buf pos-                readAesGcmV1 (v{aadFileUnique = aadFileUnique}) buf pos identifier+                parsedAadFileUnique <- readByteString buf pos+                readAesGcmV1 (v{aadFileUnique = parsedAadFileUnique}) buf pos identifier             3 -> do-                supplyAadPrefix <- readAndAdvance pos buf+                parsedSupplyAadPrefix <- readAndAdvance pos buf                 readAesGcmV1-                    (v{supplyAadPrefix = supplyAadPrefix == compactBooleanTrue})+                    (v{supplyAadPrefix = parsedSupplyAadPrefix == compactBooleanTrue})                     buf                     pos                     identifier@@ -986,9 +986,9 @@         Nothing -> return ss         Just (elemType, identifier) -> case identifier of             1 -> do-                unencodedByteArrayDataTypes <- readIntFromBuffer @Int64 buf pos+                parsedUnencodedByteArrayDataTypes <- readIntFromBuffer @Int64 buf pos                 readSizeStatistics-                    (ss{unencodedByteArrayDataTypes = unencodedByteArrayDataTypes})+                    (ss{unencodedByteArrayDataTypes = parsedUnencodedByteArrayDataTypes})                     buf                     pos                     identifier@@ -996,10 +996,10 @@                 sizeAndType <- readAndAdvance pos buf                 let sizeOnly = fromIntegral ((sizeAndType `shiftR` 4) .&. 0x0f) :: Int                 let _elemType = toTType sizeAndType-                repetitionLevelHistogram <-+                parsedRepetitionLevelHistogram <-                     replicateM sizeOnly (readIntFromBuffer @Int64 buf pos)                 readSizeStatistics-                    (ss{repetitionLevelHistogram = repetitionLevelHistogram})+                    (ss{repetitionLevelHistogram = parsedRepetitionLevelHistogram})                     buf                     pos                     identifier@@ -1007,10 +1007,10 @@                 sizeAndType <- readAndAdvance pos buf                 let sizeOnly = fromIntegral ((sizeAndType `shiftR` 4) .&. 0x0f) :: Int                 let _elemType = toTType sizeAndType-                definitionLevelHistogram <-+                parsedDefinitionLevelHistogram <-                     replicateM sizeOnly (readIntFromBuffer @Int64 buf pos)                 readSizeStatistics-                    (ss{definitionLevelHistogram = definitionLevelHistogram})+                    (ss{definitionLevelHistogram = parsedDefinitionLevelHistogram})                     buf                     pos                     identifier@@ -1033,10 +1033,10 @@  readLogicalType ::     LogicalType -> BS.ByteString -> IORef Int -> Int16 -> IO LogicalType-readLogicalType logicalType buf pos lastFieldId = do+readLogicalType parsedLogicalType buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of-        Nothing -> pure logicalType+        Nothing -> pure parsedLogicalType         Just (elemType, identifier) -> case identifier of             1 -> do                 -- This is an empty enum and is read as a field.@@ -1097,10 +1097,10 @@     IORef Int ->     Int16 ->     IO LogicalType-readIntType bitWidth intIsSigned buf pos lastFieldId = do+readIntType parsedBitWidth parsedIntIsSigned buf pos lastFieldId = do     t <- readAndAdvance pos buf     if t .&. 0x0f == 0-        then return (IntType bitWidth intIsSigned)+        then return (IntType parsedBitWidth parsedIntIsSigned)         else do             let modifier = fromIntegral ((t .&. 0xf0) `shiftR` 4) :: Int16             identifier <-@@ -1111,10 +1111,10 @@             case identifier of                 1 -> do                     bitWidth' <- readAndAdvance pos buf-                    readIntType (fromIntegral bitWidth') intIsSigned buf pos identifier+                    readIntType (fromIntegral bitWidth') parsedIntIsSigned buf pos identifier                 2 -> do                     let intIsSigned' = (t .&. 0x0f) == compactBooleanTrue-                    readIntType bitWidth intIsSigned' buf pos identifier+                    readIntType parsedBitWidth intIsSigned' buf pos identifier                 _ -> error $ "UNKNOWN field ID for IntType: " ++ show identifier  readDecimalType ::@@ -1124,17 +1124,17 @@     IORef Int ->     Int16 ->     IO LogicalType-readDecimalType precision scale buf pos lastFieldId = do+readDecimalType parsedPrecision parsedScale buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of-        Nothing -> return (DecimalType precision scale)+        Nothing -> return (DecimalType parsedPrecision parsedScale)         Just (elemType, identifier) -> case identifier of             1 -> do                 scale' <- readInt32FromBuffer buf pos-                readDecimalType precision scale' buf pos identifier+                readDecimalType parsedPrecision scale' buf pos identifier             2 -> do                 precision' <- readInt32FromBuffer buf pos-                readDecimalType precision' scale buf pos identifier+                readDecimalType precision' parsedScale buf pos identifier             _ -> error $ "UNKNOWN field ID for DecimalType" ++ show identifier  readTimeType ::@@ -1144,17 +1144,17 @@     IORef Int ->     Int16 ->     IO LogicalType-readTimeType isAdjustedToUTC unit buf pos lastFieldId = do+readTimeType parsedIsAdjustedToUTC parsedUnit buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of-        Nothing -> return (TimeType{isAdjustedToUTC = isAdjustedToUTC, unit = unit})+        Nothing -> return (TimeType{isAdjustedToUTC = parsedIsAdjustedToUTC, unit = parsedUnit})         Just (elemType, identifier) -> case identifier of             1 -> do                 let isAdjustedToUTC' = elemType == toTType compactBooleanTrue-                readTimeType isAdjustedToUTC' unit buf pos identifier+                readTimeType isAdjustedToUTC' parsedUnit buf pos identifier             2 -> do                 unit' <- readUnit TIME_UNIT_UNKNOWN buf pos 0-                readTimeType isAdjustedToUTC unit' buf pos identifier+                readTimeType parsedIsAdjustedToUTC unit' buf pos identifier             _ -> error $ "UNKNOWN field ID for TimeType" ++ show identifier  readTimestampType ::@@ -1164,24 +1164,24 @@     IORef Int ->     Int16 ->     IO LogicalType-readTimestampType isAdjustedToUTC unit buf pos lastFieldId = do+readTimestampType parsedIsAdjustedToUTC parsedUnit buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of-        Nothing -> return (TimestampType{isAdjustedToUTC = isAdjustedToUTC, unit = unit})+        Nothing -> return (TimestampType{isAdjustedToUTC = parsedIsAdjustedToUTC, unit = parsedUnit})         Just (elemType, identifier) -> case identifier of             1 -> do                 let isAdjustedToUTC' = elemType == toTType compactBooleanTrue-                readTimestampType isAdjustedToUTC' unit buf pos identifier+                readTimestampType isAdjustedToUTC' parsedUnit buf pos identifier             2 -> do                 unit' <- readUnit TIME_UNIT_UNKNOWN buf pos 0-                readTimestampType isAdjustedToUTC unit' buf pos identifier+                readTimestampType parsedIsAdjustedToUTC unit' buf pos identifier             _ -> error $ "UNKNOWN field ID for TimestampType " ++ show identifier  readUnit :: TimeUnit -> BS.ByteString -> IORef Int -> Int16 -> IO TimeUnit-readUnit unit buf pos lastFieldId = do+readUnit parsedUnit buf pos lastFieldId = do     fieldContents <- readField buf pos lastFieldId     case fieldContents of-        Nothing -> return unit+        Nothing -> return parsedUnit         Just (elemType, identifier) -> case identifier of             1 -> do                 _ <- readField buf pos 0
src/DataFrame/Internal/Column.hs view
@@ -20,7 +20,6 @@  import qualified Data.Text as T import qualified Data.Vector as VB-import qualified Data.Vector.Algorithms.Merge as VA import qualified Data.Vector.Generic as VG import qualified Data.Vector.Mutable as VBM import qualified Data.Vector.Unboxed as VU@@ -214,10 +213,6 @@     (==) :: (Eq a) => TypedColumn a -> TypedColumn a -> Bool     (==) (TColumn a) (TColumn b) = a == b -instance (Ord a) => Ord (TypedColumn a) where-    compare :: (Ord a) => TypedColumn a -> TypedColumn a -> Ordering-    compare (TColumn a) (TColumn b) = compare a b- -- | Gets the underlying value from a TypedColumn. unwrapTypedColumn :: TypedColumn a -> Column unwrapTypedColumn (TColumn value) = value@@ -370,19 +365,6 @@                         )     (==) _ _ = False --- Generalised LEQ that does reflection.-generalLEQ ::-    forall a b. (Typeable a, Typeable b, Ord a, Ord b) => a -> b -> Bool-generalLEQ x y = case testEquality (typeRep @a) (typeRep @b) of-    Nothing -> False-    Just Refl -> x <= y--instance Ord Column where-    (<=) :: Column -> Column -> Bool-    (<=) (BoxedColumn _ (a :: VB.Vector t1)) (BoxedColumn _ (b :: VB.Vector t2)) = generalLEQ a b-    (<=) (UnboxedColumn _ (a :: VU.Vector t1)) (UnboxedColumn _ (b :: VU.Vector t2)) = generalLEQ a b-    (<=) _ _ = False- {- | A class for converting a vector to a column of the appropriate type. Given each Rep we tell the `toColumnRep` function which Column type to pick. -}@@ -708,7 +690,7 @@     (a -> Bool) ->     Column ->     Either DataFrameException (VU.Vector Int)-findIndices pred = \case+findIndices predicate = \case     BoxedColumn _ (v :: VB.Vector b) -> run v VG.convert     UnboxedColumn _ (v :: VU.Vector b) -> run v id   where@@ -719,7 +701,7 @@         (v Int -> VU.Vector Int) ->         Either DataFrameException (VU.Vector Int)     run column finalize = case testEquality (typeRep @a) (typeRep @b) of-        Just Refl -> Right . finalize $ VG.findIndices pred column+        Just Refl -> Right . finalize $ VG.findIndices predicate column         Nothing ->             Left $                 TypeMismatchException@@ -729,23 +711,6 @@                         , callingFunctionName = Just "findIndices"                         , errorColumnName = Nothing                         }---- | An internal function that returns a vector of how indexes change after a column is sorted.-sortedIndexes :: Bool -> Column -> VU.Vector Int-sortedIndexes asc = \case-    BoxedColumn _ column -> sortWorker VG.convert column-    UnboxedColumn _ column -> sortWorker id column-  where-    sortWorker ::-        (VG.Vector v a, Ord a, VG.Vector v (Int, a), VG.Vector v Int) =>-        (v Int -> VU.Vector Int) -> v a -> VU.Vector Int-    sortWorker finalize column = runST $ do-        withIndexes <- VG.thaw $ VG.indexed column-        let cmp = if asc then compare else flip compare-        VA.sortBy (\(_, b) (_, b') -> cmp b b') withIndexes-        sorted <- VG.unsafeFreeze withIndexes-        return $ finalize $ VG.map fst sorted-{-# INLINE sortedIndexes #-}  -- | Fold (right) column with index. ifoldrColumn ::
src/DataFrame/Internal/DataFrame.hs view
@@ -207,6 +207,10 @@ toCsv :: DataFrame -> T.Text toCsv = toSeparated ',' +-- | Convert a DataFrame to a CSV (comma-separated) string.+toCsv' :: DataFrame -> String+toCsv' = T.unpack . toSeparated ','+ -- | Convert a DataFrame to a text representation with a custom separator. toSeparated :: Char -> DataFrame -> T.Text toSeparated sep df
src/DataFrame/Internal/Expression.hs view
@@ -53,13 +53,13 @@ data Expr a where     Col :: (Columnable a) => T.Text -> Expr a     CastWith ::-        (Columnable a, Columnable b) =>+        (Columnable a, Columnable b, Read a) =>         T.Text ->         T.Text ->         (Either String a -> b) ->         Expr b     CastExprWith ::-        (Columnable a, Columnable b, Columnable src) =>+        (Columnable a, Columnable b, Columnable src, Read a) =>         T.Text ->         (Either String a -> b) ->         Expr src ->@@ -234,7 +234,8 @@             (MkUnaryOp{unaryFn = atanh, unaryName = "atanh", unarySymbol = Nothing})  instance (Show a) => Show (Expr a) where-    show :: forall a. (Show a) => Expr a -> String+    {-# HLINT ignore "Use show" #-}+    show :: Expr a -> String     show (Col name) = "(col @" ++ show (typeRep @a) ++ " " ++ show name ++ ")"     show (CastWith name tag _) = "(castWith " ++ show tag ++ " " ++ show name ++ ")"     show (CastExprWith tag _ inner) = "(castExprWith " ++ show tag ++ " " ++ show inner ++ ")"@@ -246,7 +247,7 @@     show (Agg (FoldAgg op _ _) expr) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")"     show (Agg (MergeAgg op _ _ _ _) expr) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")" -normalize :: (Eq a, Ord a, Show a, Typeable a) => Expr a -> Expr a+normalize :: (Show a, Typeable a) => Expr a -> Expr a normalize expr = case expr of     Col name -> Col name     CastWith n t f -> CastWith n t f@@ -278,11 +279,14 @@     exprKey (Lit val) = "1:" ++ show val     exprKey (If c t e) = "2:" ++ exprKey c ++ exprKey t ++ exprKey e     exprKey (Unary op e) = "3:" ++ T.unpack (unaryName op) ++ exprKey e-    exprKey (Binary op e1 e2) = "4:" ++ T.unpack (binaryName op) ++ exprKey e1 ++ exprKey e2+    exprKey (Binary op e1' e2') = "4:" ++ T.unpack (binaryName op) ++ exprKey e1' ++ exprKey e2'     exprKey (Agg (CollectAgg name _) e) = "5:" ++ T.unpack name ++ exprKey e     exprKey (Agg (FoldAgg name _ _) e) = "5:" ++ T.unpack name ++ exprKey e     exprKey (Agg (MergeAgg name _ _ _ _) e) = "5:" ++ T.unpack name ++ exprKey e +instance (Ord a, Columnable a) => Ord (Expr a) where+    compare l r = compareExpr (normalize l) (normalize r)+ instance (Eq a, Columnable a) => Eq (Expr a) where     (==) l r = eqNormalized (normalize l) (normalize r)       where@@ -307,43 +311,6 @@             n1 == n2 && e1 `exprEq` e2         eqNormalized _ _ = False -instance (Ord a, Columnable a) => Ord (Expr a) where-    compare :: Expr a -> Expr a -> Ordering-    compare e1 e2 = case (e1, e2) of-        (Col n1, Col n2) -> compare n1 n2-        (CastWith n1 t1 _, CastWith n2 t2 _) -> compare n1 n2 <> compare t1 t2-        (CastExprWith t1 _ _, CastExprWith t2 _ _) -> compare t1 t2-        (Lit v1, Lit v2) -> compare v1 v2-        (If c1 t1 e1', If c2 t2 e2') ->-            compare c1 c2 <> exprComp t1 t2 <> exprComp e1' e2'-        (Unary op1 e1', Unary op2 e2') -> compare (unaryName op1) (unaryName op2) <> exprComp e1' e2'-        (Binary op1 a1 b1, Binary op2 a2 b2) ->-            compare (binaryName op1) (binaryName op2) <> exprComp a1 a2 <> exprComp b1 b2-        (Agg (CollectAgg n1 _) e1', Agg (CollectAgg n2 _) e2') -> compare n1 n2 <> exprComp e1' e2'-        (Agg (FoldAgg n1 _ _) e1', Agg (FoldAgg n2 _ _) e2') -> compare n1 n2 <> exprComp e1' e2'-        (Agg (MergeAgg n1 _ _ _ _) e1', Agg (MergeAgg n2 _ _ _ _) e2') -> compare n1 n2 <> exprComp e1' e2'-        -- Different constructors - compare by priority-        (Col _, _) -> LT-        (_, Col _) -> GT-        (CastWith{}, _) -> LT-        (_, CastWith{}) -> GT-        (CastExprWith{}, _) -> LT-        (_, CastExprWith{}) -> GT-        (Lit _, _) -> LT-        (_, Lit _) -> GT-        (Unary{}, _) -> LT-        (_, Unary{}) -> GT-        (Binary{}, _) -> LT-        (_, Binary{}) -> GT-        (If{}, _) -> LT-        (_, If{}) -> GT-        (Agg{}, _) -> LT--exprComp :: (Columnable b, Columnable c) => Expr b -> Expr c -> Ordering-exprComp e1 e2 = case testEquality (typeOf e1) (typeOf e2) of-    Just Refl -> e1 `compare` e2-    Nothing -> LT- replaceExpr ::     forall a b c.     (Columnable a, Columnable b, Columnable c) =>@@ -363,7 +330,7 @@             If (replaceExpr new old cond) (replaceExpr new old l) (replaceExpr new old r)         (Unary op value) -> Unary op (replaceExpr new old value)         (Binary op l r) -> Binary op (replaceExpr new old l) (replaceExpr new old r)-        (Agg op expr) -> Agg op (replaceExpr new old expr)+        (Agg op inner) -> Agg op (replaceExpr new old inner)  eSize :: Expr a -> Int eSize (Col _) = 1
src/DataFrame/Internal/Interpreter.hs view
@@ -41,6 +41,225 @@     typeRep,  ) +import Data.Int (Int16, Int32, Int64, Int8)++-- Specializations for common aggregation types to avoid dictionary overhead.+-- foldLinearGroups: mean accumulator+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Double -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Float -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Int -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Int8 -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Int16 -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Int32 -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (MeanAcc -> Int64 -> MeanAcc) ->+    MeanAcc ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+-- foldLinearGroups: count accumulator+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Double -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Float -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Int -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Int8 -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Int16 -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Int32 -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int -> Int64 -> Int) ->+    Int ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+-- foldLinearGroups: sum/min/max (acc == elem)+{-# SPECIALIZE foldLinearGroups ::+    (Double -> Double -> Double) ->+    Double ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Float -> Float -> Float) ->+    Float ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int8 -> Int8 -> Int8) ->+    Int8 ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int16 -> Int16 -> Int16) ->+    Int16 ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int32 -> Int32 -> Int32) ->+    Int32 ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE foldLinearGroups ::+    (Int64 -> Int64 -> Int64) ->+    Int64 ->+    Column ->+    VU.Vector Int ->+    Int ->+    Either DataFrameException Column+    #-}++-- mapColumn: finalize+{-# SPECIALIZE mapColumn ::+    (MeanAcc -> Double) -> Column -> Either DataFrameException Column+    #-}+{-# SPECIALIZE mapColumn ::+    (Double -> Double) -> Column -> Either DataFrameException Column+    #-}+{-# SPECIALIZE mapColumn ::+    (Float -> Float) -> Column -> Either DataFrameException Column+    #-}+{-# SPECIALIZE mapColumn ::+    (Int -> Int) -> Column -> Either DataFrameException Column+    #-}++-- zipWithColumns: binary ops+{-# SPECIALIZE zipWithColumns ::+    (Double -> Double -> Double) ->+    Column ->+    Column ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE zipWithColumns ::+    (Float -> Float -> Float) ->+    Column ->+    Column ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE zipWithColumns ::+    (Int -> Int -> Int) -> Column -> Column -> Either DataFrameException Column+    #-}+{-# SPECIALIZE zipWithColumns ::+    (Int8 -> Int8 -> Int8) -> Column -> Column -> Either DataFrameException Column+    #-}+{-# SPECIALIZE zipWithColumns ::+    (Int16 -> Int16 -> Int16) ->+    Column ->+    Column ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE zipWithColumns ::+    (Int32 -> Int32 -> Int32) ->+    Column ->+    Column ->+    Either DataFrameException Column+    #-}+{-# SPECIALIZE zipWithColumns ::+    (Int64 -> Int64 -> Int64) ->+    Column ->+    Column ->+    Either DataFrameException Column+    #-}+ ------------------------------------------------------------------------------- -- Value: the unified result type -------------------------------------------------------------------------------@@ -253,7 +472,7 @@ -} promoteColumnWith ::     forall a b.-    (Columnable a, Columnable b) =>+    (Columnable a, Columnable b, Read a) =>     (Either String a -> b) -> Column -> Either DataFrameException Column promoteColumnWith onResult col     | hasElemType @b col = Right col@@ -395,7 +614,7 @@  tryParseWith ::     forall a b.-    (Columnable a, Columnable b) =>+    (Columnable a, Columnable b, Read a) =>     (Either String a -> b) -> Column -> Either DataFrameException Column tryParseWith onResult col = case col of     BoxedColumn bm (v :: V.Vector c) ->
src/DataFrame/Internal/Parsing.hs view
@@ -13,12 +13,10 @@ import Data.Attoparsec.Text hiding (decimal, double, signed) import Data.ByteString.Lex.Fractional import Data.Foldable (fold)-import Data.Maybe (fromMaybe) import Data.Text.Read (decimal, double, signed) import Data.Time (Day, defaultTimeLocale, parseTimeM) import GHC.Stack (HasCallStack) import System.IO (Handle, IOMode (..), hIsEOF, hTell, withFile)-import Text.Read (readMaybe) import Prelude hiding (takeWhile)  isNullish :: T.Text -> Bool@@ -41,11 +39,6 @@ isFalseish :: T.Text -> Bool isFalseish t = t `elem` ["False", "false", "FALSE"] -readValue :: (HasCallStack, Read a) => T.Text -> a-readValue s = case readMaybe (T.unpack s) of-    Nothing -> error ("Could not read value: " <> T.unpack s)-    Just value -> value- readBool :: (HasCallStack) => T.Text -> Maybe Bool readBool s     | isTrueish s = Just True@@ -121,12 +114,6 @@         Right (value, "") -> Right value         Right (value, _) -> Left s {-# INLINE readDoubleEither #-}--safeReadValue :: (Read a) => T.Text -> Maybe a-safeReadValue s = readMaybe (T.unpack s)--readWithDefault :: (HasCallStack, Read a) => a -> T.Text -> a-readWithDefault v s = fromMaybe v (readMaybe (T.unpack s))  -- --------------------------------------------------------------------------- -- Attoparsec CSV parser combinators (shared between Lazy.IO.CSV and others)
src/DataFrame/Internal/Row.hs view
@@ -13,12 +13,9 @@ import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Vector as V-import qualified Data.Vector.Algorithms.Merge as VA-import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as VU  import Control.Exception (throw)-import Control.Monad.ST (runST) import Data.Function (on) import Data.Maybe (fromMaybe) import Data.Type.Equality (TestEquality (..))@@ -27,15 +24,6 @@ import DataFrame.Internal.Column import DataFrame.Internal.DataFrame import DataFrame.Internal.Expression (Expr (..))-import Text.ParserCombinators.ReadPrec (ReadPrec)-import Text.Read (-    Lexeme (Ident),-    lexP,-    parens,-    readListPrec,-    readListPrecDefault,-    readPrec,- ) import Type.Reflection (typeOf, typeRep)  data Any where@@ -47,12 +35,6 @@         Refl <- testEquality (typeOf a) (typeOf b)         return $ a == b -instance Ord Any where-    (<=) :: Any -> Any -> Bool-    (Value a) <= (Value b) = fromMaybe False $ do-        Refl <- testEquality (typeOf a) (typeOf b)-        return $ a <= b- instance Show Any where     show :: Any -> String     show (Value a) = T.unpack (showValue a)@@ -64,15 +46,6 @@         Just Refl -> T.pack v         Nothing -> (T.pack . show) v -instance Read Any where-    readListPrec :: ReadPrec [Any]-    readListPrec = readListPrecDefault--    readPrec :: ReadPrec Any-    readPrec = parens $ do-        Ident "Value" <- lexP-        readPrec- -- | Wraps a value into an \Any\ type. This helps up represent rows as heterogenous lists. toAny :: forall a. (Columnable a) => a -> Any toAny = Value@@ -200,17 +173,3 @@             Nothing -> throwError name         Nothing ->             throw $ ColumnsNotFoundException [name] "mkRowRep" (M.keys $ columnIndices df)--sortedIndexes' :: [Bool] -> V.Vector Row -> VU.Vector Int-sortedIndexes' flipCompare rows = runST $ do-    withIndexes <- VG.thaw (V.indexed rows)-    VA.sortBy (produceOrderingFromRow flipCompare `on` snd) withIndexes-    sorted <- VG.unsafeFreeze withIndexes-    return $ VU.generate (VG.length rows) (\i -> fst (sorted VG.! i))--produceOrderingFromRow :: [Bool] -> Row -> Row -> Ordering-produceOrderingFromRow mustFlips v1 v2 = V.foldr (<>) mempty vZipped-  where-    vFlip = V.fromList mustFlips-    vZipped =-        V.zipWith3 (\b e1 e2 -> if b then compare e1 e2 else compare e2 e1) vFlip v1 v2
src/DataFrame/Internal/Schema.hs view
@@ -21,7 +21,7 @@ -- | 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+    SType :: (Columnable a, Read a) => P.Proxy a -> SchemaType  {- | Show the underlying element type using 'typeRep'. @@ -59,7 +59,7 @@ >>> show (schemaType @Double) "Double" -}-schemaType :: forall a. (Columnable a) => SchemaType+schemaType :: forall a. (Columnable a, Read a) => SchemaType schemaType = SType (P.Proxy @a)  {- | Logical schema of a 'DataFrame': a mapping from column names to their
src/DataFrame/Internal/Statistics.hs view
@@ -63,12 +63,12 @@     deriving (Show)  varianceStep :: VarAcc -> Double -> VarAcc-varianceStep (VarAcc !n !mean !m2) !x =+varianceStep (VarAcc !n !meanVal !m2) !x =     let !n' = n + 1-        !delta = x - mean-        !mean' = mean + delta / fromIntegral n'-        !m2' = m2 + delta * (x - mean')-     in VarAcc n' mean' m2'+        !delta = x - meanVal+        !meanVal' = meanVal + delta / fromIntegral n'+        !m2' = m2 + delta * (x - meanVal')+     in VarAcc n' meanVal' m2' {-# INLINE varianceStep #-}  computeVariance :: VarAcc -> Double@@ -89,15 +89,15 @@ data SkewAcc = SkewAcc !Int !Double !Double !Double deriving (Show)  skewnessStep :: (VU.Unbox a, Num a, Real a) => SkewAcc -> a -> SkewAcc-skewnessStep (SkewAcc !n !mean !m2 !m3) !x' =+skewnessStep (SkewAcc !n !meanVal !m2 !m3) !x' =     let !n' = n + 1         x = rtf x'         !k = fromIntegral n'-        !delta = x - mean-        !mean' = mean + delta / k+        !delta = x - meanVal+        !meanVal' = meanVal + delta / k         !m2' = m2 + (delta ^ 2 * (k - 1)) / k         !m3' = m3 + (delta ^ 3 * (k - 1) * (k - 2)) / k ^ 2 - (3 * delta * m2) / k-     in SkewAcc n' mean' m2' m3'+     in SkewAcc n' meanVal' m2' m3' {-# INLINE skewnessStep #-}  computeSkewness :: SkewAcc -> Double
src/DataFrame/Internal/Types.hs view
@@ -19,7 +19,7 @@ import qualified Data.Vector.Unboxed as VU import Data.Word (Word16, Word32, Word64, Word8) -type Columnable' a = (Typeable a, Show a, Ord a, Eq a, Read a)+type Columnable' a = (Typeable a, Show a, Eq a)  {- | A type with column representations used to select the "right" representation when specializing the `toColumn` function.
src/DataFrame/Lazy/IO/CSV.hs view
@@ -87,13 +87,13 @@ readSeparated ::     Char -> ReadOptions -> FilePath -> IO (DataFrame, (Integer, T.Text, Int)) readSeparated c opts path = do-    totalRows <- case totalRows opts of+    totalRows' <- case totalRows opts of         Nothing ->             countRows c path >>= \total -> if hasHeader opts then return (total - 1) else return total         Just n -> if hasHeader opts then return (n - 1) else return n-    let (_, len) = case rowRange opts of-            Nothing -> (0, totalRows)-            Just (start, len) -> (start, min len (totalRows - rowsRead opts))+    let (_, len') = case rowRange opts of+            Nothing -> (0, totalRows')+            Just (start, len'') -> (start, min len'' (totalRows' - rowsRead opts))     withFile path ReadMode $ \handle -> do         firstRow <- fmap T.strip . parseSep c <$> TIO.hGetLine handle         let columnNames =@@ -109,7 +109,7 @@          -- Initialize mutable vectors for each column         let numColumns = length columnNames-        let numRows = len+        let numRows = len'         -- Use this row to infer the types of the rest of the column.         (dataRow, remainder) <- readSingleLine c (leftOver opts) handle @@ -169,11 +169,11 @@     IO (T.Text, Int) fillColumns n c mutableCols nullIndices unused handle = do     input <- newIORef unused-    rowsRead <- newIORef (0 :: Int)+    rowsRead' <- newIORef (0 :: Int)     forM_ [1 .. (n - 1)] $ \i -> do-        isEOF <- hIsEOF handle+        atEOF <- hIsEOF handle         input' <- readIORef input-        unless (isEOF && input' == mempty) $ do+        unless (atEOF && input' == mempty) $ do             parseWith (TIO.hGetChunk handle) (parseRow c) input' >>= \case                 Fail unconsumed ctx er -> do                     erpos <- hTell handle@@ -188,10 +188,10 @@                     fail "Partial handler is called"                 Done (unconsumed :: T.Text) (row :: [T.Text]) -> do                     writeIORef input unconsumed-                    modifyIORef rowsRead (+ 1)+                    modifyIORef rowsRead' (+ 1)                     zipWithM_ (writeValue mutableCols nullIndices i) [0 ..] row     l <- readIORef input-    r <- readIORef rowsRead+    r <- readIORef rowsRead'     pure (l, r) {-# INLINE fillColumns #-} @@ -206,7 +206,7 @@ writeValue mutableCols nullIndices count colIndex value = do     col <- VM.unsafeRead mutableCols colIndex     res <- writeColumn count value col-    let modify value = VM.unsafeModify nullIndices ((count, value) :) colIndex+    let modify val = VM.unsafeModify nullIndices ((count, val) :) colIndex     either modify (const (return ())) res {-# INLINE writeValue #-} @@ -392,12 +392,12 @@                             else go idx start inQ (pos + 1)      extract s e =-        let field = BS.take (e - s) (BS.drop s bs)-         in if BS.length field >= 2-                && BS.head field == quoteChar-                && BS.last field == quoteChar-                then BS.init (BS.tail field)-                else field+        let fieldVal = BS.take (e - s) (BS.drop s bs)+         in if BS.length fieldVal >= 2+                && BS.head fieldVal == quoteChar+                && BS.last fieldVal == quoteChar+                then BS.init (BS.tail fieldVal)+                else fieldVal {-# INLINE getNthFieldBs #-}  -- | Allocate a fresh 'MutableColumn' for @n@ slots based on a 'SchemaType'.
src/DataFrame/Operations/Aggregation.hs view
@@ -62,8 +62,8 @@             (VU.fromList [0])             VU.empty     | otherwise =-        let !vis = VU.map fst valueIndices-            !os = changingPoints valueIndices+        let !vis = VU.map fst valIndices+            !os = changingPoints valIndices             !n = fst (dimensions df)          in Grouped                 df@@ -75,7 +75,7 @@     indicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` names) (columnIndices df)     doubleToInt :: Double -> Int     doubleToInt = floor . (* 1000)-    valueIndices = runST $ do+    valIndices = runST $ do         let n = fst (dimensions df)         mv <- VUM.new n @@ -183,9 +183,9 @@         (VU.fromList (VU.length vs : fst (VU.ifoldl' findChangePoints initialState vs)))   where     initialState = ([0], snd (VU.head vs))-    findChangePoints (!offsets, !currentVal) index (_, !newVal)-        | currentVal == newVal = (offsets, currentVal)-        | otherwise = (index : offsets, newVal)+    findChangePoints (!offs, !currentVal) index (_, !newVal)+        | currentVal == newVal = (offs, currentVal)+        | otherwise = (index : offs, newVal)  computeRowHashes :: [Int] -> DataFrame -> VU.Vector Int computeRowHashes indices df = runST $ do@@ -275,11 +275,11 @@ All ungrouped columns will be dropped. -} aggregate :: [NamedExpr] -> GroupedDataFrame -> DataFrame-aggregate aggs gdf@(Grouped df groupingColumns valueIndices offsets _rowToGroup) =+aggregate aggs gdf@(Grouped df groupingColumns valIndices offs _rowToGroup) =     let         df' =             selectIndices-                (VU.map (valueIndices VU.!) (VU.init offsets))+                (VU.map (valIndices VU.!) (VU.init offs))                 (select groupingColumns df)          f (name, UExpr (expr :: Expr a)) d =
src/DataFrame/Operations/Join.hs view
@@ -81,8 +81,8 @@ buildCompactIndex hashes =     let n = VU.length hashes         (sortedHashes, sortedIndices) = sortWithIndices hashes-        !offsets = buildOffsets sortedHashes n 0 HM.empty-     in CompactIndex sortedIndices offsets+        !offs = buildOffsets sortedHashes n 0 HM.empty+     in CompactIndex sortedIndices offs   where     buildOffsets ::         VU.Vector Int ->
src/DataFrame/Operations/Permutation.hs view
@@ -2,30 +2,35 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}  module DataFrame.Operations.Permutation where  import qualified Data.List as L import qualified Data.Text as T import qualified Data.Vector as V+import qualified Data.Vector.Algorithms.Merge as VA+import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as VU import qualified Data.Vector.Unboxed.Mutable as VUM  import Control.Exception (throw) import Control.Monad.ST (runST)+import Data.Type.Equality (testEquality, (:~:) (Refl)) import Data.Vector.Internal.Check (HasCallStack) import DataFrame.Errors (DataFrameException (..))-import DataFrame.Internal.Column (Columnable, atIndicesStable)-import DataFrame.Internal.DataFrame (DataFrame (..))+import DataFrame.Internal.Column (Column (..), Columnable, atIndicesStable)+import DataFrame.Internal.DataFrame (DataFrame (..), unsafeGetColumn) import DataFrame.Internal.Expression (Expr (Col))-import DataFrame.Internal.Row (sortedIndexes', toRowVector) import DataFrame.Operations.Core (columnNames, dimensions) import System.Random (Random (randomR), RandomGen)+import Type.Reflection (typeRep)  -- | Sort order taken as a parameter by the 'sortBy' function. data SortOrder where-    Asc :: (Columnable a) => Expr a -> SortOrder-    Desc :: (Columnable a) => Expr a -> SortOrder+    Asc :: (Columnable a, Ord a) => Expr a -> SortOrder+    Desc :: (Columnable a, Ord a) => Expr a -> SortOrder  instance Eq SortOrder where     (==) :: SortOrder -> SortOrder -> Bool@@ -59,12 +64,44 @@                 (columnNames df)     | otherwise =         let-            indexes = sortedIndexes' mustFlips (toRowVector names df)+            comparators = map (`sortOrderComparator` df) sortOrds+            compositeCompare i j = mconcat [c i j | c <- comparators]+            nRows = fst (dataframeDimensions df)+            indexes = sortIndices compositeCompare nRows          in             df{columns = V.map (atIndicesStable indexes) (columns df)}   where     names = map getSortColumnName sortOrds-    mustFlips = map mustFlipCompare sortOrds++{- | Build a row-index comparator from a SortOrder and a DataFrame.+The Ord dictionary is recovered from the SortOrder GADT.+-}+sortOrderComparator :: SortOrder -> DataFrame -> Int -> Int -> Ordering+sortOrderComparator (Asc (Col name :: Expr a)) df =+    case unsafeGetColumn name df of+        BoxedColumn _ (v :: V.Vector b) -> case testEquality (typeRep @a) (typeRep @b) of+            Just Refl -> \i j -> compare (v `V.unsafeIndex` i) (v `V.unsafeIndex` j)+            Nothing -> \_ _ -> EQ+        UnboxedColumn _ (v :: VU.Vector b) -> case testEquality (typeRep @a) (typeRep @b) of+            Just Refl -> \i j -> compare (v `VU.unsafeIndex` i) (v `VU.unsafeIndex` j)+            Nothing -> \_ _ -> EQ+sortOrderComparator (Desc (Col name :: Expr a)) df =+    case unsafeGetColumn name df of+        BoxedColumn _ (v :: V.Vector b) -> case testEquality (typeRep @a) (typeRep @b) of+            Just Refl -> \i j -> compare (v `V.unsafeIndex` j) (v `V.unsafeIndex` i)+            Nothing -> \_ _ -> EQ+        UnboxedColumn _ (v :: VU.Vector b) -> case testEquality (typeRep @a) (typeRep @b) of+            Just Refl -> \i j -> compare (v `VU.unsafeIndex` j) (v `VU.unsafeIndex` i)+            Nothing -> \_ _ -> EQ+sortOrderComparator _ _ = error "Sorting on compound column"++-- | Sort row indices using a comparator function.+sortIndices :: (Int -> Int -> Ordering) -> Int -> VU.Vector Int+sortIndices cmp nRows = runST $ do+    withIndexes <- VG.thaw (V.generate nRows id :: V.Vector Int)+    VA.sortBy cmp withIndexes+    sorted <- VG.unsafeFreeze withIndexes+    return (VU.convert sorted)  shuffle ::     (RandomGen g) =>
src/DataFrame/Operations/Statistics.hs view
@@ -60,7 +60,8 @@  Percentage (%) | 44.26%    | 31.74% | 0.02%  | 11.09%   | 12.88% @ -}-frequencies :: forall a. (Columnable a) => Expr a -> DataFrame -> DataFrame+frequencies ::+    forall a. (Columnable a, Ord a) => Expr a -> DataFrame -> DataFrame frequencies expr df =     let         counts = valueCounts expr df@@ -68,11 +69,11 @@         initDf =             empty                 & insertVector "Statistic" (V.fromList ["Count" :: T.Text, "Percentage (%)"])-        freqs col =+        freqs col' =             L.foldl'-                ( \d (col, k) ->+                ( \d (col'', k) ->                     insertVector-                        (showValue @a col)+                        (showValue @a col'')                         (V.fromList [toAny k, calculatePercentage counts k])                         d                 )@@ -248,7 +249,7 @@     Left e -> throw e     Right (TColumn xs) -> case toVector @a @V.Vector xs of         Left e -> throw e-        Right xs -> VG.sum xs+        Right xs' -> VG.sum xs'  {- | /O(n)/ Impute missing values in a column using a derived scalar. @@ -367,7 +368,7 @@             quantiles = applyStatistics (quantiles' (VU.fromList [0, 1, 2, 3, 4]) 4) name df             min' = flip (VG.!) 0 <$> quantiles             quartile1 = flip (VG.!) 1 <$> quantiles-            median' = flip (VG.!) 2 <$> quantiles+            medianVal = flip (VG.!) 2 <$> quantiles             quartile3 = flip (VG.!) 3 <$> quantiles             max' = flip (VG.!) 4 <$> quantiles             iqr = (-) <$> quartile3 <*> quartile1@@ -377,7 +378,7 @@             , mean' <$> doubleColumn name             , min'             , quartile1-            , median'+            , medianVal             , quartile3             , max'             , sqrt . variance' <$> doubleColumn name
src/DataFrame/Operations/Subset.hs view
@@ -132,7 +132,7 @@     Nothing ->         throw $             ColumnsNotFoundException [filterColumnName] "filter" (M.keys $ columnIndices df)-    Just col@(BoxedColumn bm (column :: V.Vector b)) ->+    Just _col@(BoxedColumn bm (column :: V.Vector b)) ->         -- Check direct type match first, then try Maybe b match for nullable columns         case testEquality (typeRep @a) (typeRep @b) of             Just Refl -> filterByVector filterColumnName column condition df@@ -145,7 +145,7 @@                         Nothing -> filterByVector filterColumnName column condition df                     Nothing -> filterByVector filterColumnName column condition df                 _ -> filterByVector filterColumnName column condition df-    Just col@(UnboxedColumn bm (column :: VU.Vector b)) ->+    Just _col@(UnboxedColumn bm (column :: VU.Vector b)) ->         case testEquality (typeRep @a) (typeRep @b) of             Just Refl -> filterByVector filterColumnName column condition df             Nothing -> case (bm, typeRep @a) of@@ -160,10 +160,10 @@                 _ -> filterByVector filterColumnName column condition df filter expr condition df =     let-        (TColumn col) = case interpret @a df (normalize expr) of+        (TColumn col') = case interpret @a df (normalize expr) of             Left e -> throw e             Right c -> c-        indexes = case findIndices condition col of+        indexes = case findIndices condition col' of             Right ixs -> ixs             Left e -> throw e         c' = snd $ dataframeDimensions df@@ -211,10 +211,10 @@ filterWhere :: Expr Bool -> DataFrame -> DataFrame filterWhere expr df =     let-        (TColumn col) = case interpret @Bool df (normalize expr) of+        (TColumn col') = case interpret @Bool df (normalize expr) of             Left e -> throw e             Right c -> c-        indexes = case findIndices id col of+        indexes = case findIndices id col' of             Right ixs -> ixs             Left e -> throw e         c' = snd $ dataframeDimensions df@@ -229,12 +229,12 @@ > filterJust "col" df -} filterJust :: T.Text -> DataFrame -> DataFrame-filterJust name df = case getColumn name df of+filterJust colName df = case getColumn colName df of     Nothing ->-        throw $ ColumnsNotFoundException [name] "filterJust" (M.keys $ columnIndices df)+        throw $ ColumnsNotFoundException [colName] "filterJust" (M.keys $ columnIndices df)     Just column | hasMissing column -> case column of-        BoxedColumn (Just _) (col :: V.Vector a) -> filter (Col @(Maybe a) name) isJust df & apply @(Maybe a) fromJust name-        UnboxedColumn (Just _) (col :: VU.Vector a) -> filter (Col @(Maybe a) name) isJust df & apply @(Maybe a) fromJust name+        BoxedColumn (Just _) (_col :: V.Vector a) -> filter (Col @(Maybe a) colName) isJust df & apply @(Maybe a) fromJust colName+        UnboxedColumn (Just _) (_col :: VU.Vector a) -> filter (Col @(Maybe a) colName) isJust df & apply @(Maybe a) fromJust colName         _ -> df     Just _ -> df @@ -243,13 +243,13 @@ > filterNothing "col" df -} filterNothing :: T.Text -> DataFrame -> DataFrame-filterNothing name df = case getColumn name df of+filterNothing colName df = case getColumn colName df of     Nothing ->         throw $-            ColumnsNotFoundException [name] "filterNothing" (M.keys $ columnIndices df)+            ColumnsNotFoundException [colName] "filterNothing" (M.keys $ columnIndices df)     Just column | hasMissing column -> case column of-        BoxedColumn (Just _) (col :: V.Vector a) -> filter (Col @(Maybe a) name) isNothing df-        UnboxedColumn (Just _) (col :: VU.Vector a) -> filter (Col @(Maybe a) name) isNothing df+        BoxedColumn (Just _) (_col :: V.Vector a) -> filter (Col @(Maybe a) colName) isNothing df+        UnboxedColumn (Just _) (_col :: VU.Vector a) -> filter (Col @(Maybe a) colName) isNothing df         _ -> df     _ -> df @@ -273,7 +273,7 @@ > cube (10, 5) df -} cube :: (Int, Int) -> DataFrame -> DataFrame-cube (length, width) = take length . selectBy [ColumnIndexRange (0, width - 1)]+cube (len, width) = take len . selectBy [ColumnIndexRange (0, width - 1)]  {- | O(n) Selects a number of columns in a given dataframe. @@ -297,8 +297,8 @@          in result{derivingExpressions = filteredExprs}   where     addKeyValue d k = fromMaybe df $ do-        col <- getColumn k df-        pure $ insertColumn k col d+        col' <- getColumn k df+        pure $ insertColumn k col' d  data SelectionCriteria     = ColumnProperty (Column -> Bool)@@ -352,7 +352,7 @@   where     finalSelection = Prelude.filter (`S.member` columnsWithProperties) (columnNames df)     columnsWithProperties = S.fromList (L.foldl' columnWithProperty [] xs)-    columnWithProperty acc (ColumnName name) = acc ++ [name]+    columnWithProperty acc (ColumnName colName) = acc ++ [colName]     columnWithProperty acc (ColumnNameProperty f) = acc ++ L.filter f (columnNames df)     columnWithProperty acc (ColumnTextRange (from, to)) =         acc@@ -363,7 +363,7 @@         acc             ++ map fst (L.filter (\(k, v) -> v `elem` ixs) (M.toAscList (columnIndices df)))       where-        ixs = V.ifoldl' (\acc i c -> if f c then i : acc else acc) [] (columns df)+        ixs = V.ifoldl' (\acc' i c -> if f c then i : acc' else acc') [] (columns df)  {- | O(n) inverse of select @@ -463,24 +463,24 @@  -- | Convert any Column to a vector of Text labels (one per row). columnToTextVec :: Column -> V.Vector T.Text-columnToTextVec (BoxedColumn bm (col :: V.Vector a)) =+columnToTextVec (BoxedColumn bm (col' :: V.Vector a)) =     case bm of         Nothing -> case testEquality (typeRep @a) (typeRep @T.Text) of-            Just Refl -> col-            Nothing -> V.map (T.pack . show) col+            Just Refl -> col'+            Nothing -> V.map (T.pack . show) col'         Just bitmap ->-            V.imap (\i x -> if bitmapTestBit bitmap i then T.pack (show x) else "null") col-columnToTextVec (UnboxedColumn bm col) =+            V.imap (\i x -> if bitmapTestBit bitmap i then T.pack (show x) else "null") col'+columnToTextVec (UnboxedColumn bm col') =     case bm of-        Nothing -> V.map (T.pack . show) (V.convert col)+        Nothing -> V.map (T.pack . show) (V.convert col')         Just bitmap ->-            V.generate (VU.length col) $ \i ->-                if bitmapTestBit bitmap i then T.pack (show (col VU.! i)) else "null"+            V.generate (VU.length col') $ \i ->+                if bitmapTestBit bitmap i then T.pack (show (col' VU.! i)) else "null"  -- | Build a map from stringified label to row indices. groupByIndices :: Column -> M.Map T.Text (VU.Vector Int)-groupByIndices col =-    let textVec = columnToTextVec col+groupByIndices col' =+    let textVec = columnToTextVec col'         (grouped, _) =             V.foldl'                 (\(!m, !i) key -> (M.insertWith (++) key [i] m, i + 1))@@ -509,10 +509,10 @@     (SplittableGen g, Columnable a) =>     g -> Double -> Expr a -> DataFrame -> DataFrame stratifiedSample gen p strataCol df =-    let col = case strataCol of-            Col name -> unsafeGetColumn name df+    let col' = case strataCol of+            Col colName -> unsafeGetColumn colName df             _ -> unwrapTypedColumn (either throw id (interpret @a df strataCol))-        groups = M.elems (groupByIndices col)+        groups = M.elems (groupByIndices col')         go _ [] = mempty         go g (ixs : rest) =             let stratum = rowsAtIndices ixs df@@ -533,10 +533,10 @@     (SplittableGen g, Columnable a) =>     g -> Double -> Expr a -> DataFrame -> (DataFrame, DataFrame) stratifiedSplit gen p strataCol df =-    let col = case strataCol of-            Col name -> unsafeGetColumn name df+    let col' = case strataCol of+            Col colName -> unsafeGetColumn colName df             _ -> unwrapTypedColumn (either throw id (interpret @a df strataCol))-        groups = M.elems (groupByIndices col)+        groups = M.elems (groupByIndices col')         go _ [] = (mempty, mempty)         go g (ixs : rest) =             let stratum = rowsAtIndices ixs df
src/DataFrame/Operations/Typing.hs view
@@ -288,6 +288,6 @@ readAsEither :: (Read a) => String -> a readAsEither v = case asum [readMaybe $ "Left " <> s, readMaybe $ "Right " <> s] of     Nothing -> error $ "Couldn't read value: " <> s-    Just v -> v+    Just v' -> v'   where     s = if null v then "\"\"" else v
src/DataFrame/Operators.hs view
@@ -48,7 +48,7 @@ (|>) = (&)  as :: (Columnable a) => Expr a -> T.Text -> NamedExpr-as expr name = (name, UExpr expr)+as expr colName = (colName, UExpr expr)  name :: (Show a) => Expr a -> T.Text name (Col n) = n@@ -71,7 +71,7 @@ liftDecorated ::     (Columnable a, Columnable b) =>     (a -> b) -> T.Text -> Maybe T.Text -> Expr a -> Expr b-liftDecorated f name rep = Unary (MkUnaryOp{unaryFn = f, unaryName = name, unarySymbol = rep})+liftDecorated f opName rep = Unary (MkUnaryOp{unaryFn = f, unaryName = opName, unarySymbol = rep})  lift2Decorated ::     (Columnable c, Columnable b, Columnable a) =>@@ -83,11 +83,11 @@     Expr c ->     Expr b ->     Expr a-lift2Decorated f name rep comm prec =+lift2Decorated f opName rep comm prec =     Binary         ( MkBinaryOp             { binaryFn = f-            , binaryName = name+            , binaryName = opName             , binarySymbol = rep             , binaryCommutative = comm             , binaryPrecedence = prec
src/DataFrame/Synthesis.hs view
@@ -28,12 +28,10 @@ import DataFrame.Operations.Subset (exclude)  import Control.Exception (throw)-import Data.Containers.ListUtils import Data.Function import qualified Data.List as L import qualified Data.Map as M import Data.Maybe (listToMaybe)-import qualified Data.Set as S import qualified Data.Text as T import Data.Type.Equality import qualified Data.Vector.Unboxed as VU@@ -165,20 +163,20 @@     DataFrame ->     [Expr a] ->     [(Expr a, TypedColumn a)]-deduplicate df = go S.empty . nubOrd . L.sortBy (\e1 e2 -> compare (eSize e1) (eSize e2))+deduplicate df = go [] . L.nub . L.sortBy (\e1 e2 -> compare (eSize e1) (eSize e2))   where     go _ [] = []     go seen (x : xs)         | hasInvalid = go seen xs-        | S.member res seen = go seen xs-        | otherwise = (x, res) : go (S.insert res seen) xs+        | res `elem` seen = go seen xs+        | otherwise = (x, res) : go (res : seen) xs       where         res = case interpret @a df x of             Left e -> throw e             Right v -> v         hasInvalid = case res of-            (TColumn (UnboxedColumn _ (col :: VU.Vector b))) -> case testEquality (typeRep @Double) (typeRep @b) of-                Just Refl -> VU.any (\n -> isNaN n || isInfinite n) col+            (TColumn (UnboxedColumn _ (column :: VU.Vector b))) -> case testEquality (typeRep @Double) (typeRep @b) of+                Just Refl -> VU.any (\n -> isNaN n || isInfinite n) column                 Nothing -> False             _ -> False @@ -384,9 +382,9 @@     [(Expr Double, TypedColumn a)] ->     [Expr Double] pickTopN _ _ _ [] = []-pickTopN df (TColumn col) cfg ps =+pickTopN df (TColumn column) cfg ps =     let-        l = case toVector @Double @VU.Vector col of+        l = case toVector @Double @VU.Vector column of             Left e -> throw e             Right v -> v         ordered =@@ -414,14 +412,14 @@                 case toVector @Double @VU.Vector col' of                     Left e -> throw e                     Right v -> VU.convert v-        interpretDoubleVector e =+        interpretDoubleVector e' =             let-                (TColumn col') = case interpret df e of-                    Left e -> throw e+                (TColumn col') = case interpret df e' of+                    Left err -> throw err                     Right v -> v              in                 case toVector @Double @VU.Vector col' of-                    Left e -> throw e+                    Left err -> throw err                     Right v -> VU.convert v      in         trace@@ -441,9 +439,9 @@     [(Expr Bool, TypedColumn Bool)] ->     [Expr Bool] pickTopNBool _ _ [] = []-pickTopNBool df (TColumn col) ps =+pickTopNBool df (TColumn column) ps =     let-        l = case toVector @Double @VU.Vector col of+        l = case toVector @Double @VU.Vector column of             Left e -> throw e             Right v -> v         ordered =@@ -475,10 +473,10 @@         ordered  satisfiesExamples :: DataFrame -> TypedColumn Double -> Expr Double -> Bool-satisfiesExamples df col expr =+satisfiesExamples df column expr =     let         result = case interpret df expr of             Left e -> throw e             Right v -> v      in-        result == col+        result == column
src/DataFrame/Typed/Expr.hs view
@@ -128,12 +128,11 @@ import qualified Data.Text as T import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) +import qualified DataFrame.Functions as F import DataFrame.Internal.Column (Columnable) import DataFrame.Internal.Expression (-    AggStrategy (..),     BinaryOp (..),     Expr (..),-    MeanAcc (..),     NamedExpr,     UExpr (..),     UnaryOp (..),@@ -522,33 +521,22 @@ -------------------------------------------------------------------------------  sum :: (Columnable a, Num a) => TExpr cols a -> TExpr cols a-sum (TExpr e) = TExpr (Agg (FoldAgg "sum" Nothing (+)) e)+sum (TExpr e) = TExpr (F.sum e)  mean :: (Columnable a, Real a) => TExpr cols a -> TExpr cols Double-mean (TExpr e) =-    TExpr-        ( Agg-            ( MergeAgg-                "mean"-                (MeanAcc 0.0 0)-                (\(MeanAcc s c) x -> MeanAcc (s + realToFrac x) (c + 1))-                (\(MeanAcc s1 c1) (MeanAcc s2 c2) -> MeanAcc (s1 + s2) (c1 + c2))-                (\(MeanAcc s c) -> if c == 0 then 0 / 0 else s / fromIntegral c)-            )-            e-        )+mean (TExpr e) = TExpr (F.mean e)  count :: (Columnable a) => TExpr cols a -> TExpr cols Int-count (TExpr e) = TExpr (Agg (MergeAgg "count" (0 :: Int) (\c _ -> c + 1) (+) id) e)+count (TExpr e) = TExpr (F.count e)  minimum :: (Columnable a, Ord a) => TExpr cols a -> TExpr cols a-minimum (TExpr e) = TExpr (Agg (FoldAgg "minimum" Nothing min) e)+minimum (TExpr e) = TExpr (F.minimum e)  maximum :: (Columnable a, Ord a) => TExpr cols a -> TExpr cols a-maximum (TExpr e) = TExpr (Agg (FoldAgg "maximum" Nothing max) e)+maximum (TExpr e) = TExpr (F.maximum e)  collect :: (Columnable a) => TExpr cols a -> TExpr cols [a]-collect (TExpr e) = TExpr (Agg (FoldAgg "collect" (Just []) (flip (:))) e)+collect (TExpr e) = TExpr (F.collect e)  ------------------------------------------------------------------------------- -- Cast / coercion expressions@@ -556,14 +544,14 @@  castExpr ::     forall b cols src.-    (Columnable b, Columnable src) => TExpr cols src -> TExpr cols (Maybe b)+    (Columnable b, Columnable src, Read b) => TExpr cols src -> TExpr cols (Maybe b) castExpr (TExpr e) =     TExpr         (CastExprWith @b @(Maybe b) @src "castExpr" (either (const Nothing) Just) e)  castExprWithDefault ::     forall b cols src.-    (Columnable b, Columnable src) => b -> TExpr cols src -> TExpr cols b+    (Columnable b, Columnable src, Read b) => b -> TExpr cols src -> TExpr cols b castExprWithDefault def (TExpr e) =     TExpr         ( CastExprWith @b @b @src@@ -574,7 +562,8 @@  castExprEither ::     forall b cols src.-    (Columnable b, Columnable src) => TExpr cols src -> TExpr cols (Either T.Text b)+    (Columnable b, Columnable src, Read b) =>+    TExpr cols src -> TExpr cols (Either T.Text b) castExprEither (TExpr e) =     TExpr         ( CastExprWith @b @(Either T.Text b) @src@@ -585,7 +574,7 @@  unsafeCastExpr ::     forall b cols src.-    (Columnable b, Columnable src) => TExpr cols src -> TExpr cols b+    (Columnable b, Columnable src, Read b) => TExpr cols src -> TExpr cols b unsafeCastExpr (TExpr e) =     TExpr         ( CastExprWith @b @b @src@@ -603,9 +592,9 @@ as (TExpr e) name = (name, UExpr e)  -- | Create an ascending sort order from a typed expression.-asc :: (Columnable a) => TExpr cols a -> TSortOrder cols+asc :: (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols asc = Asc  -- | Create a descending sort order from a typed expression.-desc :: (Columnable a) => TExpr cols a -> TSortOrder cols+desc :: (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols desc = Desc
+ src/DataFrame/Typed/Lazy.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++{- |+Module      : DataFrame.Typed.Lazy+Copyright   : (c) 2025+License     : MIT+Stability   : experimental++Type-safe lazy query pipelines.++This module combines the compile-time schema tracking of 'TypedDataFrame'+with the deferred execution of 'LazyDataFrame'. Queries are built as a+logical plan tree with phantom-typed schema tracking; execution is deferred+until 'run' is called.++@+{\-\# LANGUAGE DataKinds, TypeApplications, TypeOperators \#-\}+import qualified DataFrame.Typed.Lazy as TL+import DataFrame.Typed (Column)++type Schema = '[Column \"id\" Int, Column \"name\" Text, Column \"score\" Double]++main = do+    let query = TL.scanCsv \@Schema \"data.csv\"+              & TL.filter (TL.col \@\"score\" TL..>. TL.lit 0.5)+              & TL.select \@'[\"id\", \"name\"]+    df <- TL.run query   -- TypedDataFrame '[Column \"id\" Int, Column \"name\" Text]+    print df+@+-}+module DataFrame.Typed.Lazy (+    -- * Core type+    TypedLazyDataFrame,++    -- * Data sources+    scanCsv,+    scanSeparated,+    scanParquet,+    fromDataFrame,+    fromTypedDataFrame,++    -- * Schema-preserving operations+    filter,+    take,++    -- * Schema-modifying operations+    derive,+    select,++    -- * Aggregation+    groupBy,+    aggregate,++    -- * Joins+    join,++    -- * Sort+    sortBy,++    -- * Execution+    run,++    -- * Re-exports for pipeline construction+    module DataFrame.Typed.Expr,+    module DataFrame.Typed.Types,+    SortOrder (..),+) where++import Data.Kind (Type)+import Data.Proxy (Proxy (..))+import qualified Data.Text as T+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)+import Prelude hiding (filter, take)++import qualified DataFrame.Internal.Column as C+import qualified DataFrame.Internal.Expression as E+import DataFrame.Internal.Schema (Schema)+import DataFrame.Lazy.Internal.DataFrame (LazyDataFrame)+import qualified DataFrame.Lazy.Internal.DataFrame as L+import DataFrame.Lazy.Internal.LogicalPlan (SortOrder (..))+import DataFrame.Operations.Join (JoinType)+import DataFrame.Typed.Expr+import DataFrame.Typed.Freeze (unsafeFreeze)+import DataFrame.Typed.Schema+import DataFrame.Typed.Types++-- | A lazy query with compile-time schema tracking.+newtype TypedLazyDataFrame (cols :: [Type]) = TLD {unTLD :: LazyDataFrame}++instance Show (TypedLazyDataFrame cols) where+    show (TLD ldf) = "TypedLazyDataFrame { " ++ show ldf ++ " }"++-- | Scan a CSV file with a given schema.+scanCsv ::+    Schema ->+    T.Text ->+    TypedLazyDataFrame cols+scanCsv schema path = TLD (L.scanCsv schema path)++-- | Scan a character-separated file with a given schema.+scanSeparated ::+    Char ->+    Schema ->+    T.Text ->+    TypedLazyDataFrame cols+scanSeparated sep schema path = TLD (L.scanSeparated sep schema path)++-- | Scan a Parquet file, directory, or glob pattern with a given schema.+scanParquet ::+    Schema ->+    T.Text ->+    TypedLazyDataFrame cols+scanParquet schema path = TLD (L.scanParquet schema path)++-- | Lift an already-loaded eager 'TypedDataFrame' into a lazy plan.+fromDataFrame :: TypedDataFrame cols -> TypedLazyDataFrame cols+fromDataFrame (TDF df) = TLD (L.fromDataFrame df)++-- | Synonym for 'fromDataFrame'.+fromTypedDataFrame :: TypedDataFrame cols -> TypedLazyDataFrame cols+fromTypedDataFrame = fromDataFrame++-- | Keep rows that satisfy the predicate.+filter :: TExpr cols Bool -> TypedLazyDataFrame cols -> TypedLazyDataFrame cols+filter (TExpr expr) (TLD ldf) = TLD (L.filter expr ldf)++-- | Retain at most @n@ rows.+take :: Int -> TypedLazyDataFrame cols -> TypedLazyDataFrame cols+take n (TLD ldf) = TLD (L.take n ldf)++-- | Add a computed column.+derive ::+    forall name a cols.+    (KnownSymbol name, C.Columnable a, AssertAbsent name cols) =>+    TExpr cols a ->+    TypedLazyDataFrame cols ->+    TypedLazyDataFrame (Snoc cols (Column name a))+derive (TExpr expr) (TLD ldf) =+    TLD (L.derive (T.pack (symbolVal (Proxy @name))) expr ldf)++-- | Retain only the listed columns.+select ::+    forall (names :: [Symbol]) cols.+    (AllKnownSymbol names, AssertAllPresent names cols) =>+    TypedLazyDataFrame cols ->+    TypedLazyDataFrame (SubsetSchema names cols)+select (TLD ldf) = TLD (L.select (DataFrame.Typed.Schema.symbolVals @names) ldf)++-- | A typed lazy grouped query.+newtype TypedLazyGrouped (keys :: [Symbol]) (cols :: [Type]) = TLG+    { unTLG :: ([T.Text], LazyDataFrame)+    }++-- | Group by key columns.+groupBy ::+    forall (keys :: [Symbol]) cols.+    (AllKnownSymbol keys, AssertAllPresent keys cols) =>+    TypedLazyDataFrame cols ->+    TypedLazyGrouped keys cols+groupBy (TLD ldf) = TLG (DataFrame.Typed.Schema.symbolVals @keys, ldf)++-- | Aggregate a grouped lazy query.+aggregate ::+    forall keys cols aggs.+    TAgg keys cols aggs ->+    TypedLazyGrouped keys cols ->+    TypedLazyDataFrame (Append (GroupKeyColumns keys cols) (Reverse aggs))+aggregate tagg (TLG (keys, ldf)) =+    TLD (L.groupBy keys (aggToNamedExprs tagg) ldf)++-- | Join two lazy queries on a shared key column.+join ::+    JoinType ->+    T.Text ->+    T.Text ->+    TypedLazyDataFrame left ->+    TypedLazyDataFrame right ->+    TypedLazyDataFrame left -- TODO: compute join result schema+join jt leftKey rightKey (TLD left) (TLD right) =+    TLD (L.join jt leftKey rightKey left right)++-- | Sort the result by column name and direction.+sortBy ::+    [(T.Text, SortOrder)] ->+    TypedLazyDataFrame cols ->+    TypedLazyDataFrame cols+sortBy cols (TLD ldf) = TLD (L.sortBy cols ldf)++-- | Execute the lazy query and return a typed DataFrame.+run ::+    forall cols.+    (KnownSchema cols) =>+    TypedLazyDataFrame cols ->+    IO (TypedDataFrame cols)+run (TLD ldf) = unsafeFreeze <$> L.runDataFrame ldf++-- | Convert TAgg to untyped named expressions for the lazy groupBy.+aggToNamedExprs :: TAgg keys cols aggs -> [(T.Text, E.UExpr)]+aggToNamedExprs TAggNil = []+aggToNamedExprs (TAggCons name (TExpr expr) rest) =+    (name, E.UExpr expr) : aggToNamedExprs rest
src/DataFrame/Typed/Schema.hs view
@@ -66,7 +66,6 @@ import Type.Reflection (SomeTypeRep, Typeable, someTypeRep)  import DataFrame.Internal.Column (Columnable)-import DataFrame.Internal.Types (If) import DataFrame.Typed.Types (Column)  -- | Look up the element type of a column by name.@@ -121,11 +120,21 @@ type family ExcludeSchema (names :: [Symbol]) (cols :: [Type]) :: [Type] where     ExcludeSchema names '[] = '[]     ExcludeSchema names (Column n a ': rest) =-        If-            (IsElem n names)-            (ExcludeSchema names rest)-            (Column n a ': ExcludeSchema names rest)+        ExcludeSchemaHelper (IsElem n names) n a names rest +type family+    ExcludeSchemaHelper+        (found :: Bool)+        (n :: Symbol)+        (a :: Type)+        (names :: [Symbol])+        (rest :: [Type]) ::+        [Type]+    where+    ExcludeSchemaHelper 'True n a names rest = ExcludeSchema names rest+    ExcludeSchemaHelper 'False n a names rest =+        Column n a ': ExcludeSchema names rest+ -- | Type-level elem for Symbols type family IsElem (x :: Symbol) (xs :: [Symbol]) :: Bool where     IsElem x '[] = 'False@@ -197,14 +206,22 @@ -- | Assert that a column name is present in the schema. type family AssertAllPresent (name :: [Symbol]) (cols :: [Type]) :: Constraint where     AssertAllPresent (name ': rest) cols =-        If-            (HasName name cols)-            (AssertAllPresent rest cols)-            ( TypeError-                ('Text "Column '" ':<>: 'Text name ':<>: 'Text "' not found in schema")-            )+        AssertAllPresentHelper (HasName name cols) name rest cols     AssertAllPresent '[] cols = () +type family+    AssertAllPresentHelper+        (found :: Bool)+        (name :: Symbol)+        (rest :: [Symbol])+        (cols :: [Type]) ::+        Constraint+    where+    AssertAllPresentHelper 'True name rest cols = AssertAllPresent rest cols+    AssertAllPresentHelper 'False name rest cols =+        TypeError+            ('Text "Column '" ':<>: 'Text name ':<>: 'Text "' not found in schema")+ {- | Strip 'Maybe' from all columns. Used by 'filterAllJust'.  @Column "x" (Maybe Double)@ becomes @Column "x" Double@.@@ -232,14 +249,37 @@ type family SharedNames (left :: [Type]) (right :: [Type]) :: [Symbol] where     SharedNames '[] right = '[]     SharedNames (Column n _ ': rest) right =-        If (HasName n right) (n ': SharedNames rest right) (SharedNames rest right)+        SharedNamesHelper (HasName n right) n rest right +type family+    SharedNamesHelper+        (found :: Bool)+        (n :: Symbol)+        (rest :: [Type])+        (right :: [Type]) ::+        [Symbol]+    where+    SharedNamesHelper 'True n rest right = n ': SharedNames rest right+    SharedNamesHelper 'False n rest right = SharedNames rest right+ -- | Columns from @left@ whose names do NOT appear in @right@. type family UniqueLeft (left :: [Type]) (rightNames :: [Symbol]) :: [Type] where     UniqueLeft '[] _ = '[]     UniqueLeft (Column n a ': rest) rn =-        If (IsElem n rn) (UniqueLeft rest rn) (Column n a ': UniqueLeft rest rn)+        UniqueLeftHelper (IsElem n rn) n a rest rn +type family+    UniqueLeftHelper+        (found :: Bool)+        (n :: Symbol)+        (a :: Type)+        (rest :: [Type])+        (rn :: [Symbol]) ::+        [Type]+    where+    UniqueLeftHelper 'True n a rest rn = UniqueLeft rest rn+    UniqueLeftHelper 'False n a rest rn = Column n a ': UniqueLeft rest rn+ -- | Wrap column types in Maybe. type family WrapMaybe (cols :: [Type]) :: [Type] where     WrapMaybe '[] = '[]@@ -249,24 +289,58 @@ type family WrapMaybeColumns (names :: [Symbol]) (cols :: [Type]) :: [Type] where     WrapMaybeColumns names '[] = '[]     WrapMaybeColumns names (Column n a ': rest) =-        If-            (IsElem n names)-            (Column n (Maybe a) ': WrapMaybeColumns names rest)-            (Column n a ': WrapMaybeColumns names rest)+        WrapMaybeColumnsHelper (IsElem n names) n a names rest +type family+    WrapMaybeColumnsHelper+        (found :: Bool)+        (n :: Symbol)+        (a :: Type)+        (names :: [Symbol])+        (rest :: [Type]) ::+        [Type]+    where+    WrapMaybeColumnsHelper 'True n a names rest =+        Column n (Maybe a) ': WrapMaybeColumns names rest+    WrapMaybeColumnsHelper 'False n a names rest =+        Column n a ': WrapMaybeColumns names rest+ -- | Columns in left whose names collide with right (excluding keys). type family CollidingColumns (left :: [Type]) (right :: [Type]) (keys :: [Symbol]) :: [Type] where     CollidingColumns '[] _ _ = '[]     CollidingColumns (Column n a ': rest) right keys =-        If-            (IsElem n keys)-            (CollidingColumns rest right keys)-            ( If-                (HasName n right)-                (Column n (These a (Lookup n right)) ': CollidingColumns rest right keys)-                (CollidingColumns rest right keys)-            )+        CollidingColumnsHelper1 (IsElem n keys) n a rest right keys +type family+    CollidingColumnsHelper1+        (isKey :: Bool)+        (n :: Symbol)+        (a :: Type)+        (rest :: [Type])+        (right :: [Type])+        (keys :: [Symbol]) ::+        [Type]+    where+    CollidingColumnsHelper1 'True n a rest right keys =+        CollidingColumns rest right keys+    CollidingColumnsHelper1 'False n a rest right keys =+        CollidingColumnsHelper2 (HasName n right) n a rest right keys++type family+    CollidingColumnsHelper2+        (inRight :: Bool)+        (n :: Symbol)+        (a :: Type)+        (rest :: [Type])+        (right :: [Type])+        (keys :: [Symbol]) ::+        [Type]+    where+    CollidingColumnsHelper2 'True n a rest right keys =+        Column n (These a (Lookup n right)) ': CollidingColumns rest right keys+    CollidingColumnsHelper2 'False n a rest right keys =+        CollidingColumns rest right keys+ -- | Inner join result schema. type family InnerJoinSchema (keys :: [Symbol]) (left :: [Type]) (right :: [Type]) :: [Type] where     InnerJoinSchema keys left right =@@ -322,18 +396,24 @@                 )             ) ----------------------------------------------------------------------------------- GroupBy helpers--------------------------------------------------------------------------------- -- | Extract Column entries from a schema whose names appear in @keys@. type family GroupKeyColumns (keys :: [Symbol]) (cols :: [Type]) :: [Type] where     GroupKeyColumns keys '[] = '[]     GroupKeyColumns keys (Column n a ': rest) =-        If-            (IsElem n keys)-            (Column n a ': GroupKeyColumns keys rest)-            (GroupKeyColumns keys rest)+        GroupKeyColumnsHelper (IsElem n keys) n a keys rest++type family+    GroupKeyColumnsHelper+        (found :: Bool)+        (n :: Symbol)+        (a :: Type)+        (keys :: [Symbol])+        (rest :: [Type]) ::+        [Type]+    where+    GroupKeyColumnsHelper 'True n a keys rest =+        Column n a ': GroupKeyColumns keys rest+    GroupKeyColumnsHelper 'False n a keys rest = GroupKeyColumns keys rest  -- | Provides runtime evidence of a schema: a list of (name, TypeRep) pairs. class KnownSchema (cols :: [Type]) where
src/DataFrame/Typed/Types.hs view
@@ -73,8 +73,8 @@  -- | A typed sort order validated against schema @cols@. data TSortOrder (cols :: [Type]) where-    Asc :: (Columnable a) => TExpr cols a -> TSortOrder cols-    Desc :: (Columnable a) => TExpr cols a -> TSortOrder cols+    Asc :: (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols+    Desc :: (Columnable a, Ord a) => TExpr cols a -> TSortOrder cols  -- | A phantom-typed wrapper over 'GroupedDataFrame'. newtype TypedGrouped (keys :: [Symbol]) (cols :: [Type])
+ tests/IO/CSV.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module IO.CSV where++import qualified Data.ByteString.Lazy as BL+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified DataFrame as D+import DataFrame.IO.CSV (fromCsv, fromCsvBytes)+import qualified DataFrame.Internal.Column as DI+import DataFrame.Internal.DataFrame (DataFrame (..), toCsv)+import qualified DataFrame.Operations.Core as D+import Test.HUnit (+    Test (TestCase, TestLabel),+    assertEqual,+    assertFailure,+ )++-- | Happy path: parse a simple CSV string with Int and Text columns.+fromCsvHappyPath :: Test+fromCsvHappyPath = TestLabel "fromCsv_happy_path" $ TestCase $ do+    result <- fromCsv "name,age\nAlice,30\nBob,25\nCharlie,35\n"+    case result of+        Left err -> assertFailure $ "Unexpected Left: " ++ err+        Right df -> do+            assertEqual "rows" 3 (D.nRows df)+            assertEqual "columns" 2 (D.nColumns df)++-- | Empty input should return a Left.+fromCsvEmpty :: Test+fromCsvEmpty = TestLabel "fromCsv_empty" $ TestCase $ do+    result <- fromCsv ""+    case result of+        Left _ -> return ()+        Right _ -> assertFailure "Expected Left for empty input"++-- | fromCsvBytes happy path.+fromCsvBytesHappyPath :: Test+fromCsvBytesHappyPath = TestLabel "fromCsvBytes_happy_path" $ TestCase $ do+    let bs = BL.fromStrict (TE.encodeUtf8 "x,y\n1,2\n3,4\n")+    df <- fromCsvBytes bs+    assertEqual "rows" 2 (D.nRows df)+    assertEqual "columns" 2 (D.nColumns df)++-- | Round trip: toCsv then fromCsv preserves data.+fromCsvRoundTrip :: Test+fromCsvRoundTrip = TestLabel "fromCsv_roundTrip" $ TestCase $ do+    let df =+            D.fromNamedColumns+                [ ("a", DI.fromList @Int [1, 2, 3])+                , ("b", DI.fromList @T.Text ["hello", "world", "test"])+                ]+    let csvString = T.unpack (toCsv df)+    result <- fromCsv csvString+    case result of+        Left err -> assertFailure $ "Unexpected Left: " ++ err+        Right df' -> do+            assertEqual+                "round trip dimensions"+                (dataframeDimensions df)+                (dataframeDimensions df')+            assertEqual "round trip data" df df'++-- | Round trip via fromCsvBytes.+fromCsvBytesRoundTrip :: Test+fromCsvBytesRoundTrip = TestLabel "fromCsvBytes_roundTrip" $ TestCase $ do+    let df =+            D.fromNamedColumns+                [ ("x", DI.fromList @Int [10, 20])+                , ("y", DI.fromList @Double [1.5, 2.5])+                ]+    let bs = BL.fromStrict (TE.encodeUtf8 (toCsv df))+    df' <- fromCsvBytes bs+    assertEqual+        "round trip dimensions"+        (dataframeDimensions df)+        (dataframeDimensions df')++-- | Single column CSV.+fromCsvSingleColumn :: Test+fromCsvSingleColumn = TestLabel "fromCsv_single_column" $ TestCase $ do+    result <- fromCsv "id\n10\n20\n30\n"+    case result of+        Left err -> assertFailure $ "Unexpected Left: " ++ err+        Right df -> do+            assertEqual "rows" 3 (D.nRows df)+            assertEqual "columns" 1 (D.nColumns df)++tests :: [Test]+tests =+    [ fromCsvHappyPath+    , fromCsvEmpty+    , fromCsvBytesHappyPath+    , fromCsvRoundTrip+    , fromCsvBytesRoundTrip+    , fromCsvSingleColumn+    ]
tests/Main.hs view
@@ -11,6 +11,7 @@  import qualified DecisionTree import qualified Functions+import qualified IO.CSV import qualified IO.JSON import qualified Internal.Parsing import qualified LazyParquet@@ -62,6 +63,7 @@             ++ Operations.Take.tests             ++ Operations.Typing.tests             ++ Functions.tests+            ++ IO.CSV.tests             ++ IO.JSON.tests             ++ Parquet.tests             ++ LazyParquet.tests
tests/Parquet.hs view
@@ -4,6 +4,7 @@ module Parquet where  import Assertions (assertExpectException)+import Control.Monad (forM_) import qualified DataFrame as D import qualified DataFrame.Functions as F import qualified DataFrame.IO.Parquet as DP@@ -29,7 +30,7 @@     word32ToLittleEndian,     word64ToLittleEndian,  )-import DataFrame.Internal.Column (hasMissing)+import DataFrame.Internal.Column (hasElemType, hasMissing) import DataFrame.Internal.DataFrame (unsafeGetColumn) import GHC.IO (unsafePerformIO) import Test.HUnit@@ -41,6 +42,18 @@         , test (DP._readParquetWithOpts (Just True) D.defaultParquetReadOptions)         ] +assertColumnNullability ::+    String -> [(T.Text, Bool)] -> D.DataFrame -> Assertion+assertColumnNullability label expected df =+    forM_ expected $ \(columnName, shouldBeNullable) ->+        assertBool+            ( label+                <> ": expected "+                <> T.unpack columnName+                <> if shouldBeNullable then " to be nullable" else " to be non-nullable"+            )+            (hasMissing (unsafeGetColumn columnName df) == shouldBeNullable)+ allTypesPlain :: Test allTypesPlain = testBothReadParquetPaths $ \readParquet ->     TestCase@@ -169,6 +182,58 @@             )         ) +safeColumnsWithOpts :: Test+safeColumnsWithOpts =+    TestCase $ do+        defaultDf <- D.readParquet "./tests/data/alltypes_plain.parquet"+        safeDf <-+            D.readParquetWithOpts+                (D.defaultParquetReadOptions{D.safeColumns = True})+                "./tests/data/alltypes_plain.parquet"++        assertEqual+            "safeColumnsWithOpts dimensions"+            (D.dimensions defaultDf)+            (D.dimensions safeDf)+        assertColumnNullability+            "default read"+            [("id", False), ("bool_col", False)]+            defaultDf+        assertColumnNullability+            "safeColumns read"+            [("id", True), ("bool_col", True)]+            safeDf+        assertBool+            "safeColumns id type"+            (hasElemType @(Maybe Int32) (unsafeGetColumn "id" safeDf))+        assertBool+            "safeColumns bool_col type"+            (hasElemType @(Maybe Bool) (unsafeGetColumn "bool_col" safeDf))++safeColumnsWithSelectedColumns :: Test+safeColumnsWithSelectedColumns =+    TestCase $ do+        df <-+            D.readParquetWithOpts+                ( D.defaultParquetReadOptions+                    { D.selectedColumns = Just ["id", "bool_col"]+                    , D.safeColumns = True+                    }+                )+                "./tests/data/alltypes_plain.parquet"++        assertEqual "safeColumnsWithSelectedColumns dimensions" (8, 2) (D.dimensions df)+        assertColumnNullability+            "safeColumns projected read"+            [("id", True), ("bool_col", True)]+            df+        assertBool+            "safeColumns projected id type"+            (hasElemType @(Maybe Int32) (unsafeGetColumn "id" df))+        assertBool+            "safeColumns projected bool_col type"+            (hasElemType @(Maybe Bool) (unsafeGetColumn "bool_col" df))+ predicateWithOptsAcrossFiles :: Test predicateWithOptsAcrossFiles =     TestCase@@ -1029,6 +1094,8 @@     , rowRangeWithOpts     , predicateWithOpts     , predicateUsesNonSelectedColumnWithOpts+    , safeColumnsWithOpts+    , safeColumnsWithSelectedColumns     , predicateWithOptsAcrossFiles     , missingSelectedColumnWithOpts     , mtCars