dataframe 0.4.1.0 → 0.5.0.0
raw patch · 40 files changed
+2003/−1019 lines, 40 filesdep +thesedep ~dataframedep ~randombinary-addedPVP ok
version bump matches the API change (PVP)
Dependencies added: these
Dependency ranges changed: dataframe, random
API changes (from Hackage documentation)
- DataFrame: (|>) :: a -> (a -> b) -> b
- DataFrame: Asc :: Text -> SortOrder
- DataFrame: Desc :: Text -> SortOrder
- DataFrame.Functions: (.&&) :: Expr Bool -> Expr Bool -> Expr Bool
- DataFrame.Functions: (./=) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool
- DataFrame.Functions: (.<) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool
- DataFrame.Functions: (.<=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool
- DataFrame.Functions: (.=) :: Columnable a => Text -> Expr a -> NamedExpr
- DataFrame.Functions: (.==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool
- DataFrame.Functions: (.>) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool
- DataFrame.Functions: (.>=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool
- DataFrame.Functions: (.||) :: Expr Bool -> Expr Bool -> Expr Bool
- DataFrame.Functions: as :: Columnable a => Expr a -> Text -> NamedExpr
- DataFrame.Functions: col :: Columnable a => Text -> Expr a
- DataFrame.Functions: ifThenElse :: Columnable a => Expr Bool -> Expr a -> Expr a -> Expr a
- DataFrame.Functions: infix 4 .>=
- DataFrame.Functions: infixr 0 .=
- DataFrame.Functions: infixr 2 .||
- DataFrame.Functions: infixr 3 .&&
- DataFrame.Functions: lit :: Columnable a => a -> Expr a
- DataFrame.Functions: name :: Show a => Expr a -> Text
- DataFrame.Internal.Expression: [AggFold] :: forall a b. (Columnable a, Columnable b) => Expr b -> Text -> a -> (a -> b -> a) -> Expr a
- DataFrame.Internal.Expression: [AggNumericVector] :: forall a b. (Columnable a, Columnable b, Unbox a, Unbox b, Num a, Num b) => Expr b -> Text -> (Vector b -> a) -> Expr a
- DataFrame.Internal.Expression: [AggReduce] :: forall a. Columnable a => Expr a -> Text -> (a -> a -> a) -> Expr a
- DataFrame.Internal.Expression: [AggVector] :: forall (v :: Type -> Type) b a. (Vector v b, Typeable v, Columnable a, Columnable b) => Expr b -> Text -> (v b -> a) -> Expr a
- DataFrame.Internal.Expression: [BinaryOp] :: forall c b a. (Columnable c, Columnable b, Columnable a) => Text -> (c -> b -> a) -> Expr c -> Expr b -> Expr a
- DataFrame.Internal.Expression: [UnaryOp] :: forall a b. (Columnable a, Columnable b) => Text -> (b -> a) -> Expr b -> Expr a
- DataFrame.Internal.Expression: [Wrap] :: forall a. Columnable a => Expr a -> UExpr
- DataFrame.Internal.Expression: isCommutative :: Text -> Bool
- DataFrame.Operations.Permutation: Asc :: Text -> SortOrder
- DataFrame.Operations.Permutation: Desc :: Text -> SortOrder
+ DataFrame: [Asc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame: [Desc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame: readParquet :: FilePath -> IO DataFrame
+ DataFrame: readParquetFiles :: FilePath -> IO DataFrame
+ DataFrame.Functions: declareColumnsWithPrefix :: Text -> DataFrame -> DecsQ
+ DataFrame.Functions: declareColumnsWithPrefix' :: Maybe Text -> DataFrame -> DecsQ
+ DataFrame.Functions: emptyColumnForType :: TType -> Column
+ DataFrame.Functions: infix 8 `div`
+ DataFrame.Functions: lift2Decorated :: (Columnable c, Columnable b, Columnable a) => (c -> b -> a) -> Text -> Maybe Text -> Bool -> Int -> Expr c -> Expr b -> Expr a
+ DataFrame.Functions: liftDecorated :: (Columnable a, Columnable b) => (a -> b) -> Text -> Maybe Text -> Expr a -> Expr b
+ DataFrame.Functions: schemaElemToColumn :: SchemaElement -> (Text, Column)
+ DataFrame.Functions: schemaToEmptyDataFrame :: [SchemaElement] -> DataFrame
+ DataFrame.IO.CSV: decodeSeparated :: ReadOptions -> ByteString -> IO DataFrame
+ DataFrame.IO.CSV: writeTsv :: FilePath -> DataFrame -> IO ()
+ DataFrame.IO.Parquet: applyLogicalType :: LogicalType -> Column -> Column
+ DataFrame.IO.Parquet: applyScale :: Int32 -> Int32 -> Double
+ DataFrame.IO.Parquet: getColumnPaths :: [SchemaElement] -> [(Text, Int)]
+ DataFrame.IO.Parquet: microsecondsToUTCTime :: Int64 -> UTCTime
+ DataFrame.IO.Parquet: processColumnPages :: (Int, Int) -> [Page] -> ParquetType -> ParquetEncoding -> Maybe Int32 -> LogicalType -> IO Column
+ DataFrame.IO.Parquet: readMetadataFromPath :: FilePath -> IO FileMetadata
+ DataFrame.IO.Parquet: readMetadataSizeFromFooter :: ByteString -> (Int, ByteString)
+ DataFrame.IO.Parquet: unitDivisor :: TimeUnit -> Int64
+ DataFrame.Internal.Column: mergeColumns :: Column -> Column -> Column
+ DataFrame.Internal.Column: vectorFromTypedColumn :: TypedColumn a -> Vector a
+ DataFrame.Internal.Expression: MkBinaryOp :: (a -> b -> c) -> Text -> Maybe Text -> Bool -> Int -> BinaryOp a b c
+ DataFrame.Internal.Expression: MkUnaryOp :: (a -> b) -> Text -> Maybe Text -> UnaryOp a b
+ DataFrame.Internal.Expression: [Agg] :: forall a b. (Columnable a, Columnable b) => AggStrategy a b -> Expr b -> Expr a
+ DataFrame.Internal.Expression: [Binary] :: forall c b a. (Columnable c, Columnable b, Columnable a) => BinaryOp c b a -> Expr c -> Expr b -> Expr a
+ DataFrame.Internal.Expression: [CollectAgg] :: forall (v :: Type -> Type) b a. (Vector v b, Typeable v) => Text -> (v b -> a) -> AggStrategy a b
+ DataFrame.Internal.Expression: [FoldAgg] :: forall a b. Text -> Maybe a -> (a -> b -> a) -> AggStrategy a b
+ DataFrame.Internal.Expression: [UExpr] :: forall a. Columnable a => Expr a -> UExpr
+ DataFrame.Internal.Expression: [Unary] :: forall a b. (Columnable a, Columnable b) => UnaryOp b a -> Expr b -> Expr a
+ DataFrame.Internal.Expression: [binaryCommutative] :: BinaryOp a b c -> Bool
+ DataFrame.Internal.Expression: [binaryFn] :: BinaryOp a b c -> a -> b -> c
+ DataFrame.Internal.Expression: [binaryName] :: BinaryOp a b c -> Text
+ DataFrame.Internal.Expression: [binaryPrecedence] :: BinaryOp a b c -> Int
+ DataFrame.Internal.Expression: [binarySymbol] :: BinaryOp a b c -> Maybe Text
+ DataFrame.Internal.Expression: [unaryFn] :: UnaryOp a b -> a -> b
+ DataFrame.Internal.Expression: [unaryName] :: UnaryOp a b -> Text
+ DataFrame.Internal.Expression: [unarySymbol] :: UnaryOp a b -> Maybe Text
+ DataFrame.Internal.Expression: data AggStrategy a b
+ DataFrame.Internal.Expression: data BinaryOp a b c
+ DataFrame.Internal.Expression: data UnaryOp a b
+ DataFrame.Operations.Permutation: [Asc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame.Operations.Permutation: [Desc] :: forall a. Columnable a => Expr a -> SortOrder
+ DataFrame.Operators: (.&&) :: Expr Bool -> Expr Bool -> Expr Bool
+ DataFrame.Operators: (./=) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool
+ DataFrame.Operators: (.<) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool
+ DataFrame.Operators: (.<=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool
+ DataFrame.Operators: (.=) :: Columnable a => Text -> Expr a -> NamedExpr
+ DataFrame.Operators: (.==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool
+ DataFrame.Operators: (.>) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool
+ DataFrame.Operators: (.>=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool
+ DataFrame.Operators: (.^^) :: (Columnable a, Num a) => Expr a -> Int -> Expr a
+ DataFrame.Operators: (.||) :: Expr Bool -> Expr Bool -> Expr Bool
+ DataFrame.Operators: (|>) :: a -> (a -> b) -> b
+ DataFrame.Operators: as :: Columnable a => Expr a -> Text -> NamedExpr
+ DataFrame.Operators: col :: Columnable a => Text -> Expr a
+ DataFrame.Operators: ifThenElse :: Columnable a => Expr Bool -> Expr a -> Expr a -> Expr a
+ DataFrame.Operators: infix 4 .>=
+ DataFrame.Operators: infix 8 .^^
+ DataFrame.Operators: infixr 0 .=
+ DataFrame.Operators: infixr 2 .||
+ DataFrame.Operators: infixr 3 .&&
+ DataFrame.Operators: lit :: Columnable a => a -> Expr a
+ DataFrame.Operators: name :: Show a => Expr a -> Text
- DataFrame: frequencies :: Text -> DataFrame -> DataFrame
+ DataFrame: frequencies :: Columnable a => Expr a -> DataFrame -> DataFrame
- DataFrame: valueCounts :: Columnable a => Expr a -> DataFrame -> [(a, Int)]
+ DataFrame: valueCounts :: (Ord a, Columnable a) => Expr a -> DataFrame -> [(a, Int)]
- DataFrame: valueProportions :: Columnable a => Expr a -> DataFrame -> [(a, Double)]
+ DataFrame: valueProportions :: (Ord a, Columnable a) => Expr a -> DataFrame -> [(a, Double)]
- DataFrame.Functions: mode :: (Columnable a, Eq a) => Expr a -> Expr a
+ DataFrame.Functions: mode :: (Ord a, Columnable a, Eq a) => Expr a -> Expr a
- DataFrame.Functions: parseDate :: Text -> Expr Text -> Expr (Maybe Day)
+ DataFrame.Functions: parseDate :: (ParseTime t, Columnable t) => Text -> Expr Text -> Expr (Maybe t)
- DataFrame.Functions: relu :: (Columnable a, Num a) => Expr a -> Expr a
+ DataFrame.Functions: relu :: (Columnable a, Num a, Ord a) => Expr a -> Expr a
- DataFrame.IO.Parquet.Binary: littleEndianInt32 :: [Word8] -> Int32
+ DataFrame.IO.Parquet.Binary: littleEndianInt32 :: ByteString -> Int32
- DataFrame.IO.Parquet.Binary: littleEndianWord32 :: [Word8] -> Word32
+ DataFrame.IO.Parquet.Binary: littleEndianWord32 :: ByteString -> Word32
- DataFrame.IO.Parquet.Binary: littleEndianWord64 :: [Word8] -> Word64
+ DataFrame.IO.Parquet.Binary: littleEndianWord64 :: ByteString -> Word64
- DataFrame.IO.Parquet.Binary: readByteString :: ByteString -> IORef Int -> IO [Word8]
+ DataFrame.IO.Parquet.Binary: readByteString :: ByteString -> IORef Int -> IO ByteString
- DataFrame.IO.Parquet.Binary: readByteString' :: ByteString -> Int64 -> IO [Word8]
+ DataFrame.IO.Parquet.Binary: readByteString' :: ByteString -> Int64 -> IO ByteString
- DataFrame.IO.Parquet.Binary: readByteStringFromBytes :: [Word8] -> ([Word8], [Word8])
+ DataFrame.IO.Parquet.Binary: readByteStringFromBytes :: ByteString -> (ByteString, ByteString)
- DataFrame.IO.Parquet.Binary: readInt32FromBytes :: [Word8] -> (Int32, [Word8])
+ DataFrame.IO.Parquet.Binary: readInt32FromBytes :: ByteString -> (Int32, ByteString)
- DataFrame.IO.Parquet.Binary: readIntFromBytes :: Integral a => [Word8] -> (a, [Word8])
+ DataFrame.IO.Parquet.Binary: readIntFromBytes :: Integral a => ByteString -> (a, ByteString)
- DataFrame.IO.Parquet.Binary: readUVarInt :: [Word8] -> (Word64, [Word8])
+ DataFrame.IO.Parquet.Binary: readUVarInt :: ByteString -> (Word64, ByteString)
- DataFrame.IO.Parquet.Binary: readVarIntFromBytes :: Integral a => [Word8] -> (a, [Word8])
+ DataFrame.IO.Parquet.Binary: readVarIntFromBytes :: Integral a => ByteString -> (a, ByteString)
- DataFrame.IO.Parquet.Binary: word32ToLittleEndian :: Word32 -> [Word8]
+ DataFrame.IO.Parquet.Binary: word32ToLittleEndian :: Word32 -> ByteString
- DataFrame.IO.Parquet.Binary: word64ToLittleEndian :: Word64 -> [Word8]
+ DataFrame.IO.Parquet.Binary: word64ToLittleEndian :: Word64 -> ByteString
- DataFrame.IO.Parquet.ColumnStatistics: ColumnStatistics :: [Word8] -> [Word8] -> Int64 -> Int64 -> [Word8] -> [Word8] -> Bool -> Bool -> ColumnStatistics
+ DataFrame.IO.Parquet.ColumnStatistics: ColumnStatistics :: ByteString -> ByteString -> Int64 -> Int64 -> ByteString -> ByteString -> Bool -> Bool -> ColumnStatistics
- DataFrame.IO.Parquet.ColumnStatistics: [columnMaxValue] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.ColumnStatistics: [columnMaxValue] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.ColumnStatistics: [columnMax] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.ColumnStatistics: [columnMax] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.ColumnStatistics: [columnMinValue] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.ColumnStatistics: [columnMinValue] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.ColumnStatistics: [columnMin] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.ColumnStatistics: [columnMin] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.Dictionary: decodeDictV1 :: Maybe DictVals -> Int -> [Int] -> Int -> [Word8] -> IO Column
+ DataFrame.IO.Parquet.Dictionary: decodeDictV1 :: Maybe DictVals -> Int -> [Int] -> Int -> ByteString -> IO Column
- DataFrame.IO.Parquet.Dictionary: readDictVals :: ParquetType -> [Word8] -> Maybe Int32 -> DictVals
+ DataFrame.IO.Parquet.Dictionary: readDictVals :: ParquetType -> ByteString -> Maybe Int32 -> DictVals
- DataFrame.IO.Parquet.Dictionary: readNInt96Times :: Int -> [Word8] -> ([UTCTime], [Word8])
+ DataFrame.IO.Parquet.Dictionary: readNInt96Times :: Int -> ByteString -> ([UTCTime], ByteString)
- DataFrame.IO.Parquet.Dictionary: readPageBool :: [Word8] -> [Bool]
+ DataFrame.IO.Parquet.Dictionary: readPageBool :: ByteString -> [Bool]
- DataFrame.IO.Parquet.Dictionary: readPageBytes :: [Word8] -> [Text]
+ DataFrame.IO.Parquet.Dictionary: readPageBytes :: ByteString -> [Text]
- DataFrame.IO.Parquet.Dictionary: readPageFixedBytes :: [Word8] -> Int -> [Text]
+ DataFrame.IO.Parquet.Dictionary: readPageFixedBytes :: ByteString -> Int -> [Text]
- DataFrame.IO.Parquet.Dictionary: readPageFloat :: [Word8] -> [Float]
+ DataFrame.IO.Parquet.Dictionary: readPageFloat :: ByteString -> [Float]
- DataFrame.IO.Parquet.Dictionary: readPageInt32 :: [Word8] -> [Int32]
+ DataFrame.IO.Parquet.Dictionary: readPageInt32 :: ByteString -> [Int32]
- DataFrame.IO.Parquet.Dictionary: readPageInt64 :: [Word8] -> [Int64]
+ DataFrame.IO.Parquet.Dictionary: readPageInt64 :: ByteString -> [Int64]
- DataFrame.IO.Parquet.Dictionary: readPageInt96Times :: [Word8] -> [UTCTime]
+ DataFrame.IO.Parquet.Dictionary: readPageInt96Times :: ByteString -> [UTCTime]
- DataFrame.IO.Parquet.Dictionary: readPageWord64 :: [Word8] -> [Double]
+ DataFrame.IO.Parquet.Dictionary: readPageWord64 :: ByteString -> [Double]
- DataFrame.IO.Parquet.Encoding: decodeDictIndicesV1 :: Int -> Int -> [Word8] -> ([Int], [Word8])
+ DataFrame.IO.Parquet.Encoding: decodeDictIndicesV1 :: Int -> Int -> ByteString -> ([Int], ByteString)
- DataFrame.IO.Parquet.Encoding: decodeRLEBitPackedHybrid :: Int -> Int -> [Word8] -> ([Word32], [Word8])
+ DataFrame.IO.Parquet.Encoding: decodeRLEBitPackedHybrid :: Int -> Int -> ByteString -> ([Word32], ByteString)
- DataFrame.IO.Parquet.Encoding: unpackBitPacked :: Int -> Int -> [Word8] -> ([Word32], [Word8])
+ DataFrame.IO.Parquet.Encoding: unpackBitPacked :: Int -> Int -> ByteString -> ([Word32], ByteString)
- DataFrame.IO.Parquet.Levels: readLevelsV1 :: Int -> Int -> Int -> [Word8] -> ([Int], [Int], [Word8])
+ DataFrame.IO.Parquet.Levels: readLevelsV1 :: Int -> Int -> Int -> ByteString -> ([Int], [Int], ByteString)
- DataFrame.IO.Parquet.Levels: readLevelsV2 :: Int -> Int -> Int -> Int32 -> Int32 -> [Word8] -> ([Int], [Int], [Word8])
+ DataFrame.IO.Parquet.Levels: readLevelsV2 :: Int -> Int -> Int -> Int32 -> Int32 -> ByteString -> ([Int], [Int], ByteString)
- DataFrame.IO.Parquet.Page: readAllPages :: CompressionCodec -> [Word8] -> IO [Page]
+ DataFrame.IO.Parquet.Page: readAllPages :: CompressionCodec -> ByteString -> IO [Page]
- DataFrame.IO.Parquet.Page: readField' :: [Word8] -> Int16 -> Maybe ([Word8], TType, Int16)
+ DataFrame.IO.Parquet.Page: readField' :: ByteString -> Int16 -> Maybe (ByteString, TType, Int16)
- DataFrame.IO.Parquet.Page: readNBool :: Int -> [Word8] -> ([Bool], [Word8])
+ DataFrame.IO.Parquet.Page: readNBool :: Int -> ByteString -> ([Bool], ByteString)
- DataFrame.IO.Parquet.Page: readNByteArrays :: Int -> [Word8] -> ([[Word8]], [Word8])
+ DataFrame.IO.Parquet.Page: readNByteArrays :: Int -> ByteString -> ([ByteString], ByteString)
- DataFrame.IO.Parquet.Page: readNDouble :: Int -> [Word8] -> ([Double], [Word8])
+ DataFrame.IO.Parquet.Page: readNDouble :: Int -> ByteString -> ([Double], ByteString)
- DataFrame.IO.Parquet.Page: readNFloat :: Int -> [Word8] -> ([Float], [Word8])
+ DataFrame.IO.Parquet.Page: readNFloat :: Int -> ByteString -> ([Float], ByteString)
- DataFrame.IO.Parquet.Page: readNInt32 :: Int -> [Word8] -> ([Int32], [Word8])
+ DataFrame.IO.Parquet.Page: readNInt32 :: Int -> ByteString -> ([Int32], ByteString)
- DataFrame.IO.Parquet.Page: readNInt64 :: Int -> [Word8] -> ([Int64], [Word8])
+ DataFrame.IO.Parquet.Page: readNInt64 :: Int -> ByteString -> ([Int64], ByteString)
- DataFrame.IO.Parquet.Page: readPage :: CompressionCodec -> [Word8] -> IO (Maybe Page, [Word8])
+ DataFrame.IO.Parquet.Page: readPage :: CompressionCodec -> ByteString -> IO (Maybe Page, ByteString)
- DataFrame.IO.Parquet.Page: readPageHeader :: PageHeader -> [Word8] -> Int16 -> (PageHeader, [Word8])
+ DataFrame.IO.Parquet.Page: readPageHeader :: PageHeader -> ByteString -> Int16 -> (PageHeader, ByteString)
- DataFrame.IO.Parquet.Page: readPageTypeHeader :: PageTypeHeader -> [Word8] -> Int16 -> (PageTypeHeader, [Word8])
+ DataFrame.IO.Parquet.Page: readPageTypeHeader :: PageTypeHeader -> ByteString -> Int16 -> (PageTypeHeader, ByteString)
- DataFrame.IO.Parquet.Page: readStatisticsFromBytes :: ColumnStatistics -> [Word8] -> Int16 -> (ColumnStatistics, [Word8])
+ DataFrame.IO.Parquet.Page: readStatisticsFromBytes :: ColumnStatistics -> ByteString -> Int16 -> (ColumnStatistics, ByteString)
- DataFrame.IO.Parquet.Page: splitFixed :: Int -> Int -> [Word8] -> ([[Word8]], [Word8])
+ DataFrame.IO.Parquet.Page: splitFixed :: Int -> Int -> ByteString -> ([ByteString], ByteString)
- DataFrame.IO.Parquet.Thrift: ColumnChunk :: String -> Int64 -> ColumnMetaData -> Int64 -> Int32 -> Int64 -> Int32 -> ColumnCryptoMetadata -> [Word8] -> ColumnChunk
+ DataFrame.IO.Parquet.Thrift: ColumnChunk :: String -> Int64 -> ColumnMetaData -> Int64 -> Int32 -> Int64 -> Int32 -> ColumnCryptoMetadata -> ByteString -> ColumnChunk
- DataFrame.IO.Parquet.Thrift: FileMetaData :: Int32 -> [SchemaElement] -> Integer -> [RowGroup] -> [KeyValue] -> Maybe String -> [ColumnOrder] -> EncryptionAlgorithm -> [Word8] -> FileMetadata
+ DataFrame.IO.Parquet.Thrift: FileMetaData :: Int32 -> [SchemaElement] -> Integer -> [RowGroup] -> [KeyValue] -> Maybe String -> [ColumnOrder] -> EncryptionAlgorithm -> ByteString -> FileMetadata
- DataFrame.IO.Parquet.Thrift: [encryptedColumnMetadata] :: ColumnChunk -> [Word8]
+ DataFrame.IO.Parquet.Thrift: [encryptedColumnMetadata] :: ColumnChunk -> ByteString
- DataFrame.IO.Parquet.Thrift: [footerSigningKeyMetadata] :: FileMetadata -> [Word8]
+ DataFrame.IO.Parquet.Thrift: [footerSigningKeyMetadata] :: FileMetadata -> ByteString
- DataFrame.IO.Parquet.Time: int96ToUTCTime :: [Word8] -> UTCTime
+ DataFrame.IO.Parquet.Time: int96ToUTCTime :: ByteString -> UTCTime
- DataFrame.IO.Parquet.Time: utcTimeToInt96 :: UTCTime -> [Word8]
+ DataFrame.IO.Parquet.Time: utcTimeToInt96 :: UTCTime -> ByteString
- DataFrame.IO.Parquet.Types: AesGcmCtrV1 :: [Word8] -> [Word8] -> Bool -> EncryptionAlgorithm
+ DataFrame.IO.Parquet.Types: AesGcmCtrV1 :: ByteString -> ByteString -> Bool -> EncryptionAlgorithm
- DataFrame.IO.Parquet.Types: AesGcmV1 :: [Word8] -> [Word8] -> Bool -> EncryptionAlgorithm
+ DataFrame.IO.Parquet.Types: AesGcmV1 :: ByteString -> ByteString -> Bool -> EncryptionAlgorithm
- DataFrame.IO.Parquet.Types: ColumnStatistics :: [Word8] -> [Word8] -> Int64 -> Int64 -> [Word8] -> [Word8] -> Bool -> Bool -> ColumnStatistics
+ DataFrame.IO.Parquet.Types: ColumnStatistics :: ByteString -> ByteString -> Int64 -> Int64 -> ByteString -> ByteString -> Bool -> Bool -> ColumnStatistics
- DataFrame.IO.Parquet.Types: EncryptionWithColumnKey :: [String] -> [Word8] -> ColumnCryptoMetadata
+ DataFrame.IO.Parquet.Types: EncryptionWithColumnKey :: [String] -> ByteString -> ColumnCryptoMetadata
- DataFrame.IO.Parquet.Types: Page :: PageHeader -> [Word8] -> Page
+ DataFrame.IO.Parquet.Types: Page :: PageHeader -> ByteString -> Page
- DataFrame.IO.Parquet.Types: [aadFileUnique] :: EncryptionAlgorithm -> [Word8]
+ DataFrame.IO.Parquet.Types: [aadFileUnique] :: EncryptionAlgorithm -> ByteString
- DataFrame.IO.Parquet.Types: [aadPrefix] :: EncryptionAlgorithm -> [Word8]
+ DataFrame.IO.Parquet.Types: [aadPrefix] :: EncryptionAlgorithm -> ByteString
- DataFrame.IO.Parquet.Types: [columnKeyMetadata] :: ColumnCryptoMetadata -> [Word8]
+ DataFrame.IO.Parquet.Types: [columnKeyMetadata] :: ColumnCryptoMetadata -> ByteString
- DataFrame.IO.Parquet.Types: [columnMaxValue] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.Types: [columnMaxValue] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.Types: [columnMax] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.Types: [columnMax] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.Types: [columnMinValue] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.Types: [columnMinValue] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.Types: [columnMin] :: ColumnStatistics -> [Word8]
+ DataFrame.IO.Parquet.Types: [columnMin] :: ColumnStatistics -> ByteString
- DataFrame.IO.Parquet.Types: [pageBytes] :: Page -> [Word8]
+ DataFrame.IO.Parquet.Types: [pageBytes] :: Page -> ByteString
- DataFrame.Operations.Core: valueCounts :: Columnable a => Expr a -> DataFrame -> [(a, Int)]
+ DataFrame.Operations.Core: valueCounts :: (Ord a, Columnable a) => Expr a -> DataFrame -> [(a, Int)]
- DataFrame.Operations.Core: valueProportions :: Columnable a => Expr a -> DataFrame -> [(a, Double)]
+ DataFrame.Operations.Core: valueProportions :: (Ord a, Columnable a) => Expr a -> DataFrame -> [(a, Double)]
- DataFrame.Operations.Statistics: frequencies :: Text -> DataFrame -> DataFrame
+ DataFrame.Operations.Statistics: frequencies :: Columnable a => Expr a -> DataFrame -> DataFrame
Files
- CHANGELOG.md +13/−0
- app/Synthesis.hs +1/−2
- benchmark/Main.hs +3/−3
- dataframe.cabal +16/−10
- src/DataFrame.hs +7/−9
- src/DataFrame/DecisionTree.hs +3/−3
- src/DataFrame/Display/Terminal/PrettyPrint.hs +1/−2
- src/DataFrame/Functions.hs +186/−125
- src/DataFrame/IO/CSV.hs +9/−2
- src/DataFrame/IO/Parquet.hs +55/−13
- src/DataFrame/IO/Parquet/Binary.hs +48/−32
- src/DataFrame/IO/Parquet/ColumnStatistics.hs +7/−7
- src/DataFrame/IO/Parquet/Dictionary.hs +50/−41
- src/DataFrame/IO/Parquet/Encoding.hs +30/−18
- src/DataFrame/IO/Parquet/Levels.hs +18/−10
- src/DataFrame/IO/Parquet/Page.hs +291/−262
- src/DataFrame/IO/Parquet/Thrift.hs +45/−26
- src/DataFrame/IO/Parquet/Time.hs +6/−5
- src/DataFrame/IO/Parquet/Types.hs +12/−12
- src/DataFrame/Internal/Column.hs +90/−6
- src/DataFrame/Internal/Expression.hs +190/−168
- src/DataFrame/Internal/Interpreter.hs +81/−188
- src/DataFrame/Operations/Aggregation.hs +7/−1
- src/DataFrame/Operations/Core.hs +3/−2
- src/DataFrame/Operations/Join.hs +24/−3
- src/DataFrame/Operations/Permutation.hs +18/−7
- src/DataFrame/Operations/Statistics.hs +7/−12
- src/DataFrame/Operations/Subset.hs +61/−11
- src/DataFrame/Operations/Transformations.hs +2/−2
- src/DataFrame/Operators.hs +161/−0
- src/DataFrame/Synthesis.hs +1/−1
- tests/GenDataFrame.hs +2/−2
- tests/Main.hs +8/−1
- tests/Monad.hs +27/−0
- tests/Operations/Aggregations.hs +34/−12
- tests/Operations/Join.hs +101/−5
- tests/Operations/Shuffle.hs +84/−0
- tests/Operations/Sort.hs +7/−5
- tests/Parquet.hs +294/−11
- tests/data/transactions.parquet binary
CHANGELOG.md view
@@ -1,5 +1,18 @@ # Revision history for dataframe +## 0.5.0.0+* SortOrder now takes an expression rather than a string.+* selectBy now respects the original column order.+* Some changes to the internal representation of the expression GADT.+* Added fixity to binary operations for column comparisons.+* Fixed ZSTD decompression for Parquet files.+* Added logical type handling for timestamps and decimals.+* aggregation now handles null dataframes gracefully.+* Drop random dep to 1.2 and use random-shuffle for shuffling instead.+* Join now merges columns to a `These`.+* Added writeTsv function.+* Move expression operators to `Operators` namespace.+ ## 0.4.1.0 * Improve signal handling of dataframe repl. * `writeCsv` not correctly writes `Maybe` values (thanks to @mcoady).
app/Synthesis.hs view
@@ -8,9 +8,8 @@ import qualified DataFrame.Functions as F import Data.Char-import DataFrame ((|>)) import DataFrame.DecisionTree-import DataFrame.Functions ((.&&), (.=), (.==))+import DataFrame.Operators hiding (name) import System.Random
benchmark/Main.hs view
@@ -6,7 +6,7 @@ import Control.Monad (void) import Criterion.Main-import DataFrame ((|>))+import DataFrame.Operators import System.Process haskell :: IO ()@@ -46,9 +46,9 @@ |> D.groupBy ["ocean_proximity"] |> D.aggregate [ F.minimum (F.col @Double "median_house_value")- `F.as` "minimum_median_house_value"+ `as` "minimum_median_house_value" , F.maximum (F.col @Double "median_house_value")- `F.as` "maximum_median_house_value"+ `as` "maximum_median_house_value" ] groupByPolars :: IO ()
dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: dataframe-version: 0.4.1.0+version: 0.5.0.0 synopsis: A fast, safe, and intuitive DataFrame library. @@ -65,6 +65,7 @@ DataFrame.Operations.Core, DataFrame.Operations.Join, DataFrame.Operations.Merge,+ DataFrame.Operators, DataFrame.Operations.Permutation, DataFrame.Operations.Subset, DataFrame.Operations.Statistics,@@ -104,11 +105,13 @@ hashable >= 1.2 && < 2, process ^>= 1.6, snappy-hs ^>= 0.1,- random >= 1.3 && < 2,+ random >= 1.2 && < 1.3,+ random-shuffle >= 0.0.4 && < 1, regex-tdfa >= 1.3.0 && < 2, scientific >=0.3.1 && <0.4, template-haskell >= 2.0 && < 3, text >= 2.0 && < 3,+ these >= 1.1 && < 2, time >= 1.12 && < 2, unordered-containers >= 0.1 && < 1, vector ^>= 0.13,@@ -131,7 +134,7 @@ import: warnings main-is: Benchmark.hs build-depends: base >= 4 && < 5,- dataframe ^>= 0.4,+ dataframe >= 0.5 && < 1, random >= 1 && < 2, time >= 1.12 && < 2, vector ^>= 0.13,@@ -143,7 +146,7 @@ import: warnings main-is: Synthesis.hs build-depends: base >= 4 && < 5,- dataframe ^>= 0.4,+ dataframe >= 0.5 && < 1, random >= 1 && < 2, text >= 2.0 && < 3 hs-source-dirs: app@@ -157,8 +160,9 @@ directory >= 1.3.0.0 && < 2, filepath >= 1.4 && < 2, process >= 1.6 && < 2,- time >= 1.12 && < 2,- unix >= 2 && < 3+ time >= 1.12 && < 2+ if !os(windows)+ build-depends: unix >= 2 && < 3 hs-source-dirs: app default-language: Haskell2010 ghc-options: -rtsopts -threaded -with-rtsopts=-N@@ -171,7 +175,7 @@ build-depends: base >= 4 && < 5, criterion >= 1 && < 2, process >= 1.6 && < 2,- dataframe ^>= 0.4+ dataframe >= 0.5 && < 1 default-language: Haskell2010 ghc-options: -threaded@@ -195,20 +199,22 @@ Operations.Join, Operations.Merge, Operations.ReadCsv,+ Operations.Shuffle, Operations.Sort, Operations.Subset, Operations.Statistics, Operations.Take,- Parquet+ Parquet,+ Monad build-depends: base >= 4 && < 5,- dataframe ^>= 0.4,+ dataframe >= 0.5 && < 1, directory >= 1.3.0.0 && < 2, HUnit ^>= 1.6, QuickCheck >= 2 && < 3,- random >= 1 && < 2, random-shuffle >= 0.0.4 && < 1, random >= 1 && < 2, text >= 2.0 && < 3,+ these >= 1.1 && < 2, time >= 1.12 && < 2, vector ^>= 0.13, containers >= 0.6.7 && < 0.9
src/DataFrame.hs view
@@ -69,7 +69,7 @@ longitude | 20640 | 0 | 0 | 844 | Double -- 2) Project & filter-ghci> :exposeColumn df+ghci> :declareColumns df ghci> df1 = D.filterWhere (ocean_proximity .== \"ISLAND\") df0 D.|> D.select [F.name median_house_value, F.name median_income, F.name ocean_proximity] -- 3) Add a derived column using the expression DSL@@ -77,10 +77,11 @@ ghci> df2 = D.derive "rooms_per_household" (total_rooms / households) df0 -- 4) Group + aggregate+ghci> import DataFrame.Operators ghci> let grouped = D.groupBy ["ocean_proximity"] df0 ghci> let summary = D.aggregate- [ F.maximum median_house_value \`F.as\` "max_house_value"]+ [ F.maximum median_house_value \`as\` "max_house_value"] grouped ghci> D.take 5 summary ----------------------------------@@ -198,6 +199,9 @@ module Row, module Expression, + -- * Operator symbols.+ module Operators,+ -- * Display operations module Display, @@ -226,9 +230,6 @@ -- * Plotting module Plot,-- -- * Convenience functions- (|>), ) where @@ -352,7 +353,4 @@ takeLast, ) import DataFrame.Operations.Transformations as Transformations--import Data.Function ((&))--(|>) = (&)+import DataFrame.Operators as Operators
src/DataFrame/DecisionTree.hs view
@@ -30,7 +30,7 @@ import qualified Data.Vector.Unboxed as VU import Type.Reflection (typeRep) -import DataFrame.Functions ((./=), (.<), (.<=), (.==), (.>), (.>=))+import DataFrame.Operators data TreeConfig = TreeConfig { maxTreeDepth :: Int@@ -466,8 +466,8 @@ (If condInner tInner _, _) | cond == condInner -> If cond tInner f (_, If condInner _ fInner) | cond == condInner -> If cond t fInner _ -> If cond t f-pruneExpr (UnaryOp name op e) = UnaryOp name op (pruneExpr e)-pruneExpr (BinaryOp name op l r) = BinaryOp name op (pruneExpr l) (pruneExpr r)+pruneExpr (Unary op e) = Unary op (pruneExpr e)+pruneExpr (Binary op l r) = Binary op (pruneExpr l) (pruneExpr r) pruneExpr e = e buildGreedyTree ::
src/DataFrame/Display/Terminal/PrettyPrint.hs view
@@ -65,8 +65,7 @@ lines = if properMarkdown then- T.concat [" ", border, " "]- : T.concat ["| ", fillCols colTitleFill consolidatedHeader, " |"]+ T.concat ["| ", fillCols colTitleFill consolidatedHeader, " |"] : T.concat ["| ", separator, " |"] : map ((\t -> T.concat ["| ", t, " |"]) . fillCols colValueFill) rows else
src/DataFrame/Functions.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}@@ -12,19 +13,16 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-} -module DataFrame.Functions where+module DataFrame.Functions (module DataFrame.Functions, module DataFrame.Operators) where import DataFrame.Internal.Column import DataFrame.Internal.DataFrame ( DataFrame (..), unsafeGetColumn, )-import DataFrame.Internal.Expression (- Expr (..),- NamedExpr,- UExpr (..),- )+import DataFrame.Internal.Expression hiding (normalize) import DataFrame.Internal.Statistics+import DataFrame.Operations.Core import Control.Applicative import Control.Monad@@ -32,6 +30,7 @@ import qualified Data.Char as Char import Data.Function import Data.Functor+import Data.Int import qualified Data.List as L import qualified Data.Map as M import qualified Data.Maybe as Maybe@@ -39,8 +38,11 @@ import Data.Time import qualified Data.Vector as V import qualified Data.Vector.Unboxed as VU+import Data.Word import qualified DataFrame.IO.CSV as CSV import qualified DataFrame.IO.Parquet as Parquet+import DataFrame.IO.Parquet.Thrift+import DataFrame.Operators import Debug.Trace (trace) import Language.Haskell.TH import qualified Language.Haskell.TH.Syntax as TH@@ -48,147 +50,143 @@ import Prelude hiding (maximum, minimum) import Prelude as P -infix 4 .==, .<, .<=, .>=, .>, ./=-infixr 3 .&&-infixr 2 .||--name :: (Show a) => Expr a -> T.Text-name (Col n) = n-name other =- error $- "You must call `name` on a column reference. Not the expression: " ++ show other--col :: (Columnable a) => T.Text -> Expr a-col = Col--as :: (Columnable a) => Expr a -> T.Text -> NamedExpr-as expr name = (name, Wrap expr)--infixr 0 .=-(.=) :: (Columnable a) => T.Text -> Expr a -> NamedExpr-(.=) = flip as--ifThenElse :: (Columnable a) => Expr Bool -> Expr a -> Expr a -> Expr a-ifThenElse = If--lit :: (Columnable a) => a -> Expr a-lit = Lit- lift :: (Columnable a, Columnable b) => (a -> b) -> Expr a -> Expr b-lift = UnaryOp "udf"+lift f =+ Unary (MkUnaryOp{unaryFn = f, unaryName = "unaryUdf", unarySymbol = Nothing}) lift2 :: (Columnable c, Columnable b, Columnable a) => (c -> b -> a) -> Expr c -> Expr b -> Expr a-lift2 = BinaryOp "udf"+lift2 f =+ Binary+ ( MkBinaryOp+ { binaryFn = f+ , binaryName = "binaryUdf"+ , binarySymbol = Nothing+ , binaryCommutative = False+ , binaryPrecedence = 0+ }+ ) +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})++lift2Decorated ::+ (Columnable c, Columnable b, Columnable a) =>+ (c -> b -> a) ->+ T.Text ->+ Maybe T.Text ->+ Bool ->+ Int ->+ Expr c ->+ Expr b ->+ Expr a+lift2Decorated f name rep comm prec =+ Binary+ ( MkBinaryOp+ { binaryFn = f+ , binaryName = name+ , binarySymbol = rep+ , binaryCommutative = comm+ , binaryPrecedence = prec+ }+ )+ toDouble :: (Columnable a, Real a) => Expr a -> Expr Double-toDouble = UnaryOp "toDouble" realToFrac+toDouble =+ Unary+ ( MkUnaryOp+ { unaryFn = realToFrac+ , unaryName = "toDouble"+ , unarySymbol = Nothing+ }+ ) +infix 8 `div` div :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a-div = BinaryOp "div" Prelude.div+div = lift2Decorated Prelude.div "div" (Just "//") False 7 mod :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a-mod = BinaryOp "mod" Prelude.mod--(.==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool-(.==) = BinaryOp "eq" (==)--(./=) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool-(./=) = BinaryOp "neq" (/=)+mod = lift2Decorated Prelude.mod "mod" Nothing False 7 eq :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool-eq = BinaryOp "eq" (==)--(.<) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-(.<) = BinaryOp "lt" (<)+eq = (.==) lt :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-lt = BinaryOp "lt" (<)---- TODO: Generalize this pattern for other equality functions.-(.>) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-(.>) = BinaryOp "gt" (>)+lt = (.<) gt :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool gt = (.>) -(.<=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool-(.<=) = BinaryOp "leq" (<=)- leq :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool leq = (.<=) -(.>=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool-(.>=) = BinaryOp "geq" (>=)- geq :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool-geq = BinaryOp "geq" (>=)+geq = (.>=) and :: Expr Bool -> Expr Bool -> Expr Bool-and = BinaryOp "and" (&&)--(.&&) :: Expr Bool -> Expr Bool -> Expr Bool-(.&&) = BinaryOp "and" (&&)+and = (.&&) or :: Expr Bool -> Expr Bool -> Expr Bool-or = BinaryOp "or" (||)--(.||) :: Expr Bool -> Expr Bool -> Expr Bool-(.||) = BinaryOp "or" (||)+or = (.||) not :: Expr Bool -> Expr Bool-not = UnaryOp "not" Prelude.not+not =+ Unary+ (MkUnaryOp{unaryFn = Prelude.not, unaryName = "not", unarySymbol = Just "~"}) count :: (Columnable a) => Expr a -> Expr Int-count expr = AggFold expr "count" 0 (\acc _ -> acc + 1)+count = Agg (FoldAgg "count" (Just 0) (\acc _ -> acc + 1)) collect :: (Columnable a) => Expr a -> Expr [a]-collect expr = AggFold expr "collect" [] (flip (:))+collect = Agg (FoldAgg "collect" (Just []) (flip (:))) -mode :: (Columnable a, Eq a) => Expr a -> Expr a-mode expr =- AggVector- expr- "mode"- ( fst- . L.maximumBy (compare `on` snd)- . M.toList- . V.foldl' (\m e -> M.insertWith (+) e 1 m) M.empty+mode :: (Ord a, Columnable a, Eq a) => Expr a -> Expr a+mode =+ Agg+ ( CollectAgg+ "mode"+ ( fst+ . L.maximumBy (compare `on` snd)+ . M.toList+ . V.foldl' (\m e -> M.insertWith (+) e 1 m) M.empty+ ) ) minimum :: (Columnable a, Ord a) => Expr a -> Expr a-minimum expr = AggReduce expr "minimum" Prelude.min+minimum = Agg (FoldAgg "minimum" Nothing Prelude.min) maximum :: (Columnable a, Ord a) => Expr a -> Expr a-maximum expr = AggReduce expr "maximum" Prelude.max+maximum = Agg (FoldAgg "maximum" Nothing Prelude.max) sum :: forall a. (Columnable a, Num a) => Expr a -> Expr a-sum expr = AggReduce expr "sum" (+)+sum = Agg (FoldAgg "sum" Nothing (+)) {-# SPECIALIZE DataFrame.Functions.sum :: Expr Double -> Expr Double #-} {-# SPECIALIZE DataFrame.Functions.sum :: Expr Int -> Expr Int #-} {-# INLINEABLE DataFrame.Functions.sum #-} sumMaybe :: forall a. (Columnable a, Num a) => Expr (Maybe a) -> Expr a-sumMaybe expr = AggVector expr "sumMaybe" (P.sum . Maybe.catMaybes . V.toList)+sumMaybe = Agg (CollectAgg "sumMaybe" (P.sum . Maybe.catMaybes . V.toList)) mean :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double-mean expr = AggNumericVector expr "mean" mean'+mean = Agg (CollectAgg "mean" mean') {-# SPECIALIZE DataFrame.Functions.mean :: Expr Double -> Expr Double #-} {-# SPECIALIZE DataFrame.Functions.mean :: Expr Int -> Expr Double #-} {-# INLINEABLE DataFrame.Functions.mean #-} meanMaybe :: forall a. (Columnable a, Real a) => Expr (Maybe a) -> Expr Double-meanMaybe expr = AggVector expr "meanMaybe" (mean' . optionalToDoubleVector)+meanMaybe = Agg (CollectAgg "meanMaybe" (mean' . optionalToDoubleVector)) variance :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double-variance expr = AggNumericVector expr "variance" variance'+variance = Agg (CollectAgg "variance" variance') median :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double-median expr = AggNumericVector expr "median" median'+median = Agg (CollectAgg "median" median') medianMaybe :: (Columnable a, Real a) => Expr (Maybe a) -> Expr Double-medianMaybe expr = AggVector expr "meanMaybe" (median' . optionalToDoubleVector)+medianMaybe = Agg (CollectAgg "meanMaybe" (median' . optionalToDoubleVector)) optionalToDoubleVector :: (Real a) => V.Vector (Maybe a) -> VU.Vector Double optionalToDoubleVector =@@ -198,55 +196,53 @@ [] percentile :: Int -> Expr Double -> Expr Double-percentile n expr =- AggNumericVector- expr- (T.pack $ "percentile " ++ show n)- (percentile' n)+percentile n =+ Agg+ ( CollectAgg+ (T.pack $ "percentile " ++ show n)+ (percentile' n)+ ) stddev :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double-stddev expr = AggNumericVector expr "stddev" (sqrt . variance')+stddev = Agg (CollectAgg "stddev" (sqrt . variance')) stddevMaybe :: forall a. (Columnable a, Real a) => Expr (Maybe a) -> Expr Double-stddevMaybe expr = AggVector expr "stddevMaybe" (sqrt . variance' . optionalToDoubleVector)+stddevMaybe = Agg (CollectAgg "stddevMaybe" (sqrt . variance' . optionalToDoubleVector)) zScore :: Expr Double -> Expr Double zScore c = (c - mean c) / stddev c pow :: (Columnable a, Num a) => Expr a -> Int -> Expr a-pow _ 0 = Lit 1-pow (Lit n) i = Lit (n ^ i)-pow expr 1 = expr-pow expr i = BinaryOp "pow" (^) expr (lit i)+pow = (.^^) -relu :: (Columnable a, Num a) => Expr a -> Expr a-relu = UnaryOp "relu" (Prelude.max 0)+relu :: (Columnable a, Num a, Ord a) => Expr a -> Expr a+relu = liftDecorated (Prelude.max 0) "relu" Nothing min :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr a-min = BinaryOp "min" Prelude.min+min = lift2Decorated Prelude.min "max" Nothing True 1 max :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr a-max = BinaryOp "max" Prelude.max+max = lift2Decorated Prelude.max "max" Nothing True 1 reduce :: forall a b. (Columnable a, Columnable b) => Expr b -> a -> (a -> b -> a) -> Expr a-reduce expr = AggFold expr "foldUdf"+reduce expr start f = Agg (FoldAgg "foldUdf" (Just start) f) expr toMaybe :: (Columnable a) => Expr a -> Expr (Maybe a)-toMaybe = UnaryOp "toMaybe" Just+toMaybe = liftDecorated Just "toMaybe" Nothing fromMaybe :: (Columnable a) => a -> Expr (Maybe a) -> Expr a-fromMaybe d = UnaryOp ("fromMaybe " <> T.pack (show d)) (Maybe.fromMaybe d)+fromMaybe d = liftDecorated (Maybe.fromMaybe d) "fromMaybe" Nothing isJust :: (Columnable a) => Expr (Maybe a) -> Expr Bool-isJust = UnaryOp "isJust" Maybe.isJust+isJust = liftDecorated Maybe.isJust "isJust" Nothing isNothing :: (Columnable a) => Expr (Maybe a) -> Expr Bool-isNothing = UnaryOp "isNothing" Maybe.isNothing+isNothing = liftDecorated Maybe.isNothing "isNothing" Nothing fromJust :: (Columnable a) => Expr (Maybe a) -> Expr a-fromJust = UnaryOp "fromJust" Maybe.fromJust+fromJust = liftDecorated Maybe.fromJust "fromJust" Nothing whenPresent :: forall a b.@@ -262,7 +258,14 @@ recode :: forall a b. (Columnable a, Columnable b) => [(a, b)] -> Expr a -> Expr (Maybe b)-recode mapping = UnaryOp (T.pack ("recode " ++ show mapping)) (`lookup` mapping)+recode mapping =+ Unary+ ( MkUnaryOp+ { unaryFn = (`lookup` mapping)+ , unaryName = "recode " <> T.pack (show mapping)+ , unarySymbol = Nothing+ }+ ) recodeWithCondition :: forall a b.@@ -275,30 +278,54 @@ forall a b. (Columnable a, Columnable b) => b -> [(a, b)] -> Expr a -> Expr b recodeWithDefault d mapping =- UnaryOp- (T.pack ("recodeWithDefault " ++ show d ++ " " ++ show mapping))- (Maybe.fromMaybe d . (`lookup` mapping))+ Unary+ ( MkUnaryOp+ { unaryFn = Maybe.fromMaybe d . (`lookup` mapping)+ , unaryName =+ "recodeWithDefault " <> T.pack (show d) <> " " <> T.pack (show mapping)+ , unarySymbol = Nothing+ }+ ) firstOrNothing :: (Columnable a) => Expr [a] -> Expr (Maybe a)-firstOrNothing = lift Maybe.listToMaybe+firstOrNothing = liftDecorated Maybe.listToMaybe "firstOrNothing" Nothing lastOrNothing :: (Columnable a) => Expr [a] -> Expr (Maybe a)-lastOrNothing = lift (Maybe.listToMaybe . reverse)+lastOrNothing = liftDecorated (Maybe.listToMaybe . reverse) "lastOrNothing" Nothing splitOn :: T.Text -> Expr T.Text -> Expr [T.Text]-splitOn delim = lift (T.splitOn delim)+splitOn delim = liftDecorated (T.splitOn delim) "splitOn" Nothing match :: T.Text -> Expr T.Text -> Expr (Maybe T.Text)-match regex = lift ((\r -> if T.null r then Nothing else Just r) . (=~ regex))+match regex =+ liftDecorated+ ((\r -> if T.null r then Nothing else Just r) . (=~ regex))+ ("match " <> T.pack (show regex))+ Nothing matchAll :: T.Text -> Expr T.Text -> Expr [T.Text]-matchAll regex = lift (getAllTextMatches . (=~ regex))+matchAll regex =+ liftDecorated+ (getAllTextMatches . (=~ regex))+ ("matchAll " <> T.pack (show regex))+ Nothing -parseDate :: T.Text -> Expr T.Text -> Expr (Maybe Day)-parseDate format = lift (parseTimeM True defaultTimeLocale (T.unpack format) . T.unpack)+parseDate ::+ (ParseTime t, Columnable t) => T.Text -> Expr T.Text -> Expr (Maybe t)+parseDate format =+ liftDecorated+ (parseTimeM True defaultTimeLocale (T.unpack format) . T.unpack)+ ("parseDate " <> format)+ Nothing daysBetween :: Expr Day -> Expr Day -> Expr Int-daysBetween d1 d2 = lift fromIntegral (lift2 diffDays d1 d2)+daysBetween =+ lift2Decorated+ (\d1 d2 -> fromIntegral (diffDays d1 d2))+ "daysBetween"+ Nothing+ True+ 2 bind :: forall a b m.@@ -413,23 +440,57 @@ (CSV.readSeparated (CSV.defaultReadOptions{CSV.numColumns = Just 100}) path) declareColumns df --- TODO: We don't have to read the whole file, we can just read the schema. declareColumnsFromParquetFile :: String -> DecsQ declareColumnsFromParquetFile path = do- df <- liftIO (Parquet.readParquet path)+ metadata <- liftIO (Parquet.readMetadataFromPath path)+ let df = schemaToEmptyDataFrame (schema metadata) declareColumns df +schemaToEmptyDataFrame :: [SchemaElement] -> DataFrame+schemaToEmptyDataFrame elems =+ let leafElems = filter (\e -> numChildren e == 0) elems+ in fromNamedColumns (map schemaElemToColumn leafElems)++schemaElemToColumn :: SchemaElement -> (T.Text, Column)+schemaElemToColumn elem =+ let name = elementName elem+ in (name, emptyColumnForType (elementType elem))++emptyColumnForType :: TType -> Column+emptyColumnForType = \case+ BOOL -> fromList @Bool []+ BYTE -> fromList @Word8 []+ I16 -> fromList @Int16 []+ I32 -> fromList @Int32 []+ I64 -> fromList @Int64 []+ I96 -> fromList @Int64 []+ FLOAT -> fromList @Float []+ DOUBLE -> fromList @Double []+ STRING -> fromList @T.Text []+ other -> error $ "Unsupported parquet type for column: " <> show other+ declareColumnsFromCsvWithOpts :: CSV.ReadOptions -> String -> DecsQ declareColumnsFromCsvWithOpts opts path = do df <- liftIO (CSV.readSeparated opts path) declareColumns df declareColumns :: DataFrame -> DecsQ-declareColumns df =+declareColumns = declareColumnsWithPrefix' Nothing++declareColumnsWithPrefix :: T.Text -> DataFrame -> DecsQ+declareColumnsWithPrefix prefix = declareColumnsWithPrefix' (Just prefix)++declareColumnsWithPrefix' :: Maybe T.Text -> DataFrame -> DecsQ+declareColumnsWithPrefix' prefix df = let names = (map fst . L.sortBy (compare `on` snd) . M.toList . columnIndices) df types = map (columnTypeString . (`unsafeGetColumn` df)) names- specs = zipWith (\name type_ -> (name, sanitize name, type_)) names types+ specs =+ zipWith+ ( \name type_ -> (name, maybe "" (sanitize . (<> "_")) prefix <> sanitize name, type_)+ )+ names+ types in fmap concat $ forM specs $ \(raw, nm, tyStr) -> do ty <- typeFromString (words tyStr)
src/DataFrame/IO/CSV.hs view
@@ -237,10 +237,14 @@ @ -} readSeparated :: ReadOptions -> FilePath -> IO DataFrame-readSeparated !opts !path = do- let sep = columnSeparator opts+readSeparated opts !path = do let stripUtf8Bom bs = fromMaybe bs (BL.stripPrefix "\xEF\xBB\xBF" bs) csvData <- stripUtf8Bom <$> BL.readFile path+ decodeSeparated opts csvData++decodeSeparated :: ReadOptions -> BL.ByteString -> IO DataFrame+decodeSeparated !opts csvData = do+ let sep = columnSeparator opts let decodeOpts = Csv.defaultDecodeOptions{Csv.decDelimiter = fromIntegral (ord sep)} let stream = CsvStream.decodeWith decodeOpts Csv.NoHeader csvData @@ -387,6 +391,9 @@ writeCsv :: FilePath -> DataFrame -> IO () writeCsv = writeSeparated ','++writeTsv :: FilePath -> DataFrame -> IO ()+writeTsv = writeSeparated '\t' writeSeparated :: -- | Separator
src/DataFrame/IO/Parquet.hs view
@@ -1,22 +1,23 @@+{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -module DataFrame.IO.Parquet (- readParquet,- readParquetFiles,-) where+module DataFrame.IO.Parquet where import Control.Monad import Data.Bits import qualified Data.ByteString as BSO-import Data.Char import Data.Either import Data.IORef import Data.Int import qualified Data.List as L import qualified Data.Map as M import qualified Data.Text as T+import Data.Text.Encoding+import Data.Time+import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.Word import qualified DataFrame.Internal.Column as DI import DataFrame.Internal.DataFrame (DataFrame)@@ -31,6 +32,7 @@ import DataFrame.IO.Parquet.Types import System.Directory (doesDirectoryExist) +import qualified Data.Vector.Unboxed as VU import System.FilePath ((</>)) {- | Read a parquet file from path and load it into a dataframe.@@ -47,6 +49,7 @@ let columnNames = map fst columnPaths colMap <- newIORef (M.empty :: M.Map T.Text DI.Column)+ lTypeMap <- newIORef (M.empty :: M.Map T.Text LogicalType) contents <- BSO.readFile path @@ -82,8 +85,7 @@ else colDataPageOffset let colLength = columnTotalCompressedSize metadata - let columnBytes =- map (BSO.index contents . fromIntegral) [colStart .. (colStart + colLength - 1)]+ let columnBytes = BSO.take (fromIntegral colLength) (BSO.drop (fromIntegral colStart) contents) pages <- readAllPages (columnCodec metadata) columnBytes @@ -97,6 +99,7 @@ let schemaTail = drop 1 (schema fileMetadata) let colPath = columnPathInSchema (columnMetaData colChunk) let (maxDef, maxRep) = levelsForPath schemaTail colPath+ let lType = logicalType (schemaTail !! colIdx) column <- processColumnPages (maxDef, maxRep)@@ -104,13 +107,17 @@ (columnType metadata) primaryEncoding maybeTypeLength+ lType modifyIORef colMap (M.insertWith DI.concatColumnsEither colName column)+ modifyIORef lTypeMap (M.insert colName lType) finalColMap <- readIORef colMap+ finalLTypeMap <- readIORef lTypeMap let orderedColumns = map- (\name -> (name, finalColMap M.! name))+ ( \name -> (name, applyLogicalType (finalLTypeMap M.! name) $ finalColMap M.! name)+ ) (filter (`M.member` finalColMap) columnNames) pure $ DI.fromNamedColumns orderedColumns@@ -176,8 +183,9 @@ ParquetType -> ParquetEncoding -> Maybe Int32 ->+ LogicalType -> IO DI.Column-processColumnPages (maxDef, maxRep) pages pType _ maybeTypeLength = do+processColumnPages (maxDef, maxRep) pages pType _ maybeTypeLength lType = do let dictPages = filter isDictionaryPage pages let dataPages = filter isDataPage pages @@ -225,13 +233,13 @@ in pure (toMaybeDouble maxDef defLvls vals) PBYTE_ARRAY -> let (raws, _) = readNByteArrays nPresent afterLvls- texts = map (T.pack . map (chr . fromIntegral)) raws+ texts = map decodeUtf8 raws in pure (toMaybeText maxDef defLvls texts) PFIXED_LEN_BYTE_ARRAY -> case maybeTypeLength of Just len -> let (raws, _) = splitFixed nPresent (fromIntegral len) afterLvls- texts = map (T.pack . map (chr . fromIntegral)) raws+ texts = map decodeUtf8 raws in pure (toMaybeText maxDef defLvls texts) Nothing -> error "FIXED_LEN_BYTE_ARRAY requires type length" PARQUET_TYPE_UNKNOWN -> error "Cannot read unknown Parquet type"@@ -277,13 +285,13 @@ in pure (toMaybeDouble maxDef defLvls vals) PBYTE_ARRAY -> let (raws, _) = readNByteArrays nPresent afterLvls- texts = map (T.pack . map (chr . fromIntegral)) raws+ texts = map decodeUtf8 raws in pure (toMaybeText maxDef defLvls texts) PFIXED_LEN_BYTE_ARRAY -> case maybeTypeLength of Just len -> let (raws, _) = splitFixed nPresent (fromIntegral len) afterLvls- texts = map (T.pack . map (chr . fromIntegral)) raws+ texts = map decodeUtf8 raws in pure (toMaybeText maxDef defLvls texts) Nothing -> error "FIXED_LEN_BYTE_ARRAY requires type length" PARQUET_TYPE_UNKNOWN -> error "Cannot read unknown Parquet type"@@ -300,3 +308,37 @@ (c : cs) -> pure $ L.foldl' (\l r -> fromRight (error "concat failed") (DI.concatColumns l r)) c cs++applyLogicalType :: LogicalType -> DI.Column -> DI.Column+applyLogicalType (TimestampType isUTC unit) col =+ fromRight col $+ DI.mapColumn+ (microsecondsToUTCTime . (* (1_000_000 `div` unitDivisor unit)))+ col+applyLogicalType (DecimalType precision scale) col+ | precision <= 9 = case DI.toVector @Int32 @VU.Vector col of+ Right xs ->+ DI.fromUnboxedVector $+ VU.map (\raw -> fromIntegral @Int32 @Double raw / 10 ^ scale) xs+ Left _ -> col+ | precision <= 18 = case DI.toVector @Int64 @VU.Vector col of+ Right xs ->+ DI.fromUnboxedVector $+ VU.map (\raw -> fromIntegral @Int64 @Double raw / 10 ^ scale) xs+ Left _ -> col+ | otherwise = col+applyLogicalType _ col = col++microsecondsToUTCTime :: Int64 -> UTCTime+microsecondsToUTCTime us =+ posixSecondsToUTCTime (fromIntegral us / 1_000_000)++unitDivisor :: TimeUnit -> Int64+unitDivisor MILLISECONDS = 1_000+unitDivisor MICROSECONDS = 1_000_000+unitDivisor NANOSECONDS = 1_000_000_000+unitDivisor TIME_UNIT_UNKNOWN = 1++applyScale :: Int32 -> Int32 -> Double+applyScale scale rawValue =+ fromIntegral rawValue / (10 ^ scale)
src/DataFrame/IO/Parquet/Binary.hs view
@@ -10,32 +10,45 @@ import Data.Int import Data.Word -littleEndianWord32 :: [Word8] -> Word32+littleEndianWord32 :: BS.ByteString -> Word32 littleEndianWord32 bytes- | length bytes >= 4 =+ | BS.length bytes >= 4 = foldr (.|.) 0- (zipWith (\b i -> fromIntegral b `shiftL` i) (take 4 bytes) [0, 8, 16, 24])- | otherwise = littleEndianWord32 (take 4 $ bytes ++ repeat 0)+ ( zipWith+ (\b i -> fromIntegral b `shiftL` i)+ (BS.unpack $ BS.take 4 bytes)+ [0, 8, 16, 24]+ )+ | otherwise =+ littleEndianWord32 (BS.take 4 $ bytes `BS.append` BS.pack [0, 0, 0, 0]) -littleEndianWord64 :: [Word8] -> Word64+littleEndianWord64 :: BS.ByteString -> Word64 littleEndianWord64 bytes = foldr (.|.) 0- (zipWith (\b i -> fromIntegral b `shiftL` i) (take 8 bytes) [0, 8 ..])+ ( zipWith+ (\b i -> fromIntegral b `shiftL` i)+ (BS.unpack $ BS.take 8 bytes)+ [0, 8 ..]+ ) -littleEndianInt32 :: [Word8] -> Int32+littleEndianInt32 :: BS.ByteString -> Int32 littleEndianInt32 = fromIntegral . littleEndianWord32 -word64ToLittleEndian :: Word64 -> [Word8]-word64ToLittleEndian w = map (\i -> fromIntegral (w `shiftR` i)) [0, 8, 16, 24, 32, 40, 48, 56]+word64ToLittleEndian :: Word64 -> BS.ByteString+word64ToLittleEndian w =+ BS.map+ (\i -> fromIntegral (w `shiftR` fromIntegral i))+ (BS.pack [0, 8, 16, 24, 32, 40, 48, 56]) -word32ToLittleEndian :: Word32 -> [Word8]-word32ToLittleEndian w = map (\i -> fromIntegral (w `shiftR` i)) [0, 8, 16, 24]+word32ToLittleEndian :: Word32 -> BS.ByteString+word32ToLittleEndian w =+ BS.map (\i -> fromIntegral (w `shiftR` fromIntegral i)) (BS.pack [0, 8, 16, 24]) -readUVarInt :: [Word8] -> (Word64, [Word8])+readUVarInt :: BS.ByteString -> (Word64, BS.ByteString) readUVarInt xs = loop xs 0 0 0 where {-@@ -44,31 +57,34 @@ - The high bit (0x80) is the continuation flag: 1 = more bytes follow, 0 = last byte Why the magic number 10: For a 64‑bit integer we need at most ceil(64 / 7) = 10 bytes -}- loop :: [Word8] -> Word64 -> Int -> Int -> (Word64, [Word8])+ loop :: BS.ByteString -> Word64 -> Int -> Int -> (Word64, BS.ByteString) loop bs result _ 10 = (result, bs)- loop (b : bs) result shift i- | b < 0x80 = (result .|. (fromIntegral b `shiftL` shift), bs)- | otherwise =- let payloadBits = fromIntegral (b .&. 0x7f) :: Word64- in loop bs (result .|. (payloadBits `shiftL` shift)) (shift + 7) (i + 1)- loop [] _ _ _ = error "readUVarInt: not enough input bytes"+ loop xs result shift i = case BS.uncons xs of+ Nothing -> error "readUVarInt: not enough input bytes"+ Just (b, bs) ->+ if b < 0x80+ then (result .|. (fromIntegral b `shiftL` shift), bs)+ else+ let payloadBits = fromIntegral (b .&. 0x7f) :: Word64+ in loop bs (result .|. (payloadBits `shiftL` shift)) (shift + 7) (i + 1) -readVarIntFromBytes :: (Integral a) => [Word8] -> (a, [Word8])+readVarIntFromBytes :: (Integral a) => BS.ByteString -> (a, BS.ByteString) readVarIntFromBytes bs = (fromIntegral n, rem) where (n, rem) = loop 0 0 bs- loop _ result [] = (result, [])- loop shift result (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+ loop shift 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 -readIntFromBytes :: (Integral a) => [Word8] -> (a, [Word8])+readIntFromBytes :: (Integral a) => BS.ByteString -> (a, BS.ByteString) readIntFromBytes bs = let (n, rem) = readVarIntFromBytes bs u = fromIntegral n :: Word32 in (fromIntegral $ (fromIntegral (u `shiftR` 1) :: Int32) .^. (-(n .&. 1)), rem) -readInt32FromBytes :: [Word8] -> (Int32, [Word8])+readInt32FromBytes :: BS.ByteString -> (Int32, BS.ByteString) readInt32FromBytes bs = let (n', rem) = readVarIntFromBytes @Int64 bs n = fromIntegral n' :: Int32@@ -110,20 +126,20 @@ nameSize <- readVarIntFromBuffer @Int buf pos map (chr . fromIntegral) <$> replicateM nameSize (readAndAdvance pos buf) -readByteStringFromBytes :: [Word8] -> ([Word8], [Word8])+readByteStringFromBytes :: BS.ByteString -> (BS.ByteString, BS.ByteString) readByteStringFromBytes xs = let (size, rem) = readVarIntFromBytes @Int xs in- splitAt size rem+ BS.splitAt size rem -readByteString :: BS.ByteString -> IORef Int -> IO [Word8]+readByteString :: BS.ByteString -> IORef Int -> IO BS.ByteString readByteString buf pos = do size <- readVarIntFromBuffer @Int buf pos- replicateM size (readAndAdvance pos buf)+ BS.pack <$> replicateM size (readAndAdvance pos buf) -readByteString' :: BS.ByteString -> Int64 -> IO [Word8]-readByteString' buf size = mapM (`readSingleByte` buf) [0 .. (size - 1)]+readByteString' :: BS.ByteString -> Int64 -> IO BS.ByteString+readByteString' buf size = BS.pack <$> mapM (`readSingleByte` buf) [0 .. (size - 1)] readSingleByte :: Int64 -> BS.ByteString -> IO Word8 readSingleByte pos buffer = return $ BS.index buffer (fromIntegral pos)
src/DataFrame/IO/Parquet/ColumnStatistics.hs view
@@ -1,19 +1,19 @@ module DataFrame.IO.Parquet.ColumnStatistics where -import Data.Int-import Data.Word+import qualified Data.ByteString as BS+import Data.Int (Int64) data ColumnStatistics = ColumnStatistics- { columnMin :: [Word8]- , columnMax :: [Word8]+ { columnMin :: BS.ByteString+ , columnMax :: BS.ByteString , columnNullCount :: Int64 , columnDistictCount :: Int64- , columnMinValue :: [Word8]- , columnMaxValue :: [Word8]+ , columnMinValue :: BS.ByteString+ , columnMaxValue :: BS.ByteString , isColumnMaxValueExact :: Bool , isColumnMinValueExact :: Bool } deriving (Show, Eq) emptyColumnStatistics :: ColumnStatistics-emptyColumnStatistics = ColumnStatistics [] [] 0 0 [] [] False False+emptyColumnStatistics = ColumnStatistics BS.empty BS.empty 0 0 BS.empty BS.empty False False
src/DataFrame/IO/Parquet/Dictionary.hs view
@@ -4,12 +4,13 @@ import Control.Monad import Data.Bits+import qualified Data.ByteString as BS import Data.Char import Data.Int import Data.Maybe import qualified Data.Text as T+import Data.Text.Encoding import Data.Time-import Data.Word import DataFrame.IO.Parquet.Binary import DataFrame.IO.Parquet.Encoding import DataFrame.IO.Parquet.Levels@@ -27,7 +28,7 @@ dictCardinality (DDouble ds) = length ds dictCardinality (DText ds) = length ds -readDictVals :: ParquetType -> [Word8] -> Maybe Int32 -> DictVals+readDictVals :: ParquetType -> BS.ByteString -> Maybe Int32 -> DictVals readDictVals PBOOLEAN bs (Just count) = DBool (take (fromIntegral count) $ readPageBool bs) readDictVals PINT32 bs _ = DInt32 (readPageInt32 bs) readDictVals PINT64 bs _ = DInt64 (readPageInt64 bs)@@ -38,60 +39,68 @@ readDictVals PFIXED_LEN_BYTE_ARRAY bs (Just len) = DText (readPageFixedBytes bs (fromIntegral len)) readDictVals t _ _ = error $ "Unsupported dictionary type: " ++ show t -readPageInt32 :: [Word8] -> [Int32]-readPageInt32 [] = []-readPageInt32 xs = littleEndianInt32 (take 4 xs) : readPageInt32 (drop 4 xs)+readPageInt32 :: BS.ByteString -> [Int32]+readPageInt32 xs+ | BS.null xs = []+ | otherwise = littleEndianInt32 (BS.take 4 xs) : readPageInt32 (BS.drop 4 xs) -readPageWord64 :: [Word8] -> [Double]-readPageWord64 [] = []-readPageWord64 xs =- castWord64ToDouble (littleEndianWord64 (take 8 xs)) : readPageWord64 (drop 8 xs)+readPageWord64 :: BS.ByteString -> [Double]+readPageWord64 xs+ | BS.null xs = []+ | otherwise =+ castWord64ToDouble (littleEndianWord64 (BS.take 8 xs))+ : readPageWord64 (BS.drop 8 xs) -readPageBytes :: [Word8] -> [T.Text]-readPageBytes [] = []-readPageBytes xs =- let lenBytes = fromIntegral (littleEndianInt32 $ take 4 xs)- totalBytesRead = lenBytes + 4- in T.pack (map (chr . fromIntegral) $ take lenBytes (drop 4 xs))- : readPageBytes (drop totalBytesRead xs)+readPageBytes :: BS.ByteString -> [T.Text]+readPageBytes xs+ | BS.null xs = []+ | otherwise =+ let lenBytes = fromIntegral (littleEndianInt32 $ BS.take 4 xs)+ totalBytesRead = lenBytes + 4+ in T.pack (map (chr . fromIntegral) $ take lenBytes (BS.unpack (BS.drop 4 xs)))+ : readPageBytes (BS.drop totalBytesRead xs) -readPageBool :: [Word8] -> [Bool]-readPageBool [] = []+readPageBool :: BS.ByteString -> [Bool] readPageBool bs =- concatMap (\b -> map (\i -> (b `shiftR` i) .&. 1 == 1) [0 .. 7]) bs+ concatMap (\b -> map (\i -> (b `shiftR` i) .&. 1 == 1) [0 .. 7]) (BS.unpack bs) -readPageInt64 :: [Word8] -> [Int64]-readPageInt64 [] = []-readPageInt64 xs = fromIntegral (littleEndianWord64 (take 8 xs)) : readPageInt64 (drop 8 xs)+readPageInt64 :: BS.ByteString -> [Int64]+readPageInt64 xs+ | BS.null xs = []+ | otherwise =+ fromIntegral (littleEndianWord64 (BS.take 8 xs)) : readPageInt64 (BS.drop 8 xs) -readPageFloat :: [Word8] -> [Float]-readPageFloat [] = []-readPageFloat xs =- castWord32ToFloat (littleEndianWord32 (take 4 xs)) : readPageFloat (drop 4 xs)+readPageFloat :: BS.ByteString -> [Float]+readPageFloat xs+ | BS.null xs = []+ | otherwise =+ castWord32ToFloat (littleEndianWord32 (BS.take 4 xs))+ : readPageFloat (BS.drop 4 xs) -readNInt96Times :: Int -> [Word8] -> ([UTCTime], [Word8])+readNInt96Times :: Int -> BS.ByteString -> ([UTCTime], BS.ByteString) readNInt96Times 0 bs = ([], bs) readNInt96Times k bs =- let timestamp96 = take 12 bs+ let timestamp96 = BS.take 12 bs utcTime = int96ToUTCTime timestamp96- bs' = drop 12 bs+ bs' = BS.drop 12 bs (times, rest) = readNInt96Times (k - 1) bs' in (utcTime : times, rest) -readPageInt96Times :: [Word8] -> [UTCTime]-readPageInt96Times [] = []-readPageInt96Times bs =- let (times, _) = readNInt96Times (length bs `div` 12) bs- in times+readPageInt96Times :: BS.ByteString -> [UTCTime]+readPageInt96Times bs+ | BS.null bs = []+ | otherwise =+ let (times, _) = readNInt96Times (BS.length bs `div` 12) bs+ in times -readPageFixedBytes :: [Word8] -> Int -> [T.Text]-readPageFixedBytes [] _ = []-readPageFixedBytes xs len =- let chunk = take len xs- text = T.pack (map (chr . fromIntegral) chunk)- in text : readPageFixedBytes (drop len xs) len+readPageFixedBytes :: BS.ByteString -> Int -> [T.Text]+readPageFixedBytes xs len+ | BS.null xs = []+ | otherwise =+ decodeUtf8 (BS.take len xs) : readPageFixedBytes (BS.drop len xs) len -decodeDictV1 :: Maybe DictVals -> Int -> [Int] -> Int -> [Word8] -> IO DI.Column+decodeDictV1 ::+ Maybe DictVals -> Int -> [Int] -> Int -> BS.ByteString -> IO DI.Column decodeDictV1 dictValsM maxDef defLvls nPresent bytes = case dictValsM of Nothing -> error "Dictionary-encoded page but dictionary is missing"
src/DataFrame/IO/Parquet/Encoding.hs view
@@ -1,6 +1,7 @@ module DataFrame.IO.Parquet.Encoding where import Data.Bits+import qualified Data.ByteString as BS import Data.List (foldl') import Data.Word import DataFrame.IO.Parquet.Binary@@ -16,39 +17,50 @@ bytesForBW :: Int -> Int bytesForBW bw = (bw + 7) `div` 8 -unpackBitPacked :: Int -> Int -> [Word8] -> ([Word32], [Word8])+unpackBitPacked :: Int -> Int -> BS.ByteString -> ([Word32], BS.ByteString) unpackBitPacked bw count bs | count <= 0 = ([], bs)- | null bs = ([], bs)+ | BS.null bs = ([], bs) | otherwise = let totalBits = bw * count totalBytes = (totalBits + 7) `div` 8- chunk = take totalBytes bs- rest = drop totalBytes bs- bits = concatMap (\b -> map (\i -> (b `shiftR` i) .&. 1) [0 .. 7]) chunk- toN xs = foldl' (\a (i, b) -> a .|. (b `shiftL` i)) 0 (zip [0 ..] xs)+ chunk = BS.take totalBytes bs+ rest = BS.drop totalBytes bs+ bits =+ BS.concatMap+ (\b -> BS.map (\i -> (b `shiftR` fromIntegral i) .&. 1) (BS.pack [0 .. 7]))+ chunk+ toN :: BS.ByteString -> Word32+ toN =+ fst+ . BS.foldl'+ (\(a, i) b -> (a .|. (fromIntegral b `shiftL` i), i + 1))+ (0 :: Word32, 0 :: Int) - extractValues _ [] = []+ extractValues :: Int -> BS.ByteString -> [Word32] extractValues n bitsLeft+ | BS.null bitsLeft = [] | n <= 0 = []- | length bitsLeft < bw = []+ | BS.length bitsLeft < bw = [] | otherwise =- let (this, bitsLeft') = splitAt bw bitsLeft+ let (this, bitsLeft') = BS.splitAt bw bitsLeft in toN this : extractValues (n - 1) bitsLeft' vals = extractValues count bits- in (map fromIntegral vals, rest)+ in (vals, rest) -decodeRLEBitPackedHybrid :: Int -> Int -> [Word8] -> ([Word32], [Word8])+decodeRLEBitPackedHybrid ::+ Int -> Int -> BS.ByteString -> ([Word32], BS.ByteString) decodeRLEBitPackedHybrid bw need bs | bw == 0 = (replicate need 0, bs) | otherwise = go need bs [] where mask :: Word32 mask = if bw == 32 then maxBound else (1 `shiftL` bw) - 1+ go :: Int -> BS.ByteString -> [Word32] -> ([Word32], BS.ByteString) go 0 rest acc = (reverse acc, rest) go n rest acc- | null rest = (reverse acc, rest)+ | BS.null rest = (reverse acc, rest) | otherwise = let (hdr64, afterHdr) = readUVarInt rest isPacked = (hdr64 .&. 1) == 1@@ -63,17 +75,17 @@ else let runLen = fromIntegral (hdr64 `shiftR` 1) :: Int nbytes = bytesForBW bw- word32 = littleEndianWord32 (take 4 afterHdr)- afterV = drop nbytes afterHdr+ word32 = littleEndianWord32 (BS.take 4 afterHdr)+ afterV = BS.drop nbytes afterHdr val = word32 .&. mask takeN = min n runLen in go (n - takeN) afterV (replicate takeN val ++ acc) -decodeDictIndicesV1 :: Int -> Int -> [Word8] -> ([Int], [Word8])+decodeDictIndicesV1 :: Int -> Int -> BS.ByteString -> ([Int], BS.ByteString) decodeDictIndicesV1 need dictCard bs =- case bs of- [] -> error "empty dictionary index stream"- (w0 : rest0) ->+ case BS.uncons bs of+ Nothing -> error "empty dictionary index stream"+ Just (w0, rest0) -> let bw = fromIntegral w0 :: Int (u32s, rest1) = decodeRLEBitPackedHybrid bw need rest0 in (map fromIntegral u32s, rest1)
src/DataFrame/IO/Parquet/Levels.hs view
@@ -1,5 +1,6 @@ module DataFrame.IO.Parquet.Levels where +import qualified Data.ByteString as BS import Data.Int import Data.List import qualified Data.Text as T@@ -10,7 +11,8 @@ import DataFrame.IO.Parquet.Thrift import DataFrame.IO.Parquet.Types -readLevelsV1 :: Int -> Int -> Int -> [Word8] -> ([Int], [Int], [Word8])+readLevelsV1 ::+ Int -> Int -> Int -> BS.ByteString -> ([Int], [Int], BS.ByteString) readLevelsV1 n maxDef maxRep bs = let bwDef = bitWidthForMaxLevel maxDef bwRep = bitWidthForMaxLevel maxRep@@ -19,9 +21,9 @@ if bwRep == 0 then (replicate n 0, bs) else- let repLength = littleEndianWord32 (take 4 bs)- repData = take (fromIntegral repLength) (drop 4 bs)- afterRepData = drop (4 + fromIntegral repLength) bs+ let repLength = littleEndianWord32 (BS.take 4 bs)+ repData = BS.take (fromIntegral repLength) (BS.drop 4 bs)+ afterRepData = BS.drop (4 + fromIntegral repLength) bs (repVals, _) = decodeRLEBitPackedHybrid bwRep n repData in (repVals, afterRepData) @@ -29,18 +31,24 @@ if bwDef == 0 then (replicate n 0, afterRep) else- let defLength = littleEndianWord32 (take 4 afterRep)- defData = take (fromIntegral defLength) (drop 4 afterRep)- afterDefData = drop (4 + fromIntegral defLength) afterRep+ let defLength = littleEndianWord32 (BS.take 4 afterRep)+ defData = BS.take (fromIntegral defLength) (BS.drop 4 afterRep)+ afterDefData = BS.drop (4 + fromIntegral defLength) afterRep (defVals, _) = decodeRLEBitPackedHybrid bwDef n defData in (defVals, afterDefData) in (map fromIntegral defLvlsU32, map fromIntegral repLvlsU32, afterDef) readLevelsV2 ::- Int -> Int -> Int -> Int32 -> Int32 -> [Word8] -> ([Int], [Int], [Word8])+ Int ->+ Int ->+ Int ->+ Int32 ->+ Int32 ->+ BS.ByteString ->+ ([Int], [Int], BS.ByteString) readLevelsV2 n maxDef maxRep defLen repLen bs =- let (repBytes, afterRepBytes) = splitAt (fromIntegral repLen) bs- (defBytes, afterDefBytes) = splitAt (fromIntegral defLen) afterRepBytes+ let (repBytes, afterRepBytes) = BS.splitAt (fromIntegral repLen) bs+ (defBytes, afterDefBytes) = BS.splitAt (fromIntegral defLen) afterRepBytes bwDef = bitWidthForMaxLevel maxDef bwRep = bitWidthForMaxLevel maxRep (repLvlsU32, _) =
src/DataFrame/IO/Parquet/Page.hs view
@@ -4,13 +4,12 @@ module DataFrame.IO.Parquet.Page where import qualified Codec.Compression.GZip as GZip-import Codec.Compression.Zstd.Streaming+import qualified Codec.Compression.Zstd.Streaming as Zstd import Data.Bits-import qualified Data.ByteString as BSO+import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LB import Data.Int-import Data.Maybe (fromMaybe, listToMaybe)-import Data.Word+import Data.Maybe (fromMaybe) import DataFrame.IO.Parquet.Binary import DataFrame.IO.Parquet.Thrift import DataFrame.IO.Parquet.Types@@ -28,315 +27,345 @@ DictionaryPageHeader{..} -> True _ -> False -readPage :: CompressionCodec -> [Word8] -> IO (Maybe Page, [Word8])-readPage c [] = pure (Nothing, [])-readPage c columnBytes = do- let (hdr, rem) = readPageHeader emptyPageHeader columnBytes 0- let compressed = take (fromIntegral $ compressedPageSize hdr) rem+readPage :: CompressionCodec -> BS.ByteString -> IO (Maybe Page, BS.ByteString)+readPage c columnBytes =+ if BS.null columnBytes+ then pure (Nothing, BS.empty)+ else do+ let (hdr, rem) = readPageHeader emptyPageHeader columnBytes 0 - fullData <- case c of- ZSTD -> do- Consume dFunc <- decompress- Consume dFunc' <- dFunc (BSO.pack compressed)- Done res <- dFunc' BSO.empty- pure res- SNAPPY -> case Snappy.decompress (BSO.pack compressed) of- Left e -> error (show e)- Right res -> pure res- UNCOMPRESSED -> pure (BSO.pack compressed)- GZIP -> pure (LB.toStrict (GZip.decompress (LB.pack compressed)))- other -> error ("Unsupported compression type: " ++ show other)- pure- ( Just $ Page hdr (BSO.unpack fullData)- , drop (fromIntegral $ compressedPageSize hdr) rem- )+ let compressed = BS.take (fromIntegral $ compressedPageSize hdr) rem -readPageHeader :: PageHeader -> [Word8] -> Int16 -> (PageHeader, [Word8])-readPageHeader hdr [] _ = (hdr, [])+ fullData <- case c of+ ZSTD -> do+ result <- Zstd.decompress+ drainZstd result compressed []+ where+ drainZstd (Zstd.Consume f) input acc = do+ result <- f input+ drainZstd result BS.empty acc+ drainZstd (Zstd.Produce chunk next) _ acc = do+ result <- next+ drainZstd result BS.empty (acc <> [chunk])+ drainZstd (Zstd.Done final) _ acc =+ pure $ BS.concat (acc <> [final])+ drainZstd (Zstd.Error msg msg2) _ _ =+ error ("ZSTD error: " ++ msg ++ " " ++ msg2)+ SNAPPY -> case Snappy.decompress compressed of+ Left e -> error (show e)+ Right res -> pure res+ UNCOMPRESSED -> pure compressed+ GZIP -> pure (LB.toStrict (GZip.decompress (BS.fromStrict compressed)))+ other -> error ("Unsupported compression type: " ++ show other)+ pure+ ( Just $ Page hdr fullData+ , BS.drop (fromIntegral $ compressedPageSize hdr) rem+ )++readPageHeader ::+ PageHeader -> BS.ByteString -> Int16 -> (PageHeader, BS.ByteString) readPageHeader hdr xs lastFieldId =- let- fieldContents = readField' xs lastFieldId- in- case fieldContents of- Nothing -> (hdr, drop 1 xs)- Just (rem, elemType, identifier) -> case identifier of- 1 ->- let- (pType, rem') = readInt32FromBytes rem- in- readPageHeader (hdr{pageHeaderPageType = pageTypeFromInt pType}) rem' identifier- 2 ->- let- (uncompressedPageSize, rem') = readInt32FromBytes rem- in- readPageHeader- (hdr{uncompressedPageSize = uncompressedPageSize})- rem'- identifier- 3 ->- let- (compressedPageSize, rem') = readInt32FromBytes rem- in- readPageHeader (hdr{compressedPageSize = compressedPageSize}) rem' identifier- 4 ->- let- (crc, rem') = readInt32FromBytes rem- in- readPageHeader (hdr{pageHeaderCrcChecksum = crc}) rem' identifier- 5 ->- let- (dataPageHeader, rem') = readPageTypeHeader emptyDataPageHeader rem 0- in- readPageHeader (hdr{pageTypeHeader = dataPageHeader}) rem' identifier- 6 -> error "Index page header not supported"- 7 ->- let- (dictionaryPageHeader, rem') = readPageTypeHeader emptyDictionaryPageHeader rem 0- in- readPageHeader (hdr{pageTypeHeader = dictionaryPageHeader}) rem' identifier- 8 ->- let- (dataPageHeaderV2, rem') = readPageTypeHeader emptyDataPageHeaderV2 rem 0- in- readPageHeader (hdr{pageTypeHeader = dataPageHeaderV2}) rem' identifier- n -> error $ "Unknown page header field " ++ show n+ if BS.null xs+ then (hdr, BS.empty)+ else+ let+ fieldContents = readField' xs lastFieldId+ in+ case fieldContents of+ Nothing -> (hdr, BS.drop 1 xs)+ Just (rem, elemType, identifier) -> case identifier of+ 1 ->+ let+ (pType, rem') = readInt32FromBytes rem+ in+ readPageHeader (hdr{pageHeaderPageType = pageTypeFromInt pType}) rem' identifier+ 2 ->+ let+ (uncompressedPageSize, rem') = readInt32FromBytes rem+ in+ readPageHeader+ (hdr{uncompressedPageSize = uncompressedPageSize})+ rem'+ identifier+ 3 ->+ let+ (compressedPageSize, rem') = readInt32FromBytes rem+ in+ readPageHeader (hdr{compressedPageSize = compressedPageSize}) rem' identifier+ 4 ->+ let+ (crc, rem') = readInt32FromBytes rem+ in+ readPageHeader (hdr{pageHeaderCrcChecksum = crc}) rem' identifier+ 5 ->+ let+ (dataPageHeader, rem') = readPageTypeHeader emptyDataPageHeader rem 0+ in+ readPageHeader (hdr{pageTypeHeader = dataPageHeader}) rem' identifier+ 6 -> error "Index page header not supported"+ 7 ->+ let+ (dictionaryPageHeader, rem') = readPageTypeHeader emptyDictionaryPageHeader rem 0+ in+ readPageHeader (hdr{pageTypeHeader = dictionaryPageHeader}) rem' identifier+ 8 ->+ let+ (dataPageHeaderV2, rem') = readPageTypeHeader emptyDataPageHeaderV2 rem 0+ in+ readPageHeader (hdr{pageTypeHeader = dataPageHeaderV2}) rem' identifier+ n -> error $ "Unknown page header field " ++ show n readPageTypeHeader ::- PageTypeHeader -> [Word8] -> Int16 -> (PageTypeHeader, [Word8])-readPageTypeHeader hdr [] _ = (hdr, [])+ PageTypeHeader -> BS.ByteString -> Int16 -> (PageTypeHeader, BS.ByteString) readPageTypeHeader INDEX_PAGE_HEADER _ _ = error "readPageTypeHeader: unsupported INDEX_PAGE_HEADER" readPageTypeHeader PAGE_TYPE_HEADER_UNKNOWN _ _ = error "readPageTypeHeader: unsupported PAGE_TYPE_HEADER_UNKNOWN" readPageTypeHeader hdr@(DictionaryPageHeader{..}) xs lastFieldId =- let- fieldContents = readField' xs lastFieldId- in- case fieldContents of- Nothing -> (hdr, drop 1 xs)- Just (rem, elemType, identifier) -> case identifier of- 1 ->- let- (numValues, rem') = readInt32FromBytes rem- in- readPageTypeHeader- (hdr{dictionaryPageHeaderNumValues = numValues})- rem'- identifier- 2 ->- let- (enc, rem') = readInt32FromBytes rem- in- readPageTypeHeader- (hdr{dictionaryPageHeaderEncoding = parquetEncodingFromInt enc})- rem'- identifier- 3 ->- let- isSorted = fromMaybe (error "readPageTypeHeader: not enough bytes") (listToMaybe rem)- in- readPageTypeHeader- (hdr{dictionaryPageIsSorted = isSorted == compactBooleanTrue})- -- TODO(mchavinda): The bool logic here is a little tricky.- -- If the field is a bool then you can get the value- -- from the byte (and you don't have to drop a field).- -- 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- identifier- n ->- error $ "readPageTypeHeader: unsupported identifier " ++ show n+ if BS.null xs+ then (hdr, BS.empty)+ else+ let+ fieldContents = readField' xs lastFieldId+ in+ case fieldContents of+ Nothing -> (hdr, BS.drop 1 xs)+ Just (rem, elemType, identifier) -> case identifier of+ 1 ->+ let+ (numValues, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader+ (hdr{dictionaryPageHeaderNumValues = numValues})+ rem'+ identifier+ 2 ->+ let+ (enc, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader+ (hdr{dictionaryPageHeaderEncoding = parquetEncodingFromInt enc})+ rem'+ identifier+ 3 ->+ let+ isSorted = fromMaybe (error "readPageTypeHeader: not enough bytes") (rem BS.!? 0)+ in+ readPageTypeHeader+ (hdr{dictionaryPageIsSorted = isSorted == compactBooleanTrue})+ -- TODO(mchavinda): The bool logic here is a little tricky.+ -- If the field is a bool then you can get the value+ -- from the byte (and you don't have to drop a field).+ -- 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+ identifier+ n ->+ error $ "readPageTypeHeader: unsupported identifier " ++ show n readPageTypeHeader hdr@(DataPageHeader{..}) xs lastFieldId =- let- fieldContents = readField' xs lastFieldId- in- case fieldContents of- Nothing -> (hdr, drop 1 xs)- Just (rem, elemType, identifier) -> case identifier of- 1 ->- let- (numValues, rem') = readInt32FromBytes rem- in- readPageTypeHeader (hdr{dataPageHeaderNumValues = numValues}) rem' identifier- 2 ->- let- (enc, rem') = readInt32FromBytes rem- in- readPageTypeHeader- (hdr{dataPageHeaderEncoding = parquetEncodingFromInt enc})- rem'- identifier- 3 ->- let- (enc, rem') = readInt32FromBytes rem- in- readPageTypeHeader- (hdr{definitionLevelEncoding = parquetEncodingFromInt enc})- rem'- identifier- 4 ->- let- (enc, rem') = readInt32FromBytes rem- in- readPageTypeHeader- (hdr{repetitionLevelEncoding = parquetEncodingFromInt enc})- rem'- identifier- 5 ->- let- (stats, rem') = readStatisticsFromBytes emptyColumnStatistics rem 0- in- readPageTypeHeader (hdr{dataPageHeaderStatistics = stats}) rem' identifier- n -> error $ show n+ if BS.null xs+ then (hdr, BS.empty)+ else+ let+ fieldContents = readField' xs lastFieldId+ in+ case fieldContents of+ Nothing -> (hdr, BS.drop 1 xs)+ Just (rem, elemType, identifier) -> case identifier of+ 1 ->+ let+ (numValues, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader (hdr{dataPageHeaderNumValues = numValues}) rem' identifier+ 2 ->+ let+ (enc, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader+ (hdr{dataPageHeaderEncoding = parquetEncodingFromInt enc})+ rem'+ identifier+ 3 ->+ let+ (enc, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader+ (hdr{definitionLevelEncoding = parquetEncodingFromInt enc})+ rem'+ identifier+ 4 ->+ let+ (enc, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader+ (hdr{repetitionLevelEncoding = parquetEncodingFromInt enc})+ rem'+ identifier+ 5 ->+ let+ (stats, rem') = readStatisticsFromBytes emptyColumnStatistics rem 0+ in+ readPageTypeHeader (hdr{dataPageHeaderStatistics = stats}) rem' identifier+ n -> error $ show n readPageTypeHeader hdr@(DataPageHeaderV2{..}) xs lastFieldId =- let- fieldContents = readField' xs lastFieldId- in- case fieldContents of- Nothing -> (hdr, drop 1 xs)- Just (rem, elemType, identifier) -> case identifier of- 1 ->- let- (numValues, rem') = readInt32FromBytes rem- in- readPageTypeHeader (hdr{dataPageHeaderV2NumValues = numValues}) rem' identifier- 2 ->- let- (numNulls, rem') = readInt32FromBytes rem- in- readPageTypeHeader (hdr{dataPageHeaderV2NumNulls = numNulls}) rem' identifier- 3 ->- let- (numRows, rem') = readInt32FromBytes rem- in- readPageTypeHeader (hdr{dataPageHeaderV2NumRows = numRows}) rem' identifier- 4 ->- let- (enc, rem') = readInt32FromBytes rem- in- readPageTypeHeader- (hdr{dataPageHeaderV2Encoding = parquetEncodingFromInt enc})- rem'- identifier- 5 ->- let- (n, rem') = readInt32FromBytes rem- in- readPageTypeHeader (hdr{definitionLevelByteLength = n}) rem' identifier- 6 ->- let- (n, rem') = readInt32FromBytes rem- in- readPageTypeHeader (hdr{repetitionLevelByteLength = n}) rem' identifier- 7 ->- let- (isCompressed, rem') = case rem of- b : bytes -> ((b .&. 0x0f) == compactBooleanTrue, bytes)- [] -> (True, [])- in- readPageTypeHeader- (hdr{dataPageHeaderV2IsCompressed = isCompressed})- rem'- identifier- 8 ->- let- (stats, rem') = readStatisticsFromBytes emptyColumnStatistics rem 0- in- readPageTypeHeader- (hdr{dataPageHeaderV2Statistics = stats})- rem'- identifier- n -> error $ show n+ if BS.null xs+ then (hdr, BS.empty)+ else+ let+ fieldContents = readField' xs lastFieldId+ in+ case fieldContents of+ Nothing -> (hdr, BS.drop 1 xs)+ Just (rem, elemType, identifier) -> case identifier of+ 1 ->+ let+ (numValues, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader (hdr{dataPageHeaderV2NumValues = numValues}) rem' identifier+ 2 ->+ let+ (numNulls, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader (hdr{dataPageHeaderV2NumNulls = numNulls}) rem' identifier+ 3 ->+ let+ (numRows, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader (hdr{dataPageHeaderV2NumRows = numRows}) rem' identifier+ 4 ->+ let+ (enc, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader+ (hdr{dataPageHeaderV2Encoding = parquetEncodingFromInt enc})+ rem'+ identifier+ 5 ->+ let+ (n, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader (hdr{definitionLevelByteLength = n}) rem' identifier+ 6 ->+ let+ (n, rem') = readInt32FromBytes rem+ in+ readPageTypeHeader (hdr{repetitionLevelByteLength = n}) rem' identifier+ 7 ->+ let+ (isCompressed, rem') = case BS.uncons rem of+ Just (b, bytes) -> ((b .&. 0x0f) == compactBooleanTrue, bytes)+ Nothing -> (True, BS.empty)+ in+ readPageTypeHeader+ (hdr{dataPageHeaderV2IsCompressed = isCompressed})+ rem'+ identifier+ 8 ->+ let+ (stats, rem') = readStatisticsFromBytes emptyColumnStatistics rem 0+ in+ readPageTypeHeader+ (hdr{dataPageHeaderV2Statistics = stats})+ rem'+ identifier+ n -> error $ show n -readField' :: [Word8] -> Int16 -> Maybe ([Word8], TType, Int16)-readField' [] _ = Nothing-readField' (x : xs) lastFieldId- | x .&. 0x0f == 0 = Nothing- | otherwise =- let modifier = fromIntegral ((x .&. 0xf0) `shiftR` 4) :: Int16- (identifier, rem) =- if modifier == 0- then readIntFromBytes @Int16 xs- else (lastFieldId + modifier, xs)- elemType = toTType (x .&. 0x0f)- in Just (rem, elemType, identifier)+readField' :: BS.ByteString -> Int16 -> Maybe (BS.ByteString, TType, Int16)+readField' bs lastFieldId = case BS.uncons bs of+ Nothing -> Nothing+ Just (x, xs) ->+ if x .&. 0x0f == 0+ then Nothing+ else+ let modifier = fromIntegral ((x .&. 0xf0) `shiftR` 4) :: Int16+ (identifier, rem) =+ if modifier == 0+ then readIntFromBytes @Int16 xs+ else (lastFieldId + modifier, xs)+ elemType = toTType (x .&. 0x0f)+ in Just (rem, elemType, identifier) -readAllPages :: CompressionCodec -> [Word8] -> IO [Page]+readAllPages :: CompressionCodec -> BS.ByteString -> IO [Page] readAllPages codec bytes = go bytes [] where- go [] acc = return (reverse acc)- go bs acc = do- (maybePage, remaining) <- readPage codec bs- case maybePage of- Nothing -> return (reverse acc)- Just page -> go remaining (page : acc)+ go bs acc =+ if BS.null bs+ then return (reverse acc)+ else do+ (maybePage, remaining) <- readPage codec bs+ case maybePage of+ Nothing -> return (reverse acc)+ Just page -> go remaining (page : acc) -readNInt32 :: Int -> [Word8] -> ([Int32], [Word8])+readNInt32 :: Int -> BS.ByteString -> ([Int32], BS.ByteString) readNInt32 0 bs = ([], bs) readNInt32 k bs =- let x = littleEndianInt32 (take 4 bs)- bs' = drop 4 bs+ let x = littleEndianInt32 (BS.take 4 bs)+ bs' = BS.drop 4 bs (xs, rest) = readNInt32 (k - 1) bs' in (x : xs, rest) -readNDouble :: Int -> [Word8] -> ([Double], [Word8])+readNDouble :: Int -> BS.ByteString -> ([Double], BS.ByteString) readNDouble 0 bs = ([], bs) readNDouble k bs =- let x = castWord64ToDouble (littleEndianWord64 (take 8 bs))- bs' = drop 8 bs+ let x = castWord64ToDouble (littleEndianWord64 (BS.take 8 bs))+ bs' = BS.drop 8 bs (xs, rest) = readNDouble (k - 1) bs' in (x : xs, rest) -readNByteArrays :: Int -> [Word8] -> ([[Word8]], [Word8])+readNByteArrays :: Int -> BS.ByteString -> ([BS.ByteString], BS.ByteString) readNByteArrays 0 bs = ([], bs) readNByteArrays k bs =- let len = fromIntegral (littleEndianInt32 (take 4 bs)) :: Int- body = take len (drop 4 bs)- bs' = drop (4 + len) bs+ let len = fromIntegral (littleEndianInt32 (BS.take 4 bs)) :: Int+ body = BS.take len (BS.drop 4 bs)+ bs' = BS.drop (4 + len) bs (xs, rest) = readNByteArrays (k - 1) bs' in (body : xs, rest) -readNBool :: Int -> [Word8] -> ([Bool], [Word8])+readNBool :: Int -> BS.ByteString -> ([Bool], BS.ByteString) readNBool 0 bs = ([], bs) readNBool count bs = let totalBytes = (count + 7) `div` 8- chunk = take totalBytes bs- rest = drop totalBytes bs- bits = concatMap (\b -> map (\i -> (b `shiftR` i) .&. 1 == 1) [0 .. 7]) chunk+ chunk = BS.take totalBytes bs+ rest = BS.drop totalBytes bs+ bits =+ concatMap+ (\b -> map (\i -> (b `shiftR` i) .&. 1 == 1) [0 .. 7])+ (BS.unpack chunk) bools = take count bits in (bools, rest) -readNInt64 :: Int -> [Word8] -> ([Int64], [Word8])+readNInt64 :: Int -> BS.ByteString -> ([Int64], BS.ByteString) readNInt64 0 bs = ([], bs) readNInt64 k bs =- let x = fromIntegral (littleEndianWord64 (take 8 bs))- bs' = drop 8 bs+ let x = fromIntegral (littleEndianWord64 (BS.take 8 bs))+ bs' = BS.drop 8 bs (xs, rest) = readNInt64 (k - 1) bs' in (x : xs, rest) -readNFloat :: Int -> [Word8] -> ([Float], [Word8])+readNFloat :: Int -> BS.ByteString -> ([Float], BS.ByteString) readNFloat 0 bs = ([], bs) readNFloat k bs =- let x = castWord32ToFloat (littleEndianWord32 (take 4 bs))- bs' = drop 4 bs+ let x = castWord32ToFloat (littleEndianWord32 (BS.take 4 bs))+ bs' = BS.drop 4 bs (xs, rest) = readNFloat (k - 1) bs' in (x : xs, rest) -splitFixed :: Int -> Int -> [Word8] -> ([[Word8]], [Word8])+splitFixed :: Int -> Int -> BS.ByteString -> ([BS.ByteString], BS.ByteString) splitFixed 0 _ bs = ([], bs) splitFixed k len bs =- let body = take len bs- bs' = drop len bs+ let body = BS.take len bs+ bs' = BS.drop len bs (xs, rest) = splitFixed (k - 1) len bs' in (body : xs, rest) readStatisticsFromBytes ::- ColumnStatistics -> [Word8] -> Int16 -> (ColumnStatistics, [Word8])+ ColumnStatistics -> BS.ByteString -> Int16 -> (ColumnStatistics, BS.ByteString) readStatisticsFromBytes cs xs lastFieldId = let fieldContents = readField' xs lastFieldId in case fieldContents of- Nothing -> (cs, drop 1 xs)+ Nothing -> (cs, BS.drop 1 xs) Just (rem, elemType, identifier) -> case identifier of 1 -> let@@ -369,19 +398,19 @@ in readStatisticsFromBytes (cs{columnMinValue = minInBytes}) rem' identifier 7 ->- case rem of- [] ->+ case BS.uncons rem of+ Nothing -> error "readStatisticsFromBytes: not enough bytes"- (isMaxValueExact : rem') ->+ Just (isMaxValueExact, rem') -> readStatisticsFromBytes (cs{isColumnMaxValueExact = isMaxValueExact == compactBooleanTrue}) rem' identifier 8 ->- case rem of- [] ->+ case BS.uncons rem of+ Nothing -> error "readStatisticsFromBytes: not enough bytes"- (isMinValueExact : rem') ->+ Just (isMinValueExact, rem') -> readStatisticsFromBytes (cs{isColumnMinValueExact = isMinValueExact == compactBooleanTrue}) rem'
src/DataFrame/IO/Parquet/Thrift.hs view
@@ -93,7 +93,7 @@ , createdBy :: Maybe String , columnOrders :: [ColumnOrder] , encryptionAlgorithm :: EncryptionAlgorithm- , footerSigningKeyMetadata :: [Word8]+ , footerSigningKeyMetadata :: BS.ByteString } deriving (Show, Eq) @@ -149,7 +149,7 @@ , createdBy = Nothing , columnOrders = [] , encryptionAlgorithm = ENCRYPTION_ALGORITHM_UNKNOWN- , footerSigningKeyMetadata = []+ , footerSigningKeyMetadata = BS.empty } data ColumnMetaData = ColumnMetaData@@ -182,7 +182,7 @@ , columnChunkColumnIndexOffset :: Int64 , columnChunkColumnIndexLength :: Int32 , cryptoMetadata :: ColumnCryptoMetadata- , encryptedColumnMetadata :: [Word8]+ , encryptedColumnMetadata :: BS.ByteString } deriving (Show, Eq) @@ -234,7 +234,16 @@ emptyColumnChunk :: ColumnChunk emptyColumnChunk =- ColumnChunk "" 0 emptyColumnMetadata 0 0 0 0 COLUMN_CRYPTO_METADATA_UNKNOWN []+ ColumnChunk+ ""+ 0+ emptyColumnMetadata+ 0+ 0+ 0+ 0+ COLUMN_CRYPTO_METADATA_UNKNOWN+ BS.empty emptyKeyValue :: KeyValue emptyKeyValue = KeyValue{key = "", value = ""}@@ -754,13 +763,23 @@ Just (elemType, identifier) -> case identifier of 1 -> do readAesGcmV1- (AesGcmV1{aadPrefix = [], aadFileUnique = [], supplyAadPrefix = False})+ ( AesGcmV1+ { aadPrefix = BS.empty+ , aadFileUnique = BS.empty+ , supplyAadPrefix = False+ }+ ) buf pos 0 2 -> do readAesGcmCtrV1- (AesGcmCtrV1{aadPrefix = [], aadFileUnique = [], supplyAadPrefix = False})+ ( AesGcmCtrV1+ { aadPrefix = BS.empty+ , aadFileUnique = BS.empty+ , supplyAadPrefix = False+ }+ ) buf pos 0@@ -792,21 +811,21 @@ Just (elemType, identifier) -> case identifier of 1 -> do aadPrefix <- readByteString buf pos- readAesGcmCtrV1 (v{aadPrefix = aadPrefix}) buf pos lastFieldId+ readAesGcmCtrV1 (v{aadPrefix = aadPrefix}) buf pos identifier 2 -> do aadFileUnique <- readByteString buf pos readAesGcmCtrV1 (v{aadFileUnique = aadFileUnique}) buf pos- lastFieldId+ identifier 3 -> do supplyAadPrefix <- readAndAdvance pos buf readAesGcmCtrV1 (v{supplyAadPrefix = supplyAadPrefix == compactBooleanTrue}) buf pos- lastFieldId+ identifier _ -> return ENCRYPTION_ALGORITHM_UNKNOWN readAesGcmCtrV1 _ _ _ _ = error "readAesGcmCtrV1 called with non AesGcmCtrV1"@@ -824,17 +843,17 @@ Just (elemType, identifier) -> case identifier of 1 -> do aadPrefix <- readByteString buf pos- readAesGcmV1 (v{aadPrefix = aadPrefix}) buf pos lastFieldId+ readAesGcmV1 (v{aadPrefix = aadPrefix}) buf pos identifier 2 -> do aadFileUnique <- readByteString buf pos- readAesGcmV1 (v{aadFileUnique = aadFileUnique}) buf pos lastFieldId+ readAesGcmV1 (v{aadFileUnique = aadFileUnique}) buf pos identifier 3 -> do supplyAadPrefix <- readAndAdvance pos buf readAesGcmV1 (v{supplyAadPrefix = supplyAadPrefix == compactBooleanTrue}) buf pos- lastFieldId+ identifier _ -> return ENCRYPTION_ALGORITHM_UNKNOWN readAesGcmV1 _ _ _ _ = error "readAesGcmV1 called with non AesGcmV1"@@ -1101,10 +1120,10 @@ Just (elemType, identifier) -> case identifier of 1 -> do scale' <- readInt32FromBuffer buf pos- readDecimalType precision scale' buf pos lastFieldId+ readDecimalType precision scale' buf pos identifier 2 -> do precision' <- readInt32FromBuffer buf pos- readDecimalType precision' scale buf pos lastFieldId+ readDecimalType precision' scale buf pos identifier _ -> error $ "UNKNOWN field ID for DecimalType" ++ show identifier readTimeType ::@@ -1117,15 +1136,14 @@ readTimeType isAdjustedToUTC unit buf pos lastFieldId = do fieldContents <- readField buf pos lastFieldId case fieldContents of- Nothing -> return (TimeType isAdjustedToUTC unit)+ Nothing -> return (TimeType{isAdjustedToUTC = isAdjustedToUTC, unit = unit}) Just (elemType, identifier) -> case identifier of 1 -> do- -- TODO: Check for empty- isAdjustedToUTC' <- (== compactBooleanTrue) <$> readAndAdvance pos buf- readTimeType isAdjustedToUTC' unit buf pos lastFieldId+ let isAdjustedToUTC' = elemType == toTType compactBooleanTrue+ readTimeType isAdjustedToUTC' unit buf pos identifier 2 -> do unit' <- readUnit TIME_UNIT_UNKNOWN buf pos 0- readTimeType isAdjustedToUTC unit' buf pos lastFieldId+ readTimeType isAdjustedToUTC unit' buf pos identifier _ -> error $ "UNKNOWN field ID for TimeType" ++ show identifier readTimestampType ::@@ -1138,17 +1156,15 @@ readTimestampType isAdjustedToUTC unit buf pos lastFieldId = do fieldContents <- readField buf pos lastFieldId case fieldContents of- Nothing -> return (TimestampType isAdjustedToUTC unit)+ Nothing -> return (TimestampType{isAdjustedToUTC = isAdjustedToUTC, unit = unit}) Just (elemType, identifier) -> case identifier of 1 -> do- -- TODO: Check for empty- isAdjustedToUTC' <- (== compactBooleanTrue) <$> readNoAdvance pos buf- readTimestampType False unit buf pos lastFieldId+ let isAdjustedToUTC' = elemType == toTType compactBooleanTrue+ readTimestampType isAdjustedToUTC' unit buf pos identifier 2 -> do- _ <- readField buf pos identifier unit' <- readUnit TIME_UNIT_UNKNOWN buf pos 0- readTimestampType isAdjustedToUTC unit' buf pos lastFieldId- _ -> error $ "UNKNOWN field ID for TimestampType" ++ show identifier+ readTimestampType isAdjustedToUTC 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@@ -1157,9 +1173,12 @@ Nothing -> return unit Just (elemType, identifier) -> case identifier of 1 -> do+ _ <- readField buf pos 0 readUnit MILLISECONDS buf pos identifier 2 -> do+ _ <- readField buf pos 0 readUnit MICROSECONDS buf pos identifier 3 -> do+ _ <- readField buf pos 0 readUnit NANOSECONDS buf pos identifier n -> error $ "Unknown time unit: " ++ show n
src/DataFrame/IO/Parquet/Time.hs view
@@ -2,16 +2,17 @@ module DataFrame.IO.Parquet.Time where +import qualified Data.ByteString as BS import Data.Time import Data.Word import DataFrame.IO.Parquet.Binary -int96ToUTCTime :: [Word8] -> UTCTime+int96ToUTCTime :: BS.ByteString -> UTCTime int96ToUTCTime bytes- | length bytes /= 12 = error "INT96 must be exactly 12 bytes"+ | BS.length bytes /= 12 = error "INT96 must be exactly 12 bytes" | otherwise =- let (nanosBytes, julianBytes) = splitAt 8 bytes+ let (nanosBytes, julianBytes) = BS.splitAt 8 bytes nanosSinceMidnight = littleEndianWord64 nanosBytes julianDay = littleEndianWord32 julianBytes in julianDayAndNanosToUTCTime (fromIntegral julianDay) nanosSinceMidnight@@ -38,13 +39,13 @@ -- I include this here even though it's unused because we'll likely use -- it for the writer. Since int96 is deprecated this is only included for completeness anyway.-utcTimeToInt96 :: UTCTime -> [Word8]+utcTimeToInt96 :: UTCTime -> BS.ByteString utcTimeToInt96 (UTCTime day diffTime) = let julianDay = dayToJulianDay day nanosSinceMidnight = floor (realToFrac diffTime * 1_000_000_000) nanosBytes = word64ToLittleEndian nanosSinceMidnight julianBytes = word32ToLittleEndian (fromIntegral julianDay)- in nanosBytes ++ julianBytes+ in nanosBytes `BS.append` julianBytes dayToJulianDay :: Day -> Integer dayToJulianDay day =
src/DataFrame/IO/Parquet/Types.hs view
@@ -1,9 +1,9 @@ module DataFrame.IO.Parquet.Types where +import qualified Data.ByteString as BS import Data.Int import qualified Data.Text as T import Data.Time-import Data.Word data ParquetType = PBOOLEAN@@ -125,26 +125,26 @@ emptyGeospatialStatistics = GeospatialStatistics emptyBoundingBox [] data ColumnStatistics = ColumnStatistics- { columnMin :: [Word8]- , columnMax :: [Word8]+ { columnMin :: BS.ByteString+ , columnMax :: BS.ByteString , columnNullCount :: Int64 , columnDistictCount :: Int64- , columnMinValue :: [Word8]- , columnMaxValue :: [Word8]+ , columnMinValue :: BS.ByteString+ , columnMaxValue :: BS.ByteString , isColumnMaxValueExact :: Bool , isColumnMinValueExact :: Bool } deriving (Show, Eq) emptyColumnStatistics :: ColumnStatistics-emptyColumnStatistics = ColumnStatistics [] [] 0 0 [] [] False False+emptyColumnStatistics = ColumnStatistics BS.empty BS.empty 0 0 BS.empty BS.empty False False data ColumnCryptoMetadata = COLUMN_CRYPTO_METADATA_UNKNOWN | ENCRYPTION_WITH_FOOTER_KEY | EncryptionWithColumnKey { columnCryptPathInSchema :: [String]- , columnKeyMetadata :: [Word8]+ , columnKeyMetadata :: BS.ByteString } deriving (Show, Eq) @@ -166,13 +166,13 @@ data EncryptionAlgorithm = ENCRYPTION_ALGORITHM_UNKNOWN | AesGcmV1- { aadPrefix :: [Word8]- , aadFileUnique :: [Word8]+ { aadPrefix :: BS.ByteString+ , aadFileUnique :: BS.ByteString , supplyAadPrefix :: Bool } | AesGcmCtrV1- { aadPrefix :: [Word8]- , aadFileUnique :: [Word8]+ { aadPrefix :: BS.ByteString+ , aadFileUnique :: BS.ByteString , supplyAadPrefix :: Bool } deriving (Show, Eq)@@ -189,7 +189,7 @@ data Page = Page { pageHeader :: PageHeader- , pageBytes :: [Word8]+ , pageBytes :: BS.ByteString } deriving (Show, Eq)
src/DataFrame/Internal/Column.hs view
@@ -30,6 +30,7 @@ import Control.Monad.ST (runST) import Data.Kind (Type) import Data.Maybe+import Data.These import Data.Type.Equality (TestEquality (..)) import DataFrame.Errors import DataFrame.Internal.Parsing@@ -57,15 +58,21 @@ TColumn :: (Columnable a) => Column -> TypedColumn a instance (Eq a) => Eq (TypedColumn a) where+ (==) :: (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 +-- | Gets the underlying vector from a TypedColumn.+vectorFromTypedColumn :: TypedColumn a -> VB.Vector a+vectorFromTypedColumn (TColumn value) = either throw id (toVector value)+ -- | Checks if a column contains missing values. hasMissing :: Column -> Bool hasMissing (OptionalColumn column) = True@@ -351,12 +358,8 @@ {-# INLINE atIndicesStable #-} atIndicesWithNulls :: VB.Vector (Maybe Int) -> Column -> Column-atIndicesWithNulls indices column- | VB.all isJust indices =- atIndicesStable- (VU.fromList (catMaybes (VB.toList indices)))- column- | otherwise = case column of+atIndicesWithNulls indices column =+ case column of BoxedColumn col -> OptionalColumn $ VB.map (fmap (col VB.!)) indices UnboxedColumn col ->@@ -614,6 +617,87 @@ zipColumns (OptionalColumn optcolumn) (UnboxedColumn column) = BoxedColumn (VG.zip optcolumn (VB.convert column)) zipColumns (OptionalColumn optcolumn) (OptionalColumn optother) = BoxedColumn (VG.zip optcolumn optother) {-# INLINE zipColumns #-}++-- | Merge two columns using `These`.+mergeColumns :: Column -> Column -> Column+mergeColumns (BoxedColumn column) (BoxedColumn other) = BoxedColumn (VG.zipWith These column other)+mergeColumns (BoxedColumn column) (UnboxedColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ (\i -> These (column VG.! i) (other VG.! i))+ )+mergeColumns (BoxedColumn column) (OptionalColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ ( \i ->+ if isNothing (other VG.! i)+ then This (column VG.! i)+ else These (column VG.! i) (fromJust $ other VG.! i)+ )+ )+mergeColumns (UnboxedColumn column) (BoxedColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ (\i -> These (column VG.! i) (other VG.! i))+ )+mergeColumns (UnboxedColumn column) (UnboxedColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ (\i -> These (column VG.! i) (other VG.! i))+ )+mergeColumns (UnboxedColumn column) (OptionalColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ ( \i ->+ if isNothing (other VG.! i)+ then This (column VG.! i)+ else These (column VG.! i) (fromJust $ other VG.! i)+ )+ )+mergeColumns (OptionalColumn column) (BoxedColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ ( \i ->+ if isNothing (column VG.! i)+ then That (other VG.! i)+ else These (fromJust $ column VG.! i) (other VG.! i)+ )+ )+mergeColumns (OptionalColumn column) (UnboxedColumn other) =+ BoxedColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ ( \i ->+ if isNothing (column VG.! i)+ then That (other VG.! i)+ else These (fromJust $ column VG.! i) (other VG.! i)+ )+ )+mergeColumns (OptionalColumn column) (OptionalColumn other) =+ OptionalColumn+ ( VB.generate+ (min (VG.length column) (VG.length other))+ ( \i ->+ if isNothing (column VG.! i) && isNothing (other VG.! i)+ then Nothing+ else+ ( if isNothing (column VG.! i)+ then Just (That (fromJust $ other VG.! i))+ else+ ( if isNothing (other VG.! i)+ then Just (This (fromJust $ column VG.! i))+ else Just (These (fromJust $ column VG.! i) (fromJust $ other VG.! i))+ )+ )+ )+ )+{-# INLINE mergeColumns #-} -- | An internal, column version of zipWith. zipWithColumns ::
src/DataFrame/Internal/Expression.hs view
@@ -17,75 +17,100 @@ import qualified Data.Text as T import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl)) import qualified Data.Vector.Generic as VG-import qualified Data.Vector.Unboxed as VU import DataFrame.Internal.Column import Type.Reflection (Typeable, typeOf, typeRep) +data UnaryOp a b = MkUnaryOp+ { unaryFn :: a -> b+ , unaryName :: T.Text+ , unarySymbol :: Maybe T.Text+ }++data BinaryOp a b c = MkBinaryOp+ { binaryFn :: a -> b -> c+ , binaryName :: T.Text+ , binarySymbol :: Maybe T.Text+ , binaryCommutative :: Bool+ , binaryPrecedence :: Int+ }++data AggStrategy a b where+ CollectAgg ::+ (VG.Vector v b, Typeable v) => T.Text -> (v b -> a) -> AggStrategy a b+ FoldAgg :: T.Text -> Maybe a -> (a -> b -> a) -> AggStrategy a b+ data Expr a where Col :: (Columnable a) => T.Text -> Expr a Lit :: (Columnable a) => a -> Expr a- UnaryOp ::- (Columnable a, Columnable b) => T.Text -> (b -> a) -> Expr b -> Expr a- BinaryOp ::+ Unary ::+ (Columnable a, Columnable b) => UnaryOp b a -> Expr b -> Expr a+ Binary :: (Columnable c, Columnable b, Columnable a) =>- T.Text -> (c -> b -> a) -> Expr c -> Expr b -> Expr a+ BinaryOp c b a -> Expr c -> Expr b -> Expr a If :: (Columnable a) => Expr Bool -> Expr a -> Expr a -> Expr a- AggVector ::- (VG.Vector v b, Typeable v, Columnable a, Columnable b) =>- Expr b -> T.Text -> (v b -> a) -> Expr a- AggReduce :: (Columnable a) => Expr a -> T.Text -> (a -> a -> a) -> Expr a- -- TODO(mchav): Numeric reduce might be superfluous since expressions are already type checked.- AggNumericVector ::- (Columnable a, Columnable b, VU.Unbox a, VU.Unbox b, Num a, Num b) =>- Expr b -> T.Text -> (VU.Vector b -> a) -> Expr a- AggFold ::- forall a b.- (Columnable a, Columnable b) => Expr b -> T.Text -> a -> (a -> b -> a) -> Expr a+ Agg :: (Columnable a, Columnable b) => AggStrategy a b -> Expr b -> Expr a data UExpr where- Wrap :: (Columnable a) => Expr a -> UExpr+ UExpr :: (Columnable a) => Expr a -> UExpr instance Show UExpr where show :: UExpr -> String- show (Wrap expr) = show expr+ show (UExpr expr) = show expr type NamedExpr = (T.Text, UExpr) instance (Num a, Columnable a) => Num (Expr a) where (+) :: Expr a -> Expr a -> Expr a- (+) (Lit x) (Lit y) = Lit (x + y)- (+) e1 e2- | e1 == e2 = BinaryOp "mult" (*) e1 (Lit 2)- | otherwise = BinaryOp "add" (+) e1 e2+ (+) =+ Binary+ ( MkBinaryOp+ { binaryFn = (+)+ , binaryName = "add"+ , binarySymbol = Just "+"+ , binaryCommutative = True+ , binaryPrecedence = 6+ }+ ) (-) :: Expr a -> Expr a -> Expr a- (-) (Lit x) (Lit y) = Lit (x - y)- (-) e1 e2 = BinaryOp "sub" (-) e1 e2+ (-) =+ Binary+ ( MkBinaryOp+ { binaryFn = (-)+ , binaryName = "sub"+ , binarySymbol = Just "-"+ , binaryCommutative = False+ , binaryPrecedence = 6+ }+ ) (*) :: Expr a -> Expr a -> Expr a- (*) (Lit 0) _ = Lit 0- (*) _ (Lit 0) = Lit 0- (*) (Lit 1) e = e- (*) e (Lit 1) = e- (*) (Lit x) (Lit y) = Lit (x * y)- (*) e1 e2- | e1 == e2 = BinaryOp "pow" (^) e1 (Lit @Int 2)- | otherwise = BinaryOp "mult" (*) e1 e2+ (*) =+ Binary+ ( MkBinaryOp+ { binaryFn = (*)+ , binaryName = "mult"+ , binarySymbol = Just "*"+ , binaryCommutative = True+ , binaryPrecedence = 7+ }+ ) fromInteger :: Integer -> Expr a fromInteger = Lit . fromInteger negate :: Expr a -> Expr a- negate (Lit n) = Lit (negate n)- negate expr = UnaryOp "negate" negate expr+ negate =+ Unary+ (MkUnaryOp{unaryFn = negate, unaryName = "negate", unarySymbol = Nothing}) abs :: (Num a) => Expr a -> Expr a- abs (Lit n) = Lit (abs n)- abs expr = UnaryOp "abs" abs expr+ abs = Unary (MkUnaryOp{unaryFn = abs, unaryName = "abs", unarySymbol = Nothing}) signum :: (Num a) => Expr a -> Expr a- signum (Lit n) = Lit (signum n)- signum expr = UnaryOp "signum" signum expr+ signum =+ Unary+ (MkUnaryOp{unaryFn = signum, unaryName = "signum", unarySymbol = Nothing}) add :: (Num a, Columnable a) => Expr a -> Expr a -> Expr a add = (+)@@ -101,8 +126,16 @@ fromRational = Lit . fromRational (/) :: (Fractional a, Columnable a) => Expr a -> Expr a -> Expr a- (/) (Lit l1) (Lit l2) = Lit (l1 / l2)- (/) e1 e2 = BinaryOp "divide" (/) e1 e2+ (/) =+ Binary+ ( MkBinaryOp+ { binaryFn = (/)+ , binaryName = "divide"+ , binarySymbol = Just "/"+ , binaryCommutative = True+ , binaryPrecedence = 7+ }+ ) divide :: (Fractional a, Columnable a) => Expr a -> Expr a -> Expr a divide = (/)@@ -115,75 +148,96 @@ pi :: (Floating a, Columnable a) => Expr a pi = Lit pi exp :: (Floating a, Columnable a) => Expr a -> Expr a- exp = UnaryOp "exp" exp+ exp = Unary (MkUnaryOp{unaryFn = exp, unaryName = "exp", unarySymbol = Nothing})+ sqrt :: (Floating a, Columnable a) => Expr a -> Expr a+ sqrt =+ Unary (MkUnaryOp{unaryFn = sqrt, unaryName = "sqrt", unarySymbol = Nothing})+ (**) :: (Floating a, Columnable a) => Expr a -> Expr a -> Expr a+ (**) =+ Binary+ ( MkBinaryOp+ { binaryFn = (**)+ , binaryName = "exponentiate"+ , binarySymbol = Just "**"+ , binaryCommutative = False+ , binaryPrecedence = 8+ }+ ) log :: (Floating a, Columnable a) => Expr a -> Expr a- log = UnaryOp "log" log+ log = Unary (MkUnaryOp{unaryFn = log, unaryName = "log", unarySymbol = Nothing})+ logBase :: (Floating a, Columnable a) => Expr a -> Expr a -> Expr a+ logBase =+ Binary+ ( MkBinaryOp+ { binaryFn = logBase+ , binaryName = "logBase"+ , binarySymbol = Nothing+ , binaryCommutative = False+ , binaryPrecedence = 1+ }+ ) sin :: (Floating a, Columnable a) => Expr a -> Expr a- sin = UnaryOp "sin" sin+ sin = Unary (MkUnaryOp{unaryFn = sin, unaryName = "sin", unarySymbol = Nothing}) cos :: (Floating a, Columnable a) => Expr a -> Expr a- cos = UnaryOp "cos" cos+ cos = Unary (MkUnaryOp{unaryFn = cos, unaryName = "cos", unarySymbol = Nothing})+ tan :: (Floating a, Columnable a) => Expr a -> Expr a+ tan = Unary (MkUnaryOp{unaryFn = tan, unaryName = "tan", unarySymbol = Nothing}) asin :: (Floating a, Columnable a) => Expr a -> Expr a- asin = UnaryOp "asin" asin+ asin =+ Unary (MkUnaryOp{unaryFn = asin, unaryName = "asin", unarySymbol = Nothing}) acos :: (Floating a, Columnable a) => Expr a -> Expr a- acos = UnaryOp "acos" acos+ acos =+ Unary (MkUnaryOp{unaryFn = acos, unaryName = "acos", unarySymbol = Nothing}) atan :: (Floating a, Columnable a) => Expr a -> Expr a- atan = UnaryOp "atan" atan+ atan =+ Unary (MkUnaryOp{unaryFn = atan, unaryName = "atan", unarySymbol = Nothing}) sinh :: (Floating a, Columnable a) => Expr a -> Expr a- sinh = UnaryOp "sinh" sinh+ sinh =+ Unary (MkUnaryOp{unaryFn = sinh, unaryName = "sinh", unarySymbol = Nothing}) cosh :: (Floating a, Columnable a) => Expr a -> Expr a- cosh = UnaryOp "cosh" cosh+ cosh =+ Unary (MkUnaryOp{unaryFn = cosh, unaryName = "cosh", unarySymbol = Nothing}) asinh :: (Floating a, Columnable a) => Expr a -> Expr a- asinh = UnaryOp "asinh" sinh+ asinh =+ Unary+ (MkUnaryOp{unaryFn = asinh, unaryName = "asinh", unarySymbol = Nothing}) acosh :: (Floating a, Columnable a) => Expr a -> Expr a- acosh = UnaryOp "acosh" acosh+ acosh =+ Unary+ (MkUnaryOp{unaryFn = acosh, unaryName = "acosh", unarySymbol = Nothing}) atanh :: (Floating a, Columnable a) => Expr a -> Expr a- atanh = UnaryOp "atanh" atanh+ atanh =+ Unary+ (MkUnaryOp{unaryFn = atanh, unaryName = "atanh", unarySymbol = Nothing}) instance (Show a) => Show (Expr a) where show :: forall a. (Show a) => Expr a -> String show (Col name) = "(col @" ++ show (typeRep @a) ++ " " ++ show name ++ ")" show (Lit value) = "(lit (" ++ show value ++ "))" show (If cond l r) = "(ifThenElse " ++ show cond ++ " " ++ show l ++ " " ++ show r ++ ")"- show (UnaryOp name f value) = "(" ++ T.unpack name ++ " " ++ show value ++ ")"- show (BinaryOp name f a b) = "(" ++ T.unpack name ++ " " ++ show a ++ " " ++ show b ++ ")"- show (AggNumericVector expr op _) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")"- show (AggVector expr op _) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")"- show (AggReduce expr op _) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")"- show (AggFold expr op _ _) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")"+ show (Unary op value) = "(" ++ T.unpack (unaryName op) ++ " " ++ show value ++ ")"+ show (Binary op a b) = "(" ++ T.unpack (binaryName op) ++ " " ++ show a ++ " " ++ show b ++ ")"+ show (Agg (CollectAgg op _) expr) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")"+ show (Agg (FoldAgg op _ _) expr) = "(" ++ T.unpack op ++ " " ++ show expr ++ ")" normalize :: (Eq a, Ord a, Show a, Typeable a) => Expr a -> Expr a normalize expr = case expr of Col name -> Col name Lit val -> Lit val If cond th el -> If (normalize cond) (normalize th) (normalize el)- UnaryOp name f e -> UnaryOp name f (normalize e)- BinaryOp name f e1 e2- | isCommutative name ->+ Unary op e -> Unary op (normalize e)+ Binary op e1 e2+ | binaryCommutative op -> let n1 = normalize e1 n2 = normalize e2 in case testEquality (typeOf n1) (typeOf n2) of Nothing -> expr Just Refl -> if compareExpr n1 n2 == GT- then BinaryOp name f n2 n1 -- Swap to canonical order- else BinaryOp name f n1 n2- | otherwise -> BinaryOp name f (normalize e1) (normalize e2)- AggVector e name f -> AggVector (normalize e) name f- AggReduce e name f -> AggReduce (normalize e) name f- AggNumericVector e name f -> AggNumericVector (normalize e) name f- AggFold e name init f -> AggFold (normalize e) name init f--isCommutative :: T.Text -> Bool-isCommutative name =- name- `elem` [ "add"- , "mult"- , "min"- , "max"- , "eq"- , "and"- , "or"- ]+ then Binary op n2 n1 -- Swap to canonical order+ else Binary op n1 n2+ | otherwise -> Binary op (normalize e1) (normalize e2)+ Agg strat e -> Agg strat (normalize e) -- Compare expressions for ordering (used in normalization) compareExpr :: Expr a -> Expr a -> Ordering@@ -193,12 +247,10 @@ exprKey (Col name) = "0:" ++ T.unpack name exprKey (Lit val) = "1:" ++ show val exprKey (If c t e) = "2:" ++ exprKey c ++ exprKey t ++ exprKey e- exprKey (UnaryOp name _ e) = "3:" ++ T.unpack name ++ exprKey e- exprKey (BinaryOp name _ e1 e2) = "4:" ++ T.unpack name ++ exprKey e1 ++ exprKey e2- exprKey (AggVector e name _) = "5:" ++ T.unpack name ++ exprKey e- exprKey (AggReduce e name _) = "6:" ++ T.unpack name ++ exprKey e- exprKey (AggNumericVector e name _) = "7:" ++ T.unpack name ++ exprKey e- exprKey (AggFold e name _ _) = "8:" ++ T.unpack name ++ 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 (Agg (CollectAgg name _) e) = "5:" ++ T.unpack name ++ exprKey e+ exprKey (Agg (FoldAgg name _ _) e) = "5:" ++ T.unpack name ++ exprKey e instance (Eq a, Columnable a) => Eq (Expr a) where (==) l r = eqNormalized (normalize l) (normalize r)@@ -212,18 +264,12 @@ eqNormalized (Lit v1) (Lit v2) = v1 == v2 eqNormalized (If c1 t1 e1) (If c2 t2 e2) = c1 == c2 && t1 `exprEq` t2 && e1 `exprEq` e2- eqNormalized (UnaryOp n1 _ e1) (UnaryOp n2 _ e2) =- n1 == n2 && e1 `exprEq` e2- eqNormalized (BinaryOp n1 _ e1a e1b) (BinaryOp n2 _ e2a e2b) =- n1 == n2 && e1a `exprEq` e2a && e1b `exprEq` e2b- eqNormalized (AggVector e1 n1 _) (AggVector e2 n2 _) =- n1 == n2 && e1 `exprEq` e2- eqNormalized (AggReduce e1 n1 _) (AggReduce e2 n2 _) =+ eqNormalized (Unary op1 e1) (Unary op2 e2) = unaryName op1 == unaryName op2 && e1 `exprEq` e2+ eqNormalized (Binary op1 e1a e1b) (Binary op2 e2a e2b) = binaryName op1 == binaryName op2 && e1a `exprEq` e2a && e1b `exprEq` e2b+ eqNormalized (Agg (CollectAgg n1 _) e1) (Agg (CollectAgg n2 _) e2) = n1 == n2 && e1 `exprEq` e2- eqNormalized (AggNumericVector e1 n1 _) (AggNumericVector e2 n2 _) =+ eqNormalized (Agg (FoldAgg n1 _ _) e1) (Agg (FoldAgg n2 _ _) e2) = n1 == n2 && e1 `exprEq` e2- eqNormalized (AggFold e1 n1 i1 _) (AggFold e2 n2 i2 _) =- n1 == n2 && e1 `exprEq` e2 && i1 == i2 eqNormalized _ _ = False instance (Ord a, Columnable a) => Ord (Expr a) where@@ -233,35 +279,23 @@ (Lit v1, Lit v2) -> compare v1 v2 (If c1 t1 e1', If c2 t2 e2') -> compare c1 c2 <> exprComp t1 t2 <> exprComp e1' e2'- (UnaryOp n1 _ e1', UnaryOp n2 _ e2') ->- compare n1 n2 <> exprComp e1' e2'- (BinaryOp n1 _ a1 b1, BinaryOp n2 _ a2 b2) ->- compare n1 n2 <> exprComp a1 a2 <> exprComp b1 b2- (AggVector e1' n1 _, AggVector e2' n2 _) ->- compare n1 n2 <> exprComp e1' e2'- (AggReduce e1' n1 _, AggReduce e2' n2 _) ->- compare n1 n2 <> exprComp e1' e2'- (AggNumericVector e1' n1 _, AggNumericVector e2' n2 _) ->- compare n1 n2 <> exprComp e1' e2'- (AggFold e1' n1 i1 _, AggFold e2' n2 i2 _) ->- compare n1 n2 <> exprComp e1' e2' <> compare i1 i2+ (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' -- Different constructors - compare by priority (Col _, _) -> LT (_, Col _) -> GT (Lit _, _) -> LT (_, Lit _) -> GT- (UnaryOp{}, _) -> LT- (_, UnaryOp{}) -> GT- (BinaryOp{}, _) -> LT- (_, BinaryOp{}) -> GT+ (Unary{}, _) -> LT+ (_, Unary{}) -> GT+ (Binary{}, _) -> LT+ (_, Binary{}) -> GT (If{}, _) -> LT (_, If{}) -> GT- (AggVector{}, _) -> LT- (_, AggVector{}) -> GT- (AggReduce{}, _) -> LT- (_, AggReduce{}) -> GT- (AggNumericVector{}, _) -> LT- (_, AggNumericVector{}) -> GT+ (Agg{}, _) -> LT exprComp :: (Columnable b, Columnable c) => Expr b -> Expr c -> Ordering exprComp e1 e2 = case testEquality (typeOf e1) (typeOf e2) of@@ -283,70 +317,58 @@ (Lit _) -> expr (If cond l r) -> If (replaceExpr new old cond) (replaceExpr new old l) (replaceExpr new old r)- (UnaryOp name f value) -> UnaryOp name f (replaceExpr new old value)- (BinaryOp name f l r) -> BinaryOp name f (replaceExpr new old l) (replaceExpr new old r)- (AggNumericVector expr op f) -> AggNumericVector (replaceExpr new old expr) op f- (AggVector expr op f) -> AggVector (replaceExpr new old expr) op f- (AggReduce expr op f) -> AggReduce (replaceExpr new old expr) op f- (AggFold expr op acc f) -> AggFold (replaceExpr new old expr) op acc f+ (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) eSize :: Expr a -> Int eSize (Col _) = 1 eSize (Lit _) = 1 eSize (If c l r) = 1 + eSize c + eSize l + eSize r-eSize (UnaryOp _ _ e) = 1 + eSize e-eSize (BinaryOp _ _ l r) = 1 + eSize l + eSize r-eSize (AggNumericVector expr op _) = eSize expr + 1-eSize (AggVector expr op _) = eSize expr + 1-eSize (AggReduce expr op _) = eSize expr + 1-eSize (AggFold expr op _ _) = eSize expr + 1+eSize (Unary _ e) = 1 + eSize e+eSize (Binary _ l r) = 1 + eSize l + eSize r+eSize (Agg strategy expr) = eSize expr + 1 getColumns :: Expr a -> [T.Text] getColumns (Col cName) = [cName] getColumns expr@(Lit _) = [] getColumns (If cond l r) = getColumns cond <> getColumns l <> getColumns r-getColumns (UnaryOp name f value) = getColumns value-getColumns (BinaryOp name f l r) = getColumns l <> getColumns r-getColumns (AggNumericVector expr op f) = getColumns expr-getColumns (AggVector expr op f) = getColumns expr-getColumns (AggReduce expr op f) = getColumns expr-getColumns (AggFold expr op acc f) = getColumns expr+getColumns (Unary op value) = getColumns value+getColumns (Binary op l r) = getColumns l <> getColumns r+getColumns (Agg strategy expr) = getColumns expr prettyPrint :: Expr a -> String-prettyPrint = go 0+prettyPrint = go 0 0 where- go :: Int -> Expr a -> String- go prec expr = case expr of+ indent :: Int -> String+ indent n = replicate (n * 2) ' '++ go :: Int -> Int -> Expr a -> String+ go depth prec expr = case expr of Col name -> T.unpack name Lit value -> show value If cond t e ->- "if (" ++ go 0 cond ++ ") then (" ++ go 0 t ++ ") else (" ++ go 0 e ++ ")"- UnaryOp name _ arg -> T.unpack name ++ "(" ++ go 0 arg ++ ")"- BinaryOp name _ l r ->- let p = opPrec name- inner = go p l ++ " " ++ opSym name ++ " " ++ go p r+ let inner =+ "if "+ ++ go (depth + 1) 0 cond+ ++ "\n"+ ++ indent (depth + 1)+ ++ "then "+ ++ go (depth + 1) 0 t+ ++ "\n"+ ++ indent (depth + 1)+ ++ "else "+ ++ go (depth + 1) 0 e+ in if prec > 0 then "(" ++ inner ++ ")" else inner+ Unary op arg -> case unarySymbol op of+ Nothing -> T.unpack (unaryName op) ++ "(" ++ go depth 0 arg ++ ")"+ Just sym -> T.unpack sym ++ "(" ++ go depth 0 arg ++ ")"+ Binary op l r ->+ let p = binaryPrecedence op+ inner = case binarySymbol op of+ Just name -> go depth p l ++ " " ++ T.unpack name ++ " " ++ go depth p r+ Nothing ->+ T.unpack (binaryName op) ++ "(" ++ go depth p l ++ ", " ++ go depth p r ++ ")" in if prec > p then "(" ++ inner ++ ")" else inner- AggVector arg op _ -> T.unpack op ++ "(" ++ go 0 arg ++ ")"- AggReduce arg op _ -> T.unpack op ++ "(" ++ go 0 arg ++ ")"- AggNumericVector arg op _ -> T.unpack op ++ "(" ++ go 0 arg ++ ")"- AggFold arg op _ _ -> T.unpack op ++ "(" ++ go 0 arg ++ ")"-- opPrec :: T.Text -> Int- opPrec "add" = 1- opPrec "sub" = 1- opPrec "mult" = 2- opPrec "divide" = 2- opPrec "pow" = 3- opPrec _ = 0-- opSym :: T.Text -> String- opSym "add" = "+"- opSym "sub" = "-"- opSym "mult" = "*"- opSym "divide" = "/"- opSym "pow" = "^"- opSym "lt" = "<"- opSym "leq" = "<="- opSym "gt" = ">"- opSym "geq" = ">="- opSym other = T.unpack other+ Agg (CollectAgg op _) arg -> T.unpack op ++ "(" ++ go depth 0 arg ++ ")"+ Agg (FoldAgg op _ _) arg -> T.unpack op ++ "(" ++ go depth 0 arg ++ ")"
src/DataFrame/Internal/Interpreter.hs view
@@ -17,7 +17,6 @@ import Control.Monad.ST (runST) import Data.Bifunctor import qualified Data.Map as M-import Data.Maybe (fromMaybe, isJust) import qualified Data.Text as T import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl)) import qualified Data.Vector as V@@ -43,36 +42,36 @@ where columnNotFound = Left $ ColumnNotFoundException name "" (M.keys $ columnIndices df) -- Unary operations.-interpret df expr@(UnaryOp _ (f :: c -> d) value) = first (handleInterpretException (show expr)) $ do- (TColumn value') <- interpret @c df value- fmap TColumn (mapColumn f value')+interpret df expr@(Unary (op :: UnaryOp b c) value) = first (handleInterpretException (show expr)) $ do+ (TColumn value') <- interpret df value+ fmap TColumn (mapColumn (unaryFn op) value') -- Variations of binary operations.-interpret df expr@(BinaryOp _ (f :: c -> d -> e) left right) = first (handleInterpretException (show expr)) $ case (left, right) of- (Lit left, Lit right) -> interpret df (Lit (f left right))+interpret df expr@(Binary (op :: BinaryOp c b a) left right) = first (handleInterpretException (show expr)) $ case (left, right) of+ (Lit left, Lit right) -> interpret df (Lit (binaryFn op left right)) (Lit left, right) -> do -- If we have a literal then we don't have to materialise -- the column.- (TColumn value') <- interpret @d df right- fmap TColumn (mapColumn (f left) value')+ (TColumn value') <- interpret df right+ fmap TColumn (mapColumn (binaryFn op left) value') (left, Lit right) -> do -- Same as the above except the right side is the -- literl.- (TColumn value') <- interpret @c df left- fmap TColumn (mapColumn (`f` right) value')+ (TColumn value') <- interpret df left+ fmap TColumn (mapColumn (flip (binaryFn op) right) value') (_, _) -> do -- In the general case we interpret and zip.- (TColumn left') <- interpret @c df left- (TColumn right') <- interpret @d df right- fmap TColumn (zipWithColumns f left' right')+ (TColumn left') <- interpret df left+ (TColumn right') <- interpret df right+ fmap TColumn (zipWithColumns (binaryFn op) left' right') -- Conditionals interpret df expr@(If cond l r) = first (handleInterpretException (show expr)) $ do- (TColumn conditions) <- interpret @Bool df cond- (TColumn left) <- interpret @a df l- (TColumn right) <- interpret @a df r+ (TColumn conditions) <- interpret df cond+ (TColumn left) <- interpret df l+ (TColumn right) <- interpret df r let branch (c :: Bool) (l' :: a, r' :: a) = if c then l' else r' fmap TColumn (zipWithColumns branch conditions (zipColumns left right))-interpret df expression@(AggVector expr op (f :: v b -> c)) = do- (TColumn column) <- interpret @b df expr+interpret df expression@(Agg (CollectAgg op (f :: v b -> c)) expr) = do+ (TColumn column) <- interpret df expr -- Helper for errors. Should probably find a way of throwing this -- without leaking the fact that we use `Vector` to users. let aggTypeError expected =@@ -93,30 +92,17 @@ (BoxedColumn col) -> processColumn col (OptionalColumn col) -> processColumn col (UnboxedColumn col) -> processColumn col-interpret df expression@(AggReduce expr op (f :: a -> a -> a)) = first (handleInterpretException (show expr)) $ do- (TColumn column) <- interpret @a df expr- value <- foldl1Column f column- pure $ TColumn $ fromVector $ V.replicate (fst $ dataframeDimensions df) value-interpret df expression@(AggNumericVector expr op (f :: VU.Vector b -> c)) = first (handleInterpretException (show expression)) $ do- (TColumn column) <- interpret @b df expr- case column of- (UnboxedColumn (v :: VU.Vector d)) -> case testEquality (typeRep @d) (typeRep @b) of- Just Refl ->- pure $ TColumn $ fromVector $ V.replicate (fst $ dataframeDimensions df) (f v)- Nothing ->- Left $- TypeMismatchException- ( MkTypeErrorContext- (Right (typeRep @b))- (Right (typeRep @d))- (Just "interpret")- (Just (show expression))- )- _ -> error "Trying to apply numeric computation to non-numeric column"-interpret df expression@(AggFold expr op start (f :: (a -> b -> a))) = first (handleInterpretException (show expression)) $ do- (TColumn column) <- interpret @b df expr- value <- foldlColumn f start column+interpret df expression@(Agg (FoldAgg op (Just v) f) expr) = first (handleInterpretException (show expr)) $ do+ (TColumn column) <- interpret df expr+ value <- foldlColumn f v column pure $ TColumn $ fromVector $ V.replicate (fst $ dataframeDimensions df) value+interpret df expression@(Agg (FoldAgg op Nothing (f :: a -> b -> a)) expr) = first (handleInterpretException (show expr)) $ do+ (TColumn column) <- interpret df expr+ case testEquality (typeRep @a) (typeRep @b) of+ Just Refl -> do+ value <- foldl1Column f column+ pure $ TColumn $ fromVector $ V.replicate (fst $ dataframeDimensions df) value+ Nothing -> error "Type error" data AggregationResult a = UnAggregated Column@@ -138,8 +124,8 @@ Just (OptionalColumn col) -> Right $ UnAggregated $ fromVector $ mkUnaggregatedColumnBoxed col os indices Just (UnboxedColumn col) -> Right $ UnAggregated $ fromVector $ mkUnaggregatedColumnUnboxed col os indices-interpretAggregation gdf expression@(UnaryOp _ (f :: c -> d) expr) =- case interpretAggregation @c gdf expr of+interpretAggregation gdf expression@(Unary (op :: UnaryOp b a) expr) =+ case interpretAggregation @b gdf expr of Left (TypeMismatchException context) -> Left $ TypeMismatchException@@ -150,105 +136,95 @@ ) Left e -> Left e Right (UnAggregated unaggregated) -> case unaggregated of- BoxedColumn (col :: V.Vector b) -> case testEquality (typeRep @b) (typeRep @(V.Vector c)) of- Just Refl -> case sUnbox @d of- SFalse -> Right $ UnAggregated $ fromVector $ V.map (V.map f) col+ BoxedColumn (col :: V.Vector c) -> case testEquality (typeRep @c) (typeRep @(V.Vector b)) of+ Just Refl -> case sUnbox @a of+ SFalse -> Right $ UnAggregated $ fromVector $ V.map (V.map (unaryFn op)) col STrue -> Right $ UnAggregated $ fromVector $- V.map (V.convert @V.Vector @d @VU.Vector . V.map f) col- Nothing -> case testEquality (typeRep @b) (typeRep @(VU.Vector c)) of- Nothing -> Left $ nestedTypeException @b @c (show expression)- Just Refl -> case (sUnbox @c, sUnbox @a) of+ V.map (V.convert @V.Vector @a @VU.Vector . V.map (unaryFn op)) col+ Nothing -> case testEquality (typeRep @c) (typeRep @(VU.Vector b)) of+ Nothing -> Left $ nestedTypeException @c @a (show expression)+ Just Refl -> case (sUnbox @b, sUnbox @a) of (SFalse, _) -> Left $ InternalException "Boxed type inside an unboxed column"- (STrue, STrue) -> Right $ UnAggregated $ fromVector $ V.map (VU.map f) col- (STrue, _) -> Right $ UnAggregated $ fromVector $ V.map (V.map f . VU.convert) col+ (STrue, STrue) -> Right $ UnAggregated $ fromVector $ V.map (VU.map (unaryFn op)) col+ (STrue, _) ->+ Right $ UnAggregated $ fromVector $ V.map (V.map (unaryFn op) . VU.convert) col _ -> Left $ InternalException "Aggregated into a non-boxed column"- Right (Aggregated (TColumn aggregated)) -> case mapColumn f aggregated of+ Right (Aggregated (TColumn aggregated)) -> case mapColumn (unaryFn op) aggregated of Left e -> Left e Right col -> Right $ Aggregated $ TColumn col-interpretAggregation gdf expression@(BinaryOp name (f :: c -> d -> e) left (Lit (right :: g))) = case testEquality (typeRep @g) (typeRep @d) of- Just Refl -> interpretAggregation gdf (UnaryOp name (`f` right) left)- Nothing ->- Left $- TypeMismatchException- ( MkTypeErrorContext- { userType = Right (typeRep @g)- , expectedType = Right (typeRep @d)- , callingFunctionName = Just "interpretAggregation"- , errorColumnName = Just (show expression)- }- )-interpretAggregation gdf expression@(BinaryOp name (f :: c -> d -> e) (Lit (left :: g)) right) = case testEquality (typeRep @g) (typeRep @c) of- Just Refl -> interpretAggregation gdf (UnaryOp name (f left) right)- Nothing ->- Left $- TypeMismatchException- ( MkTypeErrorContext- { userType = Right (typeRep @g)- , expectedType = Right (typeRep @c)- , callingFunctionName = Just "interpretAggregation"- , errorColumnName = Just (show expression)- }- )-interpretAggregation gdf expression@(BinaryOp _ (f :: c -> d -> e) left right) =- case (interpretAggregation @c gdf left, interpretAggregation @d gdf right) of- (Right (Aggregated (TColumn left')), Right (Aggregated (TColumn right'))) -> case zipWithColumns f left' right' of+interpretAggregation gdf expression@(Binary (op :: t c b a) left (Lit (right :: b))) =+ interpretAggregation+ gdf+ (Unary (MkUnaryOp (flip (binaryFn op) right) "udf" Nothing) left)+interpretAggregation gdf expression@(Binary (op :: BinaryOp c b a) (Lit (left :: c)) right) =+ interpretAggregation+ gdf+ (Unary (MkUnaryOp (binaryFn op left) "udf" Nothing) right)+interpretAggregation gdf expression@(Binary (op :: BinaryOp c b a) left right) =+ case (interpretAggregation gdf left, interpretAggregation gdf right) of+ (Right (Aggregated (TColumn left')), Right (Aggregated (TColumn right'))) -> case zipWithColumns (binaryFn op) left' right' of Left e -> Left e Right col -> Right $ Aggregated $ TColumn col (Right (UnAggregated left'), Right (UnAggregated right')) -> case (left', right') of (BoxedColumn (l :: V.Vector m), BoxedColumn (r :: V.Vector n)) -> case testEquality (typeRep @m) (typeRep @(VU.Vector c)) of- Just Refl -> case testEquality (typeRep @n) (typeRep @(VU.Vector d)) of- Just Refl -> case (sUnbox @c, sUnbox @d, sUnbox @e) of+ Just Refl -> case testEquality (typeRep @n) (typeRep @(VU.Vector b)) of+ Just Refl -> case (sUnbox @c, sUnbox @b, sUnbox @a) of (STrue, STrue, STrue) ->- Right $ UnAggregated $ fromVector $ V.zipWith (VU.zipWith f) l r+ Right $ UnAggregated $ fromVector $ V.zipWith (VU.zipWith (binaryFn op)) l r (STrue, STrue, SFalse) -> Right $ UnAggregated $ fromVector $- V.zipWith (\l' r' -> V.zipWith f (V.convert l') (V.convert r')) l r+ V.zipWith (\l' r' -> V.zipWith (binaryFn op) (V.convert l') (V.convert r')) l r (_, _, _) -> Left $ InternalException "Boxed vectors contain unboxed types"- Nothing -> case testEquality (typeRep @n) (typeRep @(V.Vector d)) of+ Nothing -> case testEquality (typeRep @n) (typeRep @(V.Vector b)) of Just Refl -> case sUnbox @c of STrue -> Right $ UnAggregated $ fromVector $- V.zipWith (V.zipWith f . V.convert) l r+ V.zipWith (V.zipWith (binaryFn op) . V.convert) l r SFalse -> Left $ InternalException "Unboxed vectors contain boxed types"- Nothing -> Left $ nestedTypeException @n @d (show right)+ Nothing -> Left $ nestedTypeException @n @b (show right) Nothing -> case testEquality (typeRep @m) (typeRep @(V.Vector c)) of Nothing -> Left $ nestedTypeException @m @c (show left)- Just Refl -> case testEquality (typeRep @n) (typeRep @(VU.Vector d)) of- Just Refl -> case (sUnbox @d, sUnbox @e) of+ Just Refl -> case testEquality (typeRep @n) (typeRep @(VU.Vector b)) of+ Just Refl -> case (sUnbox @b, sUnbox @a) of (STrue, STrue) -> Right $ UnAggregated $ fromVector $ V.zipWith- (\l' r' -> V.convert @V.Vector @e @VU.Vector $ V.zipWith f l' (V.convert r'))+ ( \l' r' ->+ V.convert @V.Vector @a @VU.Vector $ V.zipWith (binaryFn op) l' (V.convert r')+ ) l r (STrue, SFalse) -> Right $ UnAggregated $ fromVector $- V.zipWith (\l' r' -> V.zipWith f l' (V.convert r')) l r+ V.zipWith (\l' r' -> V.zipWith (binaryFn op) l' (V.convert r')) l r (_, _) -> Left $ InternalException "Unboxed vectors contain boxed types"- Nothing -> case testEquality (typeRep @n) (typeRep @(V.Vector d)) of- Just Refl -> case sUnbox @e of+ Nothing -> case testEquality (typeRep @n) (typeRep @(V.Vector b)) of+ Just Refl -> case sUnbox @a of SFalse -> Right $ UnAggregated $ fromVector $- V.zipWith (V.zipWith f . V.convert) l r+ V.zipWith (V.zipWith (binaryFn op) . V.convert) l r STrue -> Right $ UnAggregated $ fromVector $- V.zipWith (\l' r' -> V.convert @V.Vector @e @VU.Vector $ V.zipWith f l' r') l r- Nothing -> Left $ nestedTypeException @n @d (show right)+ V.zipWith+ (\l' r' -> V.convert @V.Vector @a @VU.Vector $ V.zipWith (binaryFn op) l' r')+ l+ r+ Nothing -> Left $ nestedTypeException @n @b (show right) _ -> Left $ InternalException "Aggregated into a non-boxed column" (Right _, Right _) -> Left $@@ -571,7 +547,7 @@ } ) (_, _, Left e) -> Left e-interpretAggregation gdf@(Grouped df names indices os) expression@(AggVector expr op (f :: v b -> c)) =+interpretAggregation gdf@(Grouped df names indices os) expression@(Agg (CollectAgg op (f :: v b -> c)) expr) = case interpretAggregation @b gdf expr of Right (UnAggregated (BoxedColumn (col :: V.Vector d))) -> case testEquality (typeRep @(v b)) (typeRep @d) of Nothing -> Left $ nestedTypeException @d @b (show expr)@@ -630,93 +606,9 @@ } ) (Left e) -> Left e-interpretAggregation gdf@(Grouped df names indices os) expression@(AggNumericVector (Col name) op (f :: VU.Vector b -> c)) =- case getColumn name df of- -- TODO(mchavinda): Fix the compedium of type errors here- -- This is mostly done help with the benchmarking.- Nothing -> Left $ ColumnNotFoundException name "" (M.keys $ columnIndices df)- Just (BoxedColumn col) -> error "Type mismatch."- Just (OptionalColumn col) -> error "Type mismatch."- Just (UnboxedColumn (col :: VU.Vector d)) -> case testEquality (typeRep @b) (typeRep @d) of- Just Refl -> case testEquality (typeRep @c) (typeRep @a) of- Just Refl ->- Right $- Aggregated $- TColumn $- fromUnboxedVector $- mkAggregatedColumnUnboxed col os indices f- Nothing -> error "Type mismatch"- Nothing -> error "Type mismatch"-interpretAggregation gdf@(Grouped df names indices os) expression@(AggNumericVector expr op (f :: VU.Vector b -> c)) =- case interpretAggregation @b gdf expr of- (Left (TypeMismatchException context)) ->- Left $- TypeMismatchException- ( context- { callingFunctionName = Just "interpretAggregation"- , errorColumnName = Just (show expression)- }- )- (Left e) -> Left e- Right (UnAggregated (BoxedColumn (col :: V.Vector d))) -> case testEquality (typeRep @(VU.Vector b)) (typeRep @d) of- Nothing -> case testEquality (typeRep @(VU.Vector Int)) (typeRep @d) of- Nothing -> case testEquality (typeRep @(V.Vector Integer)) (typeRep @d) of- Nothing -> Left $ nestedTypeException @d @b (show expr)- Just Refl ->- Right $- Aggregated $- TColumn $- fromVector $- V.map (f . VU.convert . V.map fromIntegral) col- Just Refl ->- Right $- Aggregated $- TColumn $- fromVector $- V.map f (V.map (VU.map fromIntegral) col)- Just Refl -> Right $ Aggregated $ TColumn $ fromVector $ V.map f col- Right (UnAggregated _) -> Left $ InternalException "Aggregated into non-boxed column"- Right (Aggregated (TColumn (BoxedColumn (col :: V.Vector d)))) -> case testEquality (typeRep @Integer) (typeRep @d) of- Just Refl -> interpretAggregation @c gdf (Lit ((f . V.convert . V.map fromIntegral) col))- Nothing ->- Left $- TypeMismatchException- ( MkTypeErrorContext- { userType = Right (typeRep @b)- , expectedType = Right (typeRep @d)- , callingFunctionName = Just "interpretAggregation"- , errorColumnName = Just (show expr)- }- )- Right (Aggregated (TColumn (UnboxedColumn (col :: VU.Vector d)))) -> case testEquality (typeRep @b) (typeRep @d) of- Just Refl -> interpretAggregation @c gdf (Lit (f col))- Nothing ->- Left $- TypeMismatchException- ( MkTypeErrorContext- { userType = Right (typeRep @b)- , expectedType = Right (typeRep @d)- , callingFunctionName = Just "interpretAggregation"- , errorColumnName = Just (show expr)- }- )- Right (Aggregated (TColumn (OptionalColumn (col :: V.Vector (Maybe d))))) -> case testEquality (typeRep @b) (typeRep @d) of- Just Refl ->- interpretAggregation @c- gdf- (Lit ((f . V.convert . V.map (fromMaybe 0) . V.filter isJust) col))- Nothing ->- Left $- TypeMismatchException- ( MkTypeErrorContext- { userType = Right (typeRep @b)- , expectedType = Right (typeRep @d)- , callingFunctionName = Just "interpretAggregation"- , errorColumnName = Just (show expr)- }- )-interpretAggregation gdf@(Grouped df names indices os) expression@(AggReduce (Col name) op (f :: a -> a -> a)) =- case getColumn name df of+interpretAggregation gdf@(Grouped df names indices os) expression@(Agg (FoldAgg op Nothing (f :: a -> b -> a)) (Col name)) = case testEquality (typeRep @a) (typeRep @b) of+ Nothing -> error "Type mismatch"+ Just Refl -> case getColumn name df of Nothing -> Left $ ColumnNotFoundException name "" (M.keys $ columnIndices df) Just (BoxedColumn (col :: V.Vector d)) -> case testEquality (typeRep @a) (typeRep @d) of Nothing -> error "Type mismatch"@@ -742,8 +634,9 @@ fromUnboxedVector $ mkReducedColumnUnboxed col os indices f Nothing -> error "Type mismatch"-interpretAggregation gdf@(Grouped df names indices os) expression@(AggReduce expr op (f :: a -> a -> a)) =- case interpretAggregation @a gdf expr of+interpretAggregation gdf@(Grouped df names indices os) expression@(Agg (FoldAgg op Nothing (f :: a -> b -> a)) expr) = case testEquality (typeRep @a) (typeRep @b) of+ Nothing -> error "Type mismatch"+ Just Refl -> case interpretAggregation @a gdf expr of (Left (TypeMismatchException context)) -> Left $ TypeMismatchException@@ -774,8 +667,8 @@ Right (Aggregated (TColumn column)) -> case foldl1Column f column of Left e -> Left e Right value -> interpretAggregation @a gdf (Lit value)-interpretAggregation gdf@(Grouped df names indices os) expression@(AggFold expr op s (f :: (a -> b -> a))) =- case interpretAggregation @b gdf expr of+interpretAggregation gdf@(Grouped df names indices os) expression@(Agg (FoldAgg op (Just s) (f :: a -> b -> a)) expr) =+ case interpretAggregation gdf expr of (Left (TypeMismatchException context)) -> Left $ TypeMismatchException
src/DataFrame/Operations/Aggregation.hs view
@@ -53,6 +53,12 @@ (T.pack $ show $ names L.\\ columnNames df) "groupBy" (columnNames df)+ | nRows df == 0 =+ Grouped+ df+ names+ VU.empty+ (VU.fromList [0]) | otherwise = Grouped df@@ -261,7 +267,7 @@ (VU.map (valueIndices VU.!) (VU.init offsets)) (select groupingColumns df) - f (name, Wrap (expr :: Expr a)) d =+ f (name, UExpr (expr :: Expr a)) d = let value = case interpretAggregation @a gdf expr of Left e -> throw e
src/DataFrame/Operations/Core.hs view
@@ -680,7 +680,8 @@ @ -}-valueCounts :: forall a. (Columnable a) => Expr a -> DataFrame -> [(a, Int)]+valueCounts ::+ forall a. (Ord a, Columnable a) => Expr a -> DataFrame -> [(a, Int)] valueCounts expr df = case columnAsVector expr df of Left e -> throw e Right column' ->@@ -702,7 +703,7 @@ @ -} valueProportions ::- forall a. (Columnable a) => Expr a -> DataFrame -> [(a, Double)]+ forall a. (Ord a, Columnable a) => Expr a -> DataFrame -> [(a, Double)] valueProportions expr df = case columnAsVector expr df of Left e -> throw e Right column' ->
src/DataFrame/Operations/Join.hs view
@@ -137,7 +137,14 @@ then df else ( if name `elem` leftColumns- then insertIfPresent ("Right_" <> name) (D.getColumn name expandedRight) df+ then+ insertIfPresent+ name+ ( D.mergeColumns+ <$> D.getColumn name expandedLeft+ <*> D.getColumn name expandedRight+ )+ df else insertIfPresent name (D.getColumn name expandedRight) df ) )@@ -215,7 +222,14 @@ then df else ( if name `elem` leftColumns- then insertIfPresent ("Right_" <> name) (D.getColumn name expandedRight) df+ then+ insertIfPresent+ name+ ( D.mergeColumns+ <$> D.getColumn name expandedLeft+ <*> D.getColumn name expandedRight+ )+ df else insertIfPresent name (D.getColumn name expandedRight) df ) )@@ -322,7 +336,14 @@ _ -> error "Join should have optional keys." else ( if name `elem` leftColumns- then insertIfPresent ("Right_" <> name) (D.getColumn name expandedRight) df+ then+ insertIfPresent+ name+ ( D.mergeColumns+ <$> D.getColumn name expandedLeft+ <*> D.getColumn name expandedRight+ )+ df else insertIfPresent name (D.getColumn name expandedRight) df ) ) -- ???
src/DataFrame/Operations/Permutation.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-} module DataFrame.Operations.Permutation where@@ -11,19 +14,27 @@ import DataFrame.Errors (DataFrameException (..)) import DataFrame.Internal.Column import DataFrame.Internal.DataFrame (DataFrame (..))+import DataFrame.Internal.Expression import DataFrame.Internal.Row import DataFrame.Operations.Core import System.Random+import System.Random.Shuffle (shuffle') -- | Sort order taken as a parameter by the 'sortBy' function.-data SortOrder- = Asc T.Text- | Desc T.Text- deriving (Eq)+data SortOrder where+ Asc :: (Columnable a) => Expr a -> SortOrder+ Desc :: (Columnable a) => Expr a -> SortOrder +instance Eq SortOrder where+ (==) :: SortOrder -> SortOrder -> Bool+ (==) (Asc _) (Asc _) = True+ (==) (Desc _) (Desc _) = True+ (==) _ _ = False+ getSortColumnName :: SortOrder -> T.Text-getSortColumnName (Asc n) = n-getSortColumnName (Desc n) = n+getSortColumnName (Asc (Col n)) = n+getSortColumnName (Desc (Col n)) = n+getSortColumnName _ = error "Sorting on compound column" mustFlipCompare :: SortOrder -> Bool mustFlipCompare (Asc _) = True@@ -65,4 +76,4 @@ df{columns = V.map (atIndicesStable indexes) (columns df)} shuffledIndices :: (RandomGen g) => g -> Int -> VU.Vector Int-shuffledIndices pureGen k = VU.fromList (fst (uniformShuffleList [0 .. (k - 1)] pureGen))+shuffledIndices pureGen k = VU.fromList (shuffle' [0 .. (k - 1)] k pureGen)
src/DataFrame/Operations/Statistics.hs view
@@ -57,33 +57,28 @@ Percentage (%) | 44.26% | 31.74% | 0.02% | 11.09% | 12.88% @ -}-frequencies :: T.Text -> DataFrame -> DataFrame-frequencies name df =+frequencies :: forall a. (Columnable a) => Expr a -> DataFrame -> DataFrame+frequencies expr df = let- counts :: forall a. (Columnable a) => [(a, Int)]- counts = valueCounts (Col @a name) df+ counts = valueCounts expr df calculatePercentage cs k = toAny $ toPct2dp (fromIntegral k / fromIntegral (P.sum $ map snd cs)) initDf = empty & insertVector "Statistic" (V.fromList ["Count" :: T.Text, "Percentage (%)"])- freqs :: forall v a. (VG.Vector v a, Columnable a) => v a -> DataFrame freqs col = L.foldl' ( \d (col, k) -> insertVector (showValue @a col)- (V.fromList [toAny k, calculatePercentage (counts @a) k])+ (V.fromList [toAny k, calculatePercentage counts k]) d ) initDf counts in- case getColumn name df of- Nothing ->- throw $ ColumnNotFoundException name "frequencies" (M.keys $ columnIndices df)- Just ((BoxedColumn (column :: V.Vector a))) -> freqs column- Just ((OptionalColumn (column :: V.Vector a))) -> freqs column- Just ((UnboxedColumn (column :: VU.Vector a))) -> freqs column+ case columnAsVector expr df of+ Left err -> throw err+ Right column -> freqs column -- | Calculates the mean of a given column as a standalone value. mean ::
src/DataFrame/Operations/Subset.hs view
@@ -10,6 +10,7 @@ import qualified Data.List as L import qualified Data.Map as M+import qualified Data.Set as S import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.Vector.Generic as VG@@ -295,9 +296,10 @@ -- | O(n) select columns by column predicate name. selectBy :: [SelectionCriteria] -> DataFrame -> DataFrame-selectBy xs df = select columnsWithProperties df+selectBy xs df = select finalSelection df where- columnsWithProperties = L.foldl' columnWithProperty [] xs+ finalSelection = Prelude.filter (`S.member` columnsWithProperties) (columnNames df)+ columnsWithProperties = S.fromList (L.foldl' columnWithProperty [] xs) columnWithProperty acc (ColumnName name) = acc ++ [name] columnWithProperty acc (ColumnNameProperty f) = acc ++ L.filter f (columnNames df) columnWithProperty acc (ColumnTextRange (from, to)) =@@ -339,7 +341,19 @@ in df & insertUnboxedVector "__rand__" rand- & filterWhere (BinaryOp "geq" (>=) (Col @Double "__rand__") (Lit (1 - p)))+ & filterWhere+ ( Binary+ ( MkBinaryOp+ { binaryFn = (>=)+ , binaryName = "geq"+ , binarySymbol = Just ">="+ , binaryCommutative = False+ , binaryPrecedence = 1+ }+ )+ (Col @Double "__rand__")+ (Lit (1 - p))+ ) & exclude ["__rand__"] {- | Split a dataset into two. The first in the tuple gets a sample of p (0 <= p <= 1) and the second gets (1 - p). This is useful for creating test and train splits.@@ -359,10 +373,34 @@ withRand = df & insertUnboxedVector "__rand__" rand in ( withRand- & filterWhere (BinaryOp "leq" (<=) (Col @Double "__rand__") (Lit p))+ & filterWhere+ ( Binary+ ( MkBinaryOp+ { binaryFn = (<=)+ , binaryName = "leq"+ , binarySymbol = Just "<="+ , binaryCommutative = False+ , binaryPrecedence = 1+ }+ )+ (Col @Double "__rand__")+ (Lit p)+ ) & exclude ["__rand__"] , withRand- & filterWhere (BinaryOp "gt" (>) (Col @Double "__rand__") (Lit p))+ & filterWhere+ ( Binary+ ( MkBinaryOp+ { binaryFn = (>)+ , binaryName = "gt"+ , binarySymbol = Just ">"+ , binaryCommutative = False+ , binaryPrecedence = 1+ }+ )+ (Col @Double "__rand__")+ (Lit p)+ ) & exclude ["__rand__"] ) @@ -384,9 +422,15 @@ singleFold n d = d & filterWhere- ( BinaryOp- "geq"- (>=)+ ( Binary+ ( MkBinaryOp+ { binaryFn = (>=)+ , binaryName = "geq"+ , binarySymbol = Just ">="+ , binaryCommutative = False+ , binaryPrecedence = 1+ }+ ) (Col @Double "__rand__") (Lit (fromIntegral n * partitionSize)) )@@ -397,9 +441,15 @@ d'' = d & filterWhere- ( BinaryOp- "lt"- (<)+ ( Binary+ ( MkBinaryOp+ { binaryFn = (<)+ , binaryName = "lt"+ , binarySymbol = Just "<"+ , binaryCommutative = False+ , binaryPrecedence = 1+ }+ ) (Col @Double "__rand__") (Lit (fromIntegral n * partitionSize)) )
src/DataFrame/Operations/Transformations.hs view
@@ -71,7 +71,7 @@ Left e -> throw e Right (TColumn value) -> (insertColumn name value df)- { derivingExpressions = M.insert name (Wrap expr) (derivingExpressions df)+ { derivingExpressions = M.insert name (UExpr expr) (derivingExpressions df) } {- | O(k) Apply a function to an expression in a dataframe and@@ -91,7 +91,7 @@ deriveMany :: [NamedExpr] -> DataFrame -> DataFrame deriveMany exprs df = let- f (name, Wrap (expr :: Expr a)) d =+ f (name, UExpr (expr :: Expr a)) d = case interpret @a df expr of Left e -> throw e Right (TColumn value) -> insertColumn name value d
+ src/DataFrame/Operators.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}++module DataFrame.Operators where++import Data.Function ((&))+import qualified Data.Text as T+import DataFrame.Internal.Column (Columnable)+import DataFrame.Internal.Expression (+ BinaryOp (+ MkBinaryOp,+ binaryCommutative,+ binaryFn,+ binaryName,+ binaryPrecedence,+ binarySymbol+ ),+ Expr (Binary, Col, If, Lit),+ NamedExpr,+ UExpr (UExpr),+ )++infix 8 .^^+infix 4 .==, .<, .<=, .>=, .>, ./=+infixr 3 .&&+infixr 2 .||+infixr 0 .=++(|>) :: a -> (a -> b) -> b+(|>) = (&)++as :: (Columnable a) => Expr a -> T.Text -> NamedExpr+as expr name = (name, UExpr expr)++name :: (Show a) => Expr a -> T.Text+name (Col n) = n+name other =+ error $+ "You must call `name` on a column reference. Not the expression: " ++ show other++col :: (Columnable a) => T.Text -> Expr a+col = Col++ifThenElse :: (Columnable a) => Expr Bool -> Expr a -> Expr a -> Expr a+ifThenElse = If++lit :: (Columnable a) => a -> Expr a+lit = Lit++(.=) :: (Columnable a) => T.Text -> Expr a -> NamedExpr+(.=) = flip as++(.==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool+(.==) =+ Binary+ ( MkBinaryOp+ { binaryFn = (==)+ , binaryName = "eq"+ , binarySymbol = Just "=="+ , binaryCommutative = True+ , binaryPrecedence = 4+ }+ )++(./=) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool+(./=) =+ Binary+ ( MkBinaryOp+ { binaryFn = (/=)+ , binaryName = "neq"+ , binarySymbol = Just "/="+ , binaryCommutative = True+ , binaryPrecedence = 4+ }+ )++(.<) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool+(.<) =+ Binary+ ( MkBinaryOp+ { binaryFn = (<)+ , binaryName = "lt"+ , binarySymbol = Just "<"+ , binaryCommutative = False+ , binaryPrecedence = 4+ }+ )++(.>) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool+(.>) =+ Binary+ ( MkBinaryOp+ { binaryFn = (>)+ , binaryName = "gt"+ , binarySymbol = Just ">"+ , binaryCommutative = False+ , binaryPrecedence = 4+ }+ )++(.<=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool+(.<=) =+ Binary+ ( MkBinaryOp+ { binaryFn = (<=)+ , binaryName = "leq"+ , binarySymbol = Just "<="+ , binaryCommutative = False+ , binaryPrecedence = 4+ }+ )++(.>=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool+(.>=) =+ Binary+ ( MkBinaryOp+ { binaryFn = (>=)+ , binaryName = "geq"+ , binarySymbol = Just ">="+ , binaryCommutative = False+ , binaryPrecedence = 4+ }+ )++(.&&) :: Expr Bool -> Expr Bool -> Expr Bool+(.&&) =+ Binary+ ( MkBinaryOp+ { binaryFn = (&&)+ , binaryName = "and"+ , binarySymbol = Just "&&"+ , binaryCommutative = True+ , binaryPrecedence = 3+ }+ )++(.||) :: Expr Bool -> Expr Bool -> Expr Bool+(.||) =+ Binary+ ( MkBinaryOp+ { binaryFn = (||)+ , binaryName = "or"+ , binarySymbol = Just "||"+ , binaryCommutative = True+ , binaryPrecedence = 2+ }+ )++(.^^) :: (Columnable a, Num a) => Expr a -> Int -> Expr a+(.^^) expr i =+ Binary+ ( MkBinaryOp+ { binaryFn = (^)+ , binaryName = "pow"+ , binarySymbol = Just "^"+ , binaryCommutative = False+ , binaryPrecedence = 8+ }+ )+ expr+ (Lit i)
src/DataFrame/Synthesis.hs view
@@ -37,8 +37,8 @@ import qualified Data.Text as T import Data.Type.Equality import qualified Data.Vector.Unboxed as VU-import DataFrame.Functions ((.&&), (.<=), (.>), (.||)) import qualified DataFrame.Operations.Core as D+import DataFrame.Operators import Debug.Trace (trace) import Type.Reflection (typeRep)
tests/GenDataFrame.hs view
@@ -21,8 +21,8 @@ genDataFrame :: Gen DataFrame genDataFrame = do- numRows <- choose (0, 100)- numCols <- choose (0, 10)+ numRows <- choose (100, 1000)+ numCols <- choose (1, 10) colNames <- V.fromList <$> vectorOf numCols genUniqueColName cols <- V.fromList <$> vectorOf numCols (genColumn numRows) let indices = M.fromList $ zip (V.toList colNames) [0 ..]
tests/Main.hs view
@@ -18,6 +18,7 @@ import Test.QuickCheck import qualified Functions+import qualified Monad import qualified Operations.Aggregations import qualified Operations.Apply import qualified Operations.Core@@ -28,6 +29,7 @@ import qualified Operations.Join import qualified Operations.Merge import qualified Operations.ReadCsv+import qualified Operations.Shuffle import qualified Operations.Sort import qualified Operations.Statistics import qualified Operations.Subset@@ -5119,6 +5121,7 @@ ++ Operations.Join.tests ++ Operations.Merge.tests ++ Operations.ReadCsv.tests+ ++ Operations.Shuffle.tests ++ Operations.Sort.tests ++ Operations.Statistics.tests ++ Operations.Take.tests@@ -5138,6 +5141,10 @@ mapM (quickCheckWithResult stdArgs) Operations.Subset.tests- if failures result > 0 || errors result > 0 || not (all isSuccessful propRes)+ monadRes <- mapM (quickCheckWithResult stdArgs) Monad.tests+ if failures result > 0+ || errors result > 0+ || not (all isSuccessful propRes)+ || not (all isSuccessful monadRes) then Exit.exitFailure else Exit.exitSuccess
+ tests/Monad.hs view
@@ -0,0 +1,27 @@+module Monad where++import qualified DataFrame as D+import DataFrame.Internal.DataFrame+import DataFrame.Monad+import GenDataFrame ()+import System.Random+import Test.QuickCheck+import Test.QuickCheck.Monadic++roundToTwoPlaces x = fromIntegral (round (x * 100)) / 100.0++prop_sampleM :: DataFrame -> Gen (Gen Property)+prop_sampleM df = monadic' $ do+ p <- run $ choose (0.0 :: Double, 1.0 :: Double)+ let expectedRate = roundToTwoPlaces p+ seed <- run $ choose (0, 1000)+ let rowCount = D.nRows df+ let colCount = D.nColumns df+ pre (colCount > 1 && rowCount > 100)+ let finalDf = execFrameM df (sampleM (mkStdGen seed) expectedRate)+ let finalRowCount = D.nRows finalDf+ let realRate = roundToTwoPlaces $ fromIntegral finalRowCount / fromIntegral rowCount+ let diff = abs $ expectedRate - realRate+ assert (diff <= 0.11)++tests = [prop_sampleM]
tests/Operations/Aggregations.hs view
@@ -9,6 +9,7 @@ import qualified DataFrame.Internal.Column as DI import Data.Function+import DataFrame.Operators import Test.HUnit values :: [(T.Text, DI.Column)]@@ -36,8 +37,8 @@ ) ( testData & D.groupBy ["test1"]- & D.aggregate [F.count (F.col @Int "test2") `F.as` "test2"]- & D.sortBy [D.Asc "test1"]+ & D.aggregate [F.count (F.col @Int "test2") `as` "test2"]+ & D.sortBy [D.Asc (F.col @Int "test1")] ) ) @@ -53,8 +54,8 @@ ) ( testData & D.groupBy ["test1"]- & D.aggregate [F.mean (F.col @Int "test2") `F.as` "test2"]- & D.sortBy [D.Asc "test1"]+ & D.aggregate [F.mean (F.col @Int "test2") `as` "test2"]+ & D.sortBy [D.Asc (F.col @Int "test1")] ) ) @@ -71,9 +72,9 @@ ( testData & D.groupBy ["test1"] & D.aggregate- [ F.mean (F.lift (fromIntegral @Int @Double) (F.col @Int "test2")) `F.as` "test2"+ [ F.mean (F.lift (fromIntegral @Int @Double) (F.col @Int "test2")) `as` "test2" ]- & D.sortBy [D.Asc "test1"]+ & D.sortBy [D.Asc (F.col @Int "test1")] ) ) @@ -89,8 +90,8 @@ ) ( testData & D.groupBy ["test1"]- & D.aggregate [F.mean (F.col @Int "test2" + F.col @Int "test2") `F.as` "test2"]- & D.sortBy [D.Asc "test1"]+ & D.aggregate [F.mean (F.col @Int "test2" + F.col @Int "test2") `as` "test2"]+ & D.sortBy [D.Asc (F.col @Int "test1")] ) ) @@ -108,9 +109,9 @@ & D.groupBy ["test1"] & D.aggregate [ F.maximum (F.lift (fromIntegral @Int @Double) (F.col @Int "test2"))- `F.as` "test2"+ `as` "test2" ]- & D.sortBy [D.Asc "test1"]+ & D.sortBy [D.Asc (F.col @Int "test1")] ) ) @@ -127,11 +128,29 @@ ( testData & D.groupBy ["test1"] & D.aggregate- [F.maximum (F.col @Int "test2" + F.col @Int "test2") `F.as` "test2"]- & D.sortBy [D.Asc "test1"]+ [F.maximum (F.col @Int "test2" + F.col @Int "test2") `as` "test2"]+ & D.sortBy [D.Asc (F.col @Int "test1")] ) ) +aggregationOnNoRows :: Test+aggregationOnNoRows =+ TestCase+ ( assertEqual+ "Aggregation on DataFrame with no rows"+ ( D.fromNamedColumns+ [ ("test1", DI.fromList ([] :: [Int]))+ , ("sum(test2)", DI.fromList ([] :: [Int]))+ ]+ )+ ( testData+ & D.drop 12+ & D.groupBy ["test1"]+ & D.aggregate+ [F.sum (F.col @Int "test2") `as` "sum(test2)"]+ )+ )+ tests :: [Test] tests = [ TestLabel "foldAggregation" foldAggregation@@ -148,4 +167,7 @@ , TestLabel "reduceAggregationOfUnaggregatedBinaryOp" reduceAggregationOfUnaggregatedBinaryOp+ , TestLabel+ "aggregationOnNoRows"+ aggregationOnNoRows ]
tests/Operations/Join.hs view
@@ -4,7 +4,9 @@ module Operations.Join where import Data.Text (Text)+import Data.These import qualified DataFrame as D+import qualified DataFrame.Functions as F import DataFrame.Operations.Join import Test.HUnit @@ -33,7 +35,7 @@ , ("B", D.fromList ["B0" :: Text, "B1", "B2"]) ] )- (D.sortBy [D.Asc "key"] (innerJoin ["key"] df1 df2))+ (D.sortBy [D.Asc (F.col @Text "key")] (innerJoin ["key"] df1 df2)) ) testLeftJoin :: Test@@ -47,7 +49,7 @@ , ("B", D.fromList [Just "B0", Just "B1" :: Maybe Text, Just "B2"]) ] )- (D.sortBy [D.Asc "key"] (leftJoin ["key"] df2 df1))+ (D.sortBy [D.Asc (F.col @Text "key")] (leftJoin ["key"] df2 df1)) ) testRightJoin :: Test@@ -57,11 +59,11 @@ "Test right join with single key" ( D.fromNamedColumns [ ("key", D.fromList ["K0" :: Text, "K1", "K2"])- , ("A", D.fromList ["A0" :: Text, "A1", "A2"])+ , ("A", D.fromList [Just "A0" :: Maybe Text, Just "A1", Just "A2"]) , ("B", D.fromList ["B0" :: Text, "B1", "B2"]) ] )- (D.sortBy [D.Asc "key"] (rightJoin ["key"] df2 df1))+ (D.sortBy [D.Asc (F.col @Text "key")] (rightJoin ["key"] df2 df1)) ) staffDf :: D.DataFrame@@ -108,13 +110,107 @@ ) ] )- (D.sortBy [D.Asc "Name"] (fullOuterJoin ["Name"] studentDf staffDf))+ (D.sortBy [D.Asc (F.col @Text "Name")] (fullOuterJoin ["Name"] studentDf staffDf)) ) +dfL :: D.DataFrame+dfL =+ D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2"])+ , ("X", D.fromList ["LX0" :: Text, "LX1", "LX2"])+ , ("Lonly", D.fromList ["L0" :: Text, "L1", "L2"])+ ]++dfR :: D.DataFrame+dfR =+ D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K3"])+ , ("X", D.fromList ["RX0" :: Text, "RX1", "RX3"])+ , ("Ronly", D.fromList [10 :: Int, 11, 13])+ ]++testInnerJoinWithCollisions :: Test+testInnerJoinWithCollisions =+ TestCase+ ( assertEqual+ "Test inner join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1"])+ , ("X", D.fromList [These "LX0" "RX0" :: These Text Text, These "LX1" "RX1"])+ , ("Lonly", D.fromList ["L0" :: Text, "L1"])+ , ("Ronly", D.fromList [10 :: Int, 11])+ ]+ )+ (D.sortBy [D.Asc (F.col @Text "key")] (innerJoin ["key"] dfR dfL))+ )++testLeftJoinWithCollisions :: Test+testLeftJoinWithCollisions =+ TestCase+ ( assertEqual+ "Test left join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2"])+ ,+ ( "X"+ , D.fromList [These "LX0" "RX0" :: These Text Text, These "LX1" "RX1", This "LX2"]+ )+ , ("Lonly", D.fromList ["L0" :: Text, "L1", "L2"])+ , ("Ronly", D.fromList [Just 10 :: Maybe Int, Just 11, Nothing])+ ]+ )+ (D.sortBy [D.Asc (F.col @Text "key")] (leftJoin ["key"] dfR dfL))+ )++testRightJoinWithCollisions :: Test+testRightJoinWithCollisions =+ TestCase+ ( assertEqual+ "Test right join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K3"])+ ,+ ( "X"+ , D.fromList [These "RX0" "LX0" :: These Text Text, These "RX1" "LX1", This "RX3"]+ )+ , ("Ronly", D.fromList [10 :: Int, 11, 13])+ , ("Lonly", D.fromList [Just "L0" :: Maybe Text, Just "L1", Nothing])+ ]+ )+ (D.sortBy [D.Asc (F.col @Text "key")] (rightJoin ["key"] dfR dfL))+ )++testOuterJoinWithCollisions :: Test+testOuterJoinWithCollisions =+ TestCase+ ( assertEqual+ "Test right join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2", "K3"])+ ,+ ( "X"+ , D.fromList+ [ Just (These "LX0" "RX0") :: Maybe (These Text Text)+ , Just (These "LX1" "RX1")+ , Just (This "LX2")+ , Just (That "RX3")+ ]+ )+ , ("Lonly", D.fromList [Just "L0" :: Maybe Text, Just "L1", Just "L2", Nothing])+ , ("Ronly", D.fromList [Just 10 :: Maybe Int, Just 11, Nothing, Just 13])+ ]+ )+ (D.sortBy [D.Asc (F.col @Text "key")] (fullOuterJoin ["key"] dfR dfL))+ )+ tests :: [Test] tests = [ TestLabel "innerJoin" testInnerJoin , TestLabel "leftJoin" testLeftJoin , TestLabel "rightJoin" testRightJoin , TestLabel "fullOuterJoin" testFullOuterJoin+ , TestLabel "innerJoinWithCollisions" testInnerJoinWithCollisions+ , TestLabel "leftJoinWithCollisions" testLeftJoinWithCollisions+ , TestLabel "rightJoinWithCollisions" testRightJoinWithCollisions+ , TestLabel "outerJoinWithCollisions" testOuterJoinWithCollisions ]
+ tests/Operations/Shuffle.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Operations.Shuffle where++import qualified DataFrame as D++import DataFrame.Operations.Permutation (shuffle)+import System.Random (mkStdGen)+import Test.HUnit (Test (..), assertEqual)++testDataFrame :: D.DataFrame+testDataFrame =+ D.fromNamedColumns+ [ ("numbers", D.fromList @Int [1 .. 26])+ ]++-- Test that shuffling does anything at all+shuffleShuffles :: Test+shuffleShuffles =+ let gen = mkStdGen 1234+ shuffled = shuffle gen testDataFrame+ initialNumbers = D.extractNumericColumn "numbers" testDataFrame+ shuffledNumbers = D.extractNumericColumn "numbers" shuffled+ in TestCase+ ( assertEqual+ "Shuffled column unequal to initial column"+ False+ (initialNumbers == shuffledNumbers)+ )++shufflePreservesColumnNames :: Test+shufflePreservesColumnNames =+ let gen = mkStdGen 837+ shuffled = shuffle gen testDataFrame+ in TestCase+ ( assertEqual+ "Column names are unchanged"+ (D.columnNames shuffled)+ (D.columnNames testDataFrame)+ )++-- Test that un-shuffling restores the original dataframe+-- which is known to be sorted in this case+shufflePreservesData :: Test+shufflePreservesData =+ let gen = mkStdGen 1234+ shuffled = shuffle gen testDataFrame+ sortedShuffled = D.sortBy [D.Asc (D.col @Int "numbers")] shuffled+ in TestCase+ (assertEqual "sort recovers initial numbers" testDataFrame sortedShuffled)++-- Test that shuffling isn't doing anything sneaky with summoning+-- random numbers somehow+shuffleSameSeedIsSameShuffle :: Test+shuffleSameSeedIsSameShuffle =+ let gen = mkStdGen 1234+ shuffled1 = shuffle gen testDataFrame+ shuffled2 = shuffle gen testDataFrame+ in TestCase+ (assertEqual "shuffle with same seed gives same result" shuffled1 shuffled2)++-- Test that different seeds give different results+shuffleDifferentSeedIsDifferent :: Test+shuffleDifferentSeedIsDifferent =+ let gen1 = mkStdGen 1234+ gen2 = mkStdGen 4321+ shuffled1 = shuffle gen1 testDataFrame+ shuffled2 = shuffle gen2 testDataFrame+ in TestCase+ ( assertEqual+ "shuffle with different seeds gives different results"+ False+ (shuffled1 == shuffled2)+ )++tests :: [Test]+tests =+ [ TestLabel "shuffleShuffles" shuffleShuffles+ , TestLabel "shufflePreservesData" shufflePreservesData+ , TestLabel "shufflePreservesColumnNames" shufflePreservesColumnNames+ , TestLabel "shuffleSameSeedIsSameShuffle" shuffleSameSeedIsSameShuffle+ , TestLabel "shuffleDifferentSeedIsDifferent" shuffleDifferentSeedIsDifferent+ ]
tests/Operations/Sort.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-} module Operations.Sort where @@ -6,6 +7,7 @@ import Data.Char import qualified Data.Text as T import qualified DataFrame as D+import qualified DataFrame.Functions as F import qualified DataFrame.Internal.Column as DI import System.Random import System.Random.Shuffle (shuffle')@@ -40,7 +42,7 @@ , ("test2", DI.fromList ['a' .. 'z']) ] )- (D.sortBy [D.Asc "test1"] testData)+ (D.sortBy [D.Asc (F.col @Int "test1")] testData) ) sortByDescendingWAI :: Test@@ -53,7 +55,7 @@ , ("test2", DI.fromList $ reverse ['a' .. 'z']) ] )- (D.sortBy [D.Desc "test1"] testData)+ (D.sortBy [D.Desc (F.col @Int "test1")] testData) ) sortByTwoColumns :: Test@@ -62,7 +64,7 @@ ( assertEqual "Sorting moreTestData (which is already sorted) is idempotent." moreTestData- (D.sortBy [D.Asc "test1", D.Asc "test2"] moreTestData)+ (D.sortBy [D.Asc (F.col @Int "test1"), D.Asc (F.col @Int "test2")] moreTestData) ) sortByOneColumnAscOneColumnDesc :: Test@@ -75,7 +77,7 @@ , ("test2", DI.fromList $ [10 :: Int, 9 .. 1] ++ [10, 9 .. 1]) ] )- (D.sortBy [D.Asc "test1", D.Desc "test2"] moreTestData)+ (D.sortBy [D.Asc (F.col @Int "test1"), D.Desc (F.col @Int "test2")] moreTestData) ) sortByColumnDoesNotExist :: Test@@ -84,7 +86,7 @@ ( assertExpectException "[Error Case]" (D.columnNotFound "[\"test0\"]" "sortBy" (D.columnNames testData))- (print $ D.sortBy [D.Asc "test0"] testData)+ (print $ D.sortBy [D.Asc (F.col @Int "test0")] testData) ) tests :: [Test]
tests/Parquet.hs view
@@ -40,14 +40,14 @@ , ( "timestamp_col" , D.fromList- [ UTCTime (fromGregorian 2009 3 1) (secondsToDiffTime 0)- , UTCTime (fromGregorian 2009 3 1) (secondsToDiffTime 60)- , UTCTime (fromGregorian 2009 4 1) (secondsToDiffTime 0)- , UTCTime (fromGregorian 2009 4 1) (secondsToDiffTime 60)- , UTCTime (fromGregorian 2009 2 1) (secondsToDiffTime 0)- , UTCTime (fromGregorian 2009 2 1) (secondsToDiffTime 60)- , UTCTime (fromGregorian 2009 1 1) (secondsToDiffTime 0)- , UTCTime (fromGregorian 2009 1 1) (secondsToDiffTime 60)+ [ UTCTime{utctDay = fromGregorian 2009 3 1, utctDayTime = secondsToDiffTime 0}+ , UTCTime{utctDay = fromGregorian 2009 3 1, utctDayTime = secondsToDiffTime 60}+ , UTCTime{utctDay = fromGregorian 2009 4 1, utctDayTime = secondsToDiffTime 0}+ , UTCTime{utctDay = fromGregorian 2009 4 1, utctDayTime = secondsToDiffTime 60}+ , UTCTime{utctDay = fromGregorian 2009 2 1, utctDayTime = secondsToDiffTime 0}+ , UTCTime{utctDay = fromGregorian 2009 2 1, utctDayTime = secondsToDiffTime 60}+ , UTCTime{utctDay = fromGregorian 2009 1 1, utctDayTime = secondsToDiffTime 0}+ , UTCTime{utctDay = fromGregorian 2009 1 1, utctDayTime = secondsToDiffTime 60} ] ) ]@@ -61,6 +61,113 @@ (unsafePerformIO (D.readParquet "./tests/data/alltypes_plain.parquet")) ) +allTypesTinyPagesDimensions :: Test+allTypesTinyPagesDimensions =+ TestCase+ ( assertEqual+ "allTypesTinyPages last few"+ (7300, 13)+ ( unsafePerformIO+ (fmap D.dimensions (D.readParquet "./tests/data/alltypes_tiny_pages.parquet"))+ )+ )++tinyPagesLast10 :: D.DataFrame+tinyPagesLast10 =+ D.fromNamedColumns+ [ ("id", D.fromList @Int32 (reverse [6174 .. 6183]))+ , ("bool_col", D.fromList @Bool (Prelude.take 10 (cycle [False, True])))+ , ("tinyint_col", D.fromList @Int32 [3, 2, 1, 0, 9, 8, 7, 6, 5, 4])+ , ("smallint_col", D.fromList @Int32 [3, 2, 1, 0, 9, 8, 7, 6, 5, 4])+ , ("int_col", D.fromList @Int32 [3, 2, 1, 0, 9, 8, 7, 6, 5, 4])+ , ("bigint_col", D.fromList @Int64 [30, 20, 10, 0, 90, 80, 70, 60, 50, 40])+ ,+ ( "float_col"+ , D.fromList @Float [3.3, 2.2, 1.1, 0, 9.9, 8.8, 7.7, 6.6, 5.5, 4.4]+ )+ ,+ ( "date_string_col"+ , D.fromList @Text+ [ "09/11/10"+ , "09/11/10"+ , "09/11/10"+ , "09/11/10"+ , "09/10/10"+ , "09/10/10"+ , "09/10/10"+ , "09/10/10"+ , "09/10/10"+ , "09/10/10"+ ]+ )+ ,+ ( "string_col"+ , D.fromList @Text ["3", "2", "1", "0", "9", "8", "7", "6", "5", "4"]+ )+ ,+ ( "timestamp_col"+ , D.fromList @UTCTime+ [ UTCTime+ { utctDay = fromGregorian 2010 9 10+ , utctDayTime = secondsToDiffTime 85384+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 10+ , utctDayTime = secondsToDiffTime 85324+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 10+ , utctDayTime = secondsToDiffTime 85264+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 10+ , utctDayTime = secondsToDiffTime 85204+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 9+ , utctDayTime = secondsToDiffTime 85144+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 9+ , utctDayTime = secondsToDiffTime 85084+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 9+ , utctDayTime = secondsToDiffTime 85024+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 9+ , utctDayTime = secondsToDiffTime 84964+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 9+ , utctDayTime = secondsToDiffTime 84904+ }+ , UTCTime+ { utctDay = fromGregorian 2010 9 9+ , utctDayTime = secondsToDiffTime 84844+ }+ ]+ )+ , ("year", D.fromList @Int32 (replicate 10 2010))+ , ("month", D.fromList @Int32 (replicate 10 9))+ ]++allTypesTinyPagesLastFew :: Test+allTypesTinyPagesLastFew =+ TestCase+ ( assertEqual+ "allTypesTinyPages dimensions"+ tinyPagesLast10+ ( unsafePerformIO+ -- Excluding doubles because they are weird to compare.+ ( fmap+ (D.takeLast 10 . D.exclude ["double_col"])+ (D.readParquet "./tests/data/alltypes_tiny_pages.parquet")+ )+ )+ )+ allTypesPlainSnappy :: Test allTypesPlainSnappy = TestCase@@ -79,6 +186,176 @@ (unsafePerformIO (D.readParquet "./tests/data/alltypes_dictionary.parquet")) ) +transactions :: D.DataFrame+transactions =+ D.fromNamedColumns+ [ ("transaction_id", D.fromList [1 :: Int32, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])+ ,+ ( "event_time"+ , D.fromList+ [ UTCTime+ { utctDay = fromGregorian 2024 1 3+ , utctDayTime = secondsToDiffTime 29564 + picosecondsToDiffTime 2311000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 3+ , utctDayTime = secondsToDiffTime 35101 + picosecondsToDiffTime 118900000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 4+ , utctDayTime = secondsToDiffTime 39802 + picosecondsToDiffTime 774512000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 5+ , utctDayTime = secondsToDiffTime 53739 + picosecondsToDiffTime 1000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 6+ , utctDayTime = secondsToDiffTime 8278 + picosecondsToDiffTime 543210000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 6+ , utctDayTime = secondsToDiffTime 8284 + picosecondsToDiffTime 211000000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 7+ , utctDayTime = secondsToDiffTime 63000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 8+ , utctDayTime = secondsToDiffTime 24259 + picosecondsToDiffTime 390000000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 9+ , utctDayTime = secondsToDiffTime 48067 + picosecondsToDiffTime 812345000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 10+ , utctDayTime = secondsToDiffTime 82799 + picosecondsToDiffTime 999999000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 11+ , utctDayTime = secondsToDiffTime 36000 + picosecondsToDiffTime 100000000000+ }+ , UTCTime+ { utctDay = fromGregorian 2024 1 12+ , utctDayTime = secondsToDiffTime 56028 + picosecondsToDiffTime 667891000000+ }+ ]+ )+ ,+ ( "user_email"+ , D.fromList+ [ "alice@example.com" :: Text+ , "bob@example.com"+ , "carol@example.com"+ , "alice@example.com"+ , "dave@example.com"+ , "dave@example.com"+ , "eve@example.com"+ , "frank@example.com"+ , "grace@example.com"+ , "dave@example.com"+ , "alice@example.com"+ , "heidi@example.com"+ ]+ )+ ,+ ( "transaction_type"+ , D.fromList+ [ "purchase" :: Text+ , "purchase"+ , "refund"+ , "purchase"+ , "purchase"+ , "purchase"+ , "purchase"+ , "withdrawal"+ , "purchase"+ , "purchase"+ , "purchase"+ , "refund"+ ]+ )+ ,+ ( "amount"+ , D.fromList+ [ 142.50 :: Double+ , 29.99+ , 89.00+ , 2399.00+ , 15.00+ , 15.00+ , 450.75+ , 200.00+ , 55.20+ , 3200.00+ , 74.99+ , 120.00+ ]+ )+ ,+ ( "currency"+ , D.fromList+ [ "USD" :: Text+ , "USD"+ , "EUR"+ , "USD"+ , "GBP"+ , "GBP"+ , "USD"+ , "EUR"+ , "CAD"+ , "USD"+ , "USD"+ , "GBP"+ ]+ )+ ,+ ( "status"+ , D.fromList+ [ "approved" :: Text+ , "approved"+ , "approved"+ , "declined"+ , "approved"+ , "declined"+ , "approved"+ , "approved"+ , "approved"+ , "flagged"+ , "approved"+ , "approved"+ ]+ )+ ,+ ( "location"+ , D.fromList+ [ "New York, US" :: Text+ , "London, GB"+ , "Berlin, DE"+ , "New York, US"+ , "Manchester, GB"+ , "Lagos, NG"+ , "San Francisco, US"+ , "Paris, FR"+ , "Toronto, CA"+ , "New York, US"+ , "New York, US"+ , "Edinburgh, GB"+ ]+ )+ ]++transactionsTest :: Test+transactionsTest =+ TestCase+ ( assertEqual+ "transactions"+ transactions+ (unsafePerformIO (D.readParquet "./tests/data/transactions.parquet"))+ )+ mtCarsDataset :: D.DataFrame mtCarsDataset = D.fromNamedColumns@@ -537,7 +814,13 @@ (unsafePerformIO (D.readParquet "./tests/data/mtcars.parquet")) ) --- Uncomment to run parquet tests.--- Currently commented because they don't run with github CI tests :: [Test]-tests = [allTypesPlain, allTypesPlainSnappy, allTypesDictionary, mtCars]+tests =+ [ allTypesPlain+ , allTypesPlainSnappy+ , allTypesDictionary+ , mtCars+ , allTypesTinyPagesLastFew+ , allTypesTinyPagesDimensions+ , transactionsTest+ ]
+ tests/data/transactions.parquet view
binary file changed (absent → 1746 bytes)