packages feed

dataframe 0.3.4.0 → 0.3.4.1

raw patch · 7 files changed

+685/−496 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- DataFrame.Functions: BeamConfig :: Int -> Int -> LossFunction -> Bool -> BeamConfig
- DataFrame.Functions: F1 :: LossFunction
- DataFrame.Functions: MeanSquaredError :: LossFunction
- DataFrame.Functions: MutualInformation :: LossFunction
- DataFrame.Functions: PearsonCorrelation :: LossFunction
- DataFrame.Functions: [beamLength] :: BeamConfig -> Int
- DataFrame.Functions: [includeConditionals] :: BeamConfig -> Bool
- DataFrame.Functions: [lossFunction] :: BeamConfig -> LossFunction
- DataFrame.Functions: [searchDepth] :: BeamConfig -> Int
- DataFrame.Functions: beamSearch :: DataFrame -> BeamConfig -> TypedColumn Double -> [Expr Double] -> [Expr Bool] -> [Expr Double] -> Maybe (Expr Double)
- DataFrame.Functions: data BeamConfig
- DataFrame.Functions: data LossFunction
- DataFrame.Functions: deduplicate :: Columnable a => DataFrame -> [Expr a] -> [(Expr a, TypedColumn a)]
- DataFrame.Functions: defaultBeamConfig :: BeamConfig
- DataFrame.Functions: equivalent :: DataFrame -> Expr Double -> Expr Double -> Bool
- DataFrame.Functions: f1FromBinary :: Vector Double -> Vector Double -> Maybe Double
- DataFrame.Functions: f1FromCounts :: Int -> Int -> Int -> Maybe Double
- DataFrame.Functions: fitClassifier :: Text -> Int -> Int -> DataFrame -> Either String (Expr Int)
- DataFrame.Functions: fitRegression :: Text -> Int -> Int -> DataFrame -> Either String (Expr Double)
- DataFrame.Functions: generateConditions :: TypedColumn Double -> [Expr Bool] -> [Expr Double] -> DataFrame -> [Expr Bool]
- DataFrame.Functions: generatePrograms :: Bool -> [Expr Bool] -> [Expr Double] -> [Expr Double] -> [Expr Double] -> [Expr Double]
- DataFrame.Functions: getLossFunction :: LossFunction -> Vector Double -> Vector Double -> Maybe Double
- DataFrame.Functions: isLiteral :: Expr a -> Bool
- DataFrame.Functions: percentiles :: DataFrame -> [Expr Double]
- DataFrame.Functions: pickTopN :: DataFrame -> TypedColumn Double -> BeamConfig -> [(Expr Double, TypedColumn a)] -> [Expr Double]
- DataFrame.Functions: pickTopNBool :: DataFrame -> TypedColumn Double -> [(Expr Bool, TypedColumn Bool)] -> [Expr Bool]
- DataFrame.Functions: roundTo2SigDigits :: Double -> Double
- DataFrame.Functions: roundToSigDigits :: Int -> Double -> Double
- DataFrame.Functions: satisfiesExamples :: DataFrame -> TypedColumn Double -> Expr Double -> Bool
- DataFrame.Functions: synthesizeFeatureExpr :: Text -> BeamConfig -> DataFrame -> Either String (Expr Double)
+ DataFrame.Functions: infix 4 .>=
+ DataFrame.Functions: infixr 2 .||
+ DataFrame.Functions: infixr 3 .&&
+ DataFrame.Functions: medianMaybe :: (Columnable a, Real a) => Expr (Maybe a) -> Expr Double
+ DataFrame.Internal.Expression: mkReducedColumnBoxed :: Vector a -> Vector Int -> Vector Int -> (a -> a -> a) -> Vector a
+ DataFrame.Internal.Expression: mkReducedColumnUnboxed :: Unbox a => Vector a -> Vector Int -> Vector Int -> (a -> a -> a) -> Vector a
+ DataFrame.Synthesis: BeamConfig :: Int -> Int -> LossFunction -> Bool -> BeamConfig
+ DataFrame.Synthesis: F1 :: LossFunction
+ DataFrame.Synthesis: MeanSquaredError :: LossFunction
+ DataFrame.Synthesis: MutualInformation :: LossFunction
+ DataFrame.Synthesis: PearsonCorrelation :: LossFunction
+ DataFrame.Synthesis: [beamLength] :: BeamConfig -> Int
+ DataFrame.Synthesis: [includeConditionals] :: BeamConfig -> Bool
+ DataFrame.Synthesis: [lossFunction] :: BeamConfig -> LossFunction
+ DataFrame.Synthesis: [searchDepth] :: BeamConfig -> Int
+ DataFrame.Synthesis: beamSearch :: DataFrame -> BeamConfig -> TypedColumn Double -> [Expr Double] -> [Expr Bool] -> [Expr Double] -> Maybe (Expr Double)
+ DataFrame.Synthesis: data BeamConfig
+ DataFrame.Synthesis: data LossFunction
+ DataFrame.Synthesis: deduplicate :: Columnable a => DataFrame -> [Expr a] -> [(Expr a, TypedColumn a)]
+ DataFrame.Synthesis: defaultBeamConfig :: BeamConfig
+ DataFrame.Synthesis: equivalent :: DataFrame -> Expr Double -> Expr Double -> Bool
+ DataFrame.Synthesis: f1FromBinary :: Vector Double -> Vector Double -> Maybe Double
+ DataFrame.Synthesis: f1FromCounts :: Int -> Int -> Int -> Maybe Double
+ DataFrame.Synthesis: fitClassifier :: Text -> Int -> Int -> DataFrame -> Either String (Expr Int)
+ DataFrame.Synthesis: fitRegression :: Text -> Int -> Int -> DataFrame -> Either String (Expr Double)
+ DataFrame.Synthesis: generateConditions :: TypedColumn Double -> [Expr Bool] -> [Expr Double] -> DataFrame -> [Expr Bool]
+ DataFrame.Synthesis: generatePrograms :: Bool -> [Expr Bool] -> [Expr Double] -> [Expr Double] -> [Expr Double] -> [Expr Double]
+ DataFrame.Synthesis: getLossFunction :: LossFunction -> Vector Double -> Vector Double -> Maybe Double
+ DataFrame.Synthesis: isLiteral :: Expr a -> Bool
+ DataFrame.Synthesis: percentiles :: DataFrame -> [Expr Double]
+ DataFrame.Synthesis: pickTopN :: DataFrame -> TypedColumn Double -> BeamConfig -> [(Expr Double, TypedColumn a)] -> [Expr Double]
+ DataFrame.Synthesis: pickTopNBool :: DataFrame -> TypedColumn Double -> [(Expr Bool, TypedColumn Bool)] -> [Expr Bool]
+ DataFrame.Synthesis: roundTo2SigDigits :: Double -> Double
+ DataFrame.Synthesis: roundToSigDigits :: Int -> Double -> Double
+ DataFrame.Synthesis: satisfiesExamples :: DataFrame -> TypedColumn Double -> Expr Double -> Bool
+ DataFrame.Synthesis: synthesizeFeatureExpr :: Text -> BeamConfig -> DataFrame -> Either String (Expr Double)
- DataFrame.Functions: sum :: (Columnable a, Num a, Unbox a) => Expr a -> Expr a
+ DataFrame.Functions: sum :: (Columnable a, Num a) => Expr a -> Expr a
- DataFrame.Internal.Expression: [AggReduce] :: forall a. Columnable a => Expr a -> Text -> (forall a1. Columnable a1 => a1 -> a1 -> a1) -> Expr a
+ DataFrame.Internal.Expression: [AggReduce] :: forall a. Columnable a => Expr a -> Text -> (a -> a -> a) -> Expr a

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for dataframe +## 0.3.4.1+* Faster sum operation (now does a reduction instead of collecting the vector and aggregating)+* Update the fixity of comparison operations. Before `(x + y) .<= 10`. Now: `x + y ,<= 10`.+* Revert sort for groupby back to mergesort.+ ## 0.3.4.0 * Fix right join - previously erased some values in the key. * Change sort API so we can sort on different rows.
dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               dataframe-version:            0.3.4.0+version:            0.3.4.1  synopsis: A fast, safe, and intuitive DataFrame library. @@ -44,6 +44,7 @@     exposed-modules: DataFrame,                     DataFrame.Lazy,                     DataFrame.Functions,+                    DataFrame.Synthesis,                     DataFrame.Display.Web.Plot,                     DataFrame.Internal.Types,                     DataFrame.Internal.Expression,
src/DataFrame/Functions.hs view
@@ -17,49 +17,37 @@ import DataFrame.Internal.Column import DataFrame.Internal.DataFrame (     DataFrame (..),-    columnAsDoubleVector,     unsafeGetColumn,  ) import DataFrame.Internal.Expression (     Expr (..),     NamedExpr,     UExpr (..),-    eSize,-    interpret,-    replaceExpr,  ) import DataFrame.Internal.Statistics-import qualified DataFrame.Operations.Statistics as Stats-import DataFrame.Operations.Subset (exclude, select) -import Control.Exception (throw) import Control.Monad-import Control.Monad.IO.Class import qualified Data.Char as Char-import Data.Containers.ListUtils import Data.Function import Data.Functor import qualified Data.List as L import qualified Data.Map as M import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe)-import qualified Data.Set as S import qualified Data.Text as T-import qualified Data.Text.IO as T import Data.Time-import Data.Type.Equality import qualified Data.Vector as V-import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as VU-import qualified DataFrame.Operations.Core as D-import qualified DataFrame.Operations.Transformations as D import Debug.Trace (trace) import Language.Haskell.TH import qualified Language.Haskell.TH.Syntax as TH import Text.Regex.TDFA-import Type.Reflection (typeRep) 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 =@@ -167,8 +155,8 @@ maximum :: (Columnable a, Ord a) => Expr a -> Expr a maximum expr = AggReduce expr "maximum" Prelude.max -sum :: forall a. (Columnable a, Num a, VU.Unbox a) => Expr a -> Expr a-sum expr = AggNumericVector expr "sum" VG.sum+sum :: forall a. (Columnable a, Num a) => Expr a -> Expr a+sum expr = AggReduce expr "sum" (+)  sumMaybe :: forall a. (Columnable a, Num a) => Expr (Maybe a) -> Expr a sumMaybe expr = AggVector expr "sumMaybe" (P.sum . catMaybes . V.toList)@@ -185,6 +173,9 @@ median :: (Columnable a, Real a, VU.Unbox a) => Expr a -> Expr Double median expr = AggNumericVector expr "median" median' +medianMaybe :: (Columnable a, Real a) => Expr (Maybe a) -> Expr Double+medianMaybe expr = AggVector expr "meanMaybe" (median' . optionalToDoubleVector)+ optionalToDoubleVector :: (Real a) => V.Vector (Maybe a) -> VU.Vector Double optionalToDoubleVector =     VU.fromList@@ -276,447 +267,6 @@     (a -> m b) -> Expr (m a) -> Expr (m b) bind f = lift (>>= f) -generateConditions ::-    TypedColumn Double -> [Expr Bool] -> [Expr Double] -> DataFrame -> [Expr Bool]-generateConditions labels conds ps df =-    let-        newConds =-            [ p .<= q-            | p <- ps-            , q <- ps-            , p /= q-            ]-                ++ [ DataFrame.Functions.not p-                   | p <- conds-                   ]-        expandedConds =-            conds-                ++ newConds-                ++ [p .&& q | p <- newConds, q <- conds, p /= q]-                ++ [p .|| q | p <- newConds, q <- conds, p /= q]-     in-        pickTopNBool df labels (deduplicate df expandedConds)--generatePrograms ::-    Bool ->-    [Expr Bool] ->-    [Expr Double] ->-    [Expr Double] ->-    [Expr Double] ->-    [Expr Double]-generatePrograms _ _ vars' constants [] = vars' ++ constants-generatePrograms includeConds conds vars constants ps =-    let-        existingPrograms = ps ++ vars ++ constants-     in-        existingPrograms-            ++ [ transform p-               | p <- ps ++ vars-               , transform <--                    [ sqrt-                    , abs-                    , log . (+ Lit 1)-                    , exp-                    , sin-                    , cos-                    , relu-                    , signum-                    ]-               ]-            ++ [ pow i p-               | p <- existingPrograms-               , i <- [2 .. 6]-               ]-            ++ [ p + q-               | (i, p) <- zip [0 ..] existingPrograms-               , (j, q) <- zip [0 ..] existingPrograms-               , Prelude.not (isLiteral p && isLiteral q)-               , i >= j-               ]-            ++ ( if includeConds-                    then-                        [ DataFrame.Functions.min p q-                        | (i, p) <- zip [0 ..] existingPrograms-                        , (j, q) <- zip [0 ..] existingPrograms-                        , Prelude.not (isLiteral p && isLiteral q)-                        , p /= q-                        , i > j-                        ]-                            ++ [ DataFrame.Functions.max p q-                               | (i, p) <- zip [0 ..] existingPrograms-                               , (j, q) <- zip [0 ..] existingPrograms-                               , Prelude.not (isLiteral p && isLiteral q)-                               , p /= q-                               , i > j-                               ]-                            ++ [ ifThenElse cond r s-                               | cond <- conds-                               , r <- existingPrograms-                               , s <- existingPrograms-                               , r /= s-                               ]-                    else []-               )-            ++ [ p - q-               | (i, p) <- zip [0 ..] existingPrograms-               , (j, q) <- zip [0 ..] existingPrograms-               , Prelude.not (isLiteral p && isLiteral q)-               , i /= j-               ]-            ++ [ p * q-               | (i, p) <- zip [0 ..] existingPrograms-               , (j, q) <- zip [0 ..] existingPrograms-               , Prelude.not (isLiteral p && isLiteral q)-               , i >= j-               ]-            ++ [ p / q-               | p <- existingPrograms-               , q <- existingPrograms-               , Prelude.not (isLiteral p && isLiteral q)-               , p /= q-               ]--isLiteral :: Expr a -> Bool-isLiteral (Lit _) = True-isLiteral _ = False--deduplicate ::-    forall a.-    (Columnable a) =>-    DataFrame ->-    [Expr a] ->-    [(Expr a, TypedColumn a)]-deduplicate df = go S.empty . nubOrd . L.sortBy (\e1 e2 -> compare (eSize e1) (eSize e2))-  where-    go _ [] = []-    go seen (x : xs)-        | hasInvalid = go seen xs-        | S.member res seen = go seen xs-        | otherwise = (x, res) : go (S.insert res seen) xs-      where-        res = case interpret @a df x of-            Left e -> throw e-            Right v -> v-        hasInvalid = case res of-            (TColumn (UnboxedColumn (col :: VU.Vector b))) -> case testEquality (typeRep @Double) (typeRep @b) of-                Just Refl -> VU.any (\n -> isNaN n || isInfinite n) col-                Nothing -> False-            _ -> False---- | Checks if two programs generate the same outputs given all the same inputs.-equivalent :: DataFrame -> Expr Double -> Expr Double -> Bool-equivalent df p1 p2 = case (==) <$> interpret df p1 <*> interpret df p2 of-    Left e -> throw e-    Right v -> v--synthesizeFeatureExpr ::-    -- | Target expression-    T.Text ->-    BeamConfig ->-    DataFrame ->-    Either String (Expr Double)-synthesizeFeatureExpr target cfg df =-    let-        df' = exclude [target] df-        t = case interpret df (Col target) of-            Left e -> throw e-            Right v -> v-     in-        case beamSearch-            df'-            cfg-            t-            (percentiles df')-            []-            [] of-            Nothing -> Left "No programs found"-            Just p -> Right p--f1FromBinary :: VU.Vector Double -> VU.Vector Double -> Maybe Double-f1FromBinary trues preds =-    let (!tp, !fp, !fn) =-            VU.foldl' step (0 :: Int, 0 :: Int, 0 :: Int) $-                VU.zip (VU.map (> 0) preds) (VU.map (> 0) trues)-     in f1FromCounts tp fp fn-  where-    step (!tp, !fp, !fn) (!p, !t) =-        case (p, t) of-            (True, True) -> (tp + 1, fp, fn)-            (True, False) -> (tp, fp + 1, fn)-            (False, True) -> (tp, fp, fn + 1)-            (False, False) -> (tp, fp, fn)--f1FromCounts :: Int -> Int -> Int -> Maybe Double-f1FromCounts tp fp fn =-    let tp' = fromIntegral tp-        fp' = fromIntegral fp-        fn' = fromIntegral fn-        precision = if tp' + fp' == 0 then 0 else tp' / (tp' + fp')-        recall = if tp' + fn' == 0 then 0 else tp' / (tp' + fn')-     in if precision + recall == 0-            then Nothing-            else Just (2 * precision * recall / (precision + recall))--fitClassifier ::-    -- | Target expression-    T.Text ->-    -- | Depth of search (Roughly, how many terms in the final expression)-    Int ->-    -- | Beam size - the number of candidate expressions to consider at a time.-    Int ->-    DataFrame ->-    Either String (Expr Int)-fitClassifier target d b df =-    let-        df' = exclude [target] df-        t = case interpret df (Col target) of-            Left e -> throw e-            Right v -> v-     in-        case beamSearch-            df'-            (BeamConfig d b F1 True)-            t-            (percentiles df' ++ [lit 1, lit 0, lit (-1)])-            []-            [] of-            Nothing -> Left "No programs found"-            Just p -> Right (ifThenElse (p .> 0) 1 0)--percentiles :: DataFrame -> [Expr Double]-percentiles df =-    let-        doubleColumns = map (either throw id . (`columnAsDoubleVector` df)) (D.columnNames df)-     in-        concatMap-            (\c -> map (lit . roundTo2SigDigits . (`percentile'` c)) [1, 25, 75, 99])-            doubleColumns-            ++ map (lit . roundTo2SigDigits . variance') doubleColumns-            ++ map (lit . roundTo2SigDigits . sqrt . variance') doubleColumns--roundToSigDigits :: Int -> Double -> Double-roundToSigDigits n x-    | x == 0 = 0-    | otherwise =-        let magnitude = floor (logBase 10 (abs x))-            scale = 10 ** fromIntegral (n - 1 - magnitude)-         in fromIntegral (round (x * scale)) / scale--roundTo2SigDigits :: Double -> Double-roundTo2SigDigits = roundToSigDigits 2--fitRegression ::-    -- | Target expression-    T.Text ->-    -- | Depth of search (Roughly, how many terms in the final expression)-    Int ->-    -- | Beam size - the number of candidate expressions to consider at a time.-    Int ->-    DataFrame ->-    Either String (Expr Double)-fitRegression target d b df =-    let-        df' = exclude [target] df-        targetMean = Stats.mean (Col @Double target) df-        t = case interpret df (Col target) of-            Left e -> throw e-            Right v -> v-     in-        case beamSearch-            df'-            ( BeamConfig-                d-                b-                MutualInformation-                False-            )-            t-            (percentiles df')-            []-            [] of-            Nothing -> Left "No programs found"-            Just p ->-                trace (show p) $-                    let-                     in case beamSearch-                            ( D.derive "_generated_regression_feature_" p df-                                & select ["_generated_regression_feature_"]-                            )-                            (BeamConfig d b MeanSquaredError False)-                            t-                            (percentiles df' ++ [lit targetMean, lit 10])-                            []-                            [Col "_generated_regression_feature_"] of-                            Nothing -> Left "Could not find coefficients"-                            Just p' -> Right (replaceExpr p (Col @Double "_generated_regression_feature_") p')--data LossFunction-    = PearsonCorrelation-    | MutualInformation-    | MeanSquaredError-    | F1--getLossFunction ::-    LossFunction -> (VU.Vector Double -> VU.Vector Double -> Maybe Double)-getLossFunction f = case f of-    MutualInformation ->-        ( \l r ->-            mutualInformationBinned-                (Prelude.max 10 (ceiling (sqrt (fromIntegral (VU.length l)))))-                l-                r-        )-    PearsonCorrelation -> (\l r -> (^ 2) <$> correlation' l r)-    MeanSquaredError -> (\l r -> fmap negate (meanSquaredError l r))-    F1 -> f1FromBinary--data BeamConfig = BeamConfig-    { searchDepth :: Int-    , beamLength :: Int-    , lossFunction :: LossFunction-    , includeConditionals :: Bool-    }--defaultBeamConfig :: BeamConfig-defaultBeamConfig = BeamConfig 2 100 PearsonCorrelation False--beamSearch ::-    DataFrame ->-    -- | Parameters of the beam search.-    BeamConfig ->-    -- | Examples-    TypedColumn Double ->-    -- | Constants-    [Expr Double] ->-    -- | Conditions-    [Expr Bool] ->-    -- | Programs-    [Expr Double] ->-    Maybe (Expr Double)-beamSearch df cfg outputs constants conds programs-    | searchDepth cfg == 0 = case ps of-        [] -> Nothing-        (x : _) -> Just x-    | otherwise =-        beamSearch-            df-            (cfg{searchDepth = searchDepth cfg - 1})-            outputs-            constants-            conditions-            (generatePrograms (includeConditionals cfg) conditions vars constants ps)-  where-    vars = map col names-    conditions = generateConditions outputs conds (vars ++ constants ++ ps) df-    ps = pickTopN df outputs cfg $ deduplicate df programs-    names = (map fst . L.sortBy (compare `on` snd) . M.toList . columnIndices) df--pickTopN ::-    DataFrame ->-    TypedColumn Double ->-    BeamConfig ->-    [(Expr Double, TypedColumn a)] ->-    [Expr Double]-pickTopN _ _ _ [] = []-pickTopN df (TColumn col) cfg ps =-    let-        l = case toVector @Double @VU.Vector col of-            Left e -> throw e-            Right v -> v-        ordered =-            Prelude.take-                (beamLength cfg)-                ( map fst $-                    L.sortBy-                        ( \(_, c2) (_, c1) ->-                            if maybe False isInfinite c1-                                || maybe False isInfinite c2-                                || maybe False isNaN c1-                                || maybe False isNaN c2-                                then LT-                                else compare c1 c2-                        )-                        ( map-                            (\(e, res) -> (e, getLossFunction (lossFunction cfg) l (asDoubleVector res)))-                            ps-                        )-                )-        asDoubleVector c =-            let-                (TColumn col') = c-             in-                case toVector @Double @VU.Vector col' of-                    Left e -> throw e-                    Right v -> VU.convert v-        interpretDoubleVector e =-            let-                (TColumn col') = case interpret df e of-                    Left e -> throw e-                    Right v -> v-             in-                case toVector @Double @VU.Vector col' of-                    Left e -> throw e-                    Right v -> VU.convert v-     in-        trace-            ( "Best loss: "-                ++ show-                    ( getLossFunction (lossFunction cfg) l . interpretDoubleVector-                        <$> listToMaybe ordered-                    )-                ++ " "-                ++ (if null ordered then "empty" else show (listToMaybe ordered))-            )-            ordered--pickTopNBool ::-    DataFrame ->-    TypedColumn Double ->-    [(Expr Bool, TypedColumn Bool)] ->-    [Expr Bool]-pickTopNBool _ _ [] = []-pickTopNBool df (TColumn col) ps =-    let-        l = case toVector @Double @VU.Vector col of-            Left e -> throw e-            Right v -> v-        ordered =-            Prelude.take-                10-                ( map fst $-                    L.sortBy-                        ( \(_, c2) (_, c1) ->-                            if maybe False isInfinite c1-                                || maybe False isInfinite c2-                                || maybe False isNaN c1-                                || maybe False isNaN c2-                                then LT-                                else compare c1 c2-                        )-                        ( map-                            (\(e, res) -> (e, getLossFunction MutualInformation l (asDoubleVector res)))-                            ps-                        )-                )-        asDoubleVector c =-            let-                (TColumn col') = c-             in-                case toVector @Bool @VU.Vector col' of-                    Left e -> throw e-                    Right v -> VU.map (fromIntegral @Int @Double . fromEnum) v-     in-        ordered--satisfiesExamples :: DataFrame -> TypedColumn Double -> Expr Double -> Bool-satisfiesExamples df col expr =-    let-        result = case interpret df expr of-            Left e -> throw e-            Right v -> v-     in-        result == col- -- See Section 2.4 of the Haskell Report https://www.haskell.org/definition/haskell2010.pdf isReservedId :: T.Text -> Bool isReservedId t = case t of@@ -817,7 +367,7 @@      in         fmap concat $ forM specs $ \(raw, nm, tyStr) -> do             ty <- typeFromString (words tyStr)-            liftIO $ T.putStrLn (nm <> " :: Expr " <> T.pack tyStr)+            trace (T.unpack (nm <> " :: Expr " <> T.pack tyStr)) pure ()             let n = mkName (T.unpack nm)             sig <- sigD n [t|Expr $(pure ty)|]             val <- valD (varP n) (normalB [|col $(TH.lift raw)|]) []
src/DataFrame/Internal/Expression.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}@@ -14,6 +15,7 @@  module DataFrame.Internal.Expression where +import Control.Monad.ST (runST) import qualified Data.Map as M import Data.Maybe (fromMaybe, isJust) import Data.String@@ -21,7 +23,9 @@ import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl)) import qualified Data.Vector as V import qualified Data.Vector.Generic as VG+import qualified Data.Vector.Mutable as VM import qualified Data.Vector.Unboxed as VU+import qualified Data.Vector.Unboxed.Mutable as VUM import DataFrame.Errors import DataFrame.Internal.Column import DataFrame.Internal.DataFrame@@ -69,7 +73,7 @@         (Columnable a) =>         Expr a ->         T.Text -> -- Operation name-        (forall a. (Columnable a) => a -> a -> a) ->+        (a -> a -> a) ->         Expr a     AggNumericVector ::         ( Columnable a@@ -290,7 +294,7 @@                                 , errorColumnName = Nothing                                 }                             )-interpret df expression@(AggReduce expr op (f :: forall a. (Columnable a) => a -> a -> a)) = case interpret @a df expr of+interpret df expression@(AggReduce expr op (f :: a -> a -> a)) = case interpret @a df expr of     Left (TypeMismatchException context) ->         Left $             TypeMismatchException@@ -473,6 +477,68 @@                 f (VU.unsafeSlice (start i) (n i) sorted)             ) +mkReducedColumnUnboxed ::+    forall a.+    (VU.Unbox a) =>+    VU.Vector a ->+    VU.Vector Int ->+    VU.Vector Int ->+    (a -> a -> a) ->+    VU.Vector a+mkReducedColumnUnboxed col os indices f = runST $ do+    let len = VU.length os - 1+    mvec <- VUM.unsafeNew len++    let loopOut i+            | i == len = return ()+            | otherwise = do+                let start = os `VU.unsafeIndex` i+                let end = os `VU.unsafeIndex` (i + 1)+                let initVal = col `VU.unsafeIndex` (indices `VU.unsafeIndex` start)++                let loopIn !acc idx+                        | idx == end = acc+                        | otherwise =+                            let val = col `VU.unsafeIndex` (indices `VU.unsafeIndex` idx)+                             in loopIn (f acc val) (idx + 1)+                let !finalVal = loopIn initVal (start + 1)+                VUM.unsafeWrite mvec i finalVal+                loopOut (i + 1)++    loopOut 0+    VU.unsafeFreeze mvec+{-# INLINE mkReducedColumnUnboxed #-}++mkReducedColumnBoxed ::+    V.Vector a ->+    VU.Vector Int ->+    VU.Vector Int ->+    (a -> a -> a) ->+    V.Vector a+mkReducedColumnBoxed col os indices f = runST $ do+    let len = VU.length os - 1+    mvec <- VM.unsafeNew len++    let loopOut i+            | i == len = return ()+            | otherwise = do+                let start = os `VU.unsafeIndex` i+                let end = os `VU.unsafeIndex` (i + 1)+                let initVal = col `V.unsafeIndex` (indices `VU.unsafeIndex` start)++                let loopIn !acc idx+                        | idx == end = acc+                        | otherwise =+                            let val = col `V.unsafeIndex` (indices `VU.unsafeIndex` idx)+                             in loopIn (f acc val) (idx + 1)+                let !finalVal = loopIn initVal (start + 1)+                VM.unsafeWrite mvec i finalVal+                loopOut (i + 1)++    loopOut 0+    V.unsafeFreeze mvec+{-# INLINE mkReducedColumnBoxed #-}+ nestedTypeException ::     forall a b. (Typeable a, Typeable b) => String -> DataFrameException nestedTypeException expression = case typeRep @a of@@ -861,7 +927,34 @@                             , errorColumnName = Just (show expr)                             }                         )-interpretAggregation gdf@(Grouped df names indices os) expression@(AggReduce expr op (f :: forall a. (Columnable a) => a -> a -> a)) =+interpretAggregation gdf@(Grouped df names indices os) expression@(AggReduce (Col name) op (f :: a -> a -> a)) =+    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"+            Just Refl ->+                Right $+                    Aggregated $+                        TColumn $+                            fromVector $+                                mkReducedColumnBoxed col os indices f+        Just (OptionalColumn (col :: V.Vector d)) -> case testEquality (typeRep @a) (typeRep @d) of+            Nothing -> error "Type mismatch"+            Just Refl ->+                Right $+                    Aggregated $+                        TColumn $+                            fromVector $+                                mkReducedColumnBoxed col os indices f+        Just (UnboxedColumn (col :: VU.Vector d)) -> case testEquality (typeRep @a) (typeRep @d) of+            Just Refl ->+                Right $+                    Aggregated $+                        TColumn $+                            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         (Left (TypeMismatchException context)) ->             Left $
src/DataFrame/Operations/Aggregation.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -12,11 +13,13 @@ import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Vector as V-import qualified Data.Vector.Algorithms.Radix as VA+import qualified Data.Vector.Algorithms.Merge as VA import qualified Data.Vector.Generic as VG import qualified Data.Vector.Unboxed as VU+import qualified Data.Vector.Unboxed.Mutable as VUM  import Control.Exception (throw)+import Control.Monad import Control.Monad.ST (runST) import Data.Hashable import Data.Type.Equality (TestEquality (..), type (:~:) (Refl))@@ -59,11 +62,7 @@      valueIndices = runST $ do         withIndexes <- VG.thaw $ VG.indexed rowRepresentations-        VA.sortBy-            (VA.passes @Int 0)-            (VA.size @Int 0)-            (\p e -> VA.radix 0 (snd e))-            withIndexes+        VA.sortBy (\(a, b) (a', b') -> compare b' b) withIndexes         VG.unsafeFreeze withIndexes  changingPoints :: (Eq a, VU.Unbox a) => VU.Vector (Int, a) -> [Int]@@ -75,36 +74,91 @@         | otherwise = (index : offsets, newVal)  computeRowHashes :: [Int] -> DataFrame -> VU.Vector Int-computeRowHashes indices df =-    L.foldl' combineCol initialHashes selectedCols-  where-    n = fst (dimensions df)-    initialHashes = VU.replicate n 0+computeRowHashes indices df = runST $ do+    let n = fst (dimensions df)+    mv <- VUM.new n -    selectedCols = map (columns df V.!) indices+    let selectedCols = map (columns df V.!) indices -    combineCol :: VU.Vector Int -> Column -> VU.Vector Int-    combineCol acc col = case col of-        UnboxedColumn (v :: VU.Vector a) -> case testEquality (typeRep @a) (typeRep @Int) of-            Just Refl -> VU.zipWith hashWithSalt acc v-            Nothing -> case testEquality (typeRep @a) (typeRep @Double) of-                Just Refl -> VU.zipWith (\h d -> hashWithSalt h (doubleToInt d)) acc v-                Nothing -> case sIntegral @a of-                    STrue -> VU.zipWith (\h d -> hashWithSalt h (fromIntegral @a @Int d)) acc v-                    SFalse -> case sFloating @a of-                        STrue -> VU.zipWith (\h d -> hashWithSalt h ((doubleToInt . realToFrac) d)) acc v-                        SFalse -> VU.zipWith (\h d -> hashWithSalt h (hash (show d))) acc v-        BoxedColumn (v :: V.Vector a) -> case testEquality (typeRep @a) (typeRep @T.Text) of-            Just Refl -> VG.convert (V.zipWith hashWithSalt (VG.convert acc) v)-            Nothing ->-                VG.convert-                    (V.zipWith (\h d -> hashWithSalt h (hash (show d))) (VG.convert acc) v)+    forM_ selectedCols $ \case+        UnboxedColumn (v :: VU.Vector a) ->+            case testEquality (typeRep @a) (typeRep @Int) of+                Just Refl ->+                    VU.imapM_+                        ( \i (x :: Int) -> do+                            h <- VUM.unsafeRead mv i+                            VUM.unsafeWrite mv i (hashWithSalt h x)+                        )+                        v+                Nothing ->+                    case testEquality (typeRep @a) (typeRep @Double) of+                        Just Refl ->+                            VU.imapM_+                                ( \i (d :: Double) -> do+                                    h <- VUM.unsafeRead mv i+                                    VUM.unsafeWrite mv i (hashWithSalt h (doubleToInt d))+                                )+                                v+                        Nothing ->+                            case sIntegral @a of+                                STrue ->+                                    VU.imapM_+                                        ( \i d -> do+                                            let x :: Int+                                                x = fromIntegral @a @Int d+                                            h <- VUM.unsafeRead mv i+                                            VUM.unsafeWrite mv i (hashWithSalt h x)+                                        )+                                        v+                                SFalse ->+                                    case sFloating @a of+                                        STrue ->+                                            VU.imapM_+                                                ( \i d -> do+                                                    let x :: Int+                                                        x = doubleToInt (realToFrac d :: Double)+                                                    h <- VUM.unsafeRead mv i+                                                    VUM.unsafeWrite mv i (hashWithSalt h x)+                                                )+                                                v+                                        SFalse ->+                                            VU.imapM_+                                                ( \i d -> do+                                                    let x = hash (show d)+                                                    h <- VUM.unsafeRead mv i+                                                    VUM.unsafeWrite mv i (hashWithSalt h x)+                                                )+                                                v+        BoxedColumn (v :: V.Vector a) ->+            case testEquality (typeRep @a) (typeRep @T.Text) of+                Just Refl ->+                    V.imapM_+                        ( \i (t :: T.Text) -> do+                            h <- VUM.unsafeRead mv i+                            VUM.unsafeWrite mv i (hashWithSalt h t)+                        )+                        v+                Nothing ->+                    V.imapM_+                        ( \i d -> do+                            let x = hash (show d)+                            h <- VUM.unsafeRead mv i+                            VUM.unsafeWrite mv i (hashWithSalt h x)+                        )+                        v         OptionalColumn v ->-            VG.convert-                (V.zipWith (\h d -> hashWithSalt h (hash (show d))) (VG.convert acc) v)+            V.imapM_+                ( \i d -> do+                    let x = hash (show d)+                    h <- VUM.unsafeRead mv i+                    VUM.unsafeWrite mv i (hashWithSalt h x)+                )+                v +    VU.unsafeFreeze mv+  where     doubleToInt :: Double -> Int-    doubleToInt = floor+    doubleToInt = floor . (* 1000)  {- | Aggregate a grouped dataframe using the expressions given. All ungrouped columns will be dropped.
+ src/DataFrame/Synthesis.hs view
@@ -0,0 +1,486 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ExplicitNamespaces #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE UndecidableInstances #-}++module DataFrame.Synthesis where++import qualified DataFrame.Functions as F+import DataFrame.Internal.Column+import DataFrame.Internal.DataFrame (+    DataFrame (..),+    columnAsDoubleVector,+ )+import DataFrame.Internal.Expression (+    Expr (..),+    eSize,+    interpret,+    replaceExpr,+ )+import DataFrame.Internal.Statistics+import qualified DataFrame.Operations.Statistics as Stats+import DataFrame.Operations.Subset (exclude, select)++import Control.Exception (throw)+import Data.Containers.ListUtils+import Data.Function+import qualified Data.List as L+import qualified Data.Map as M+import Data.Maybe (listToMaybe)+import qualified Data.Set as S+import qualified Data.Text as T+import Data.Type.Equality+import qualified Data.Vector.Unboxed as VU+import DataFrame.Functions ((.&&), (.<=), (.>), (.||))+import qualified DataFrame.Operations.Core as D+import qualified DataFrame.Operations.Transformations as D+import Debug.Trace (trace)+import Type.Reflection (typeRep)++generateConditions ::+    TypedColumn Double -> [Expr Bool] -> [Expr Double] -> DataFrame -> [Expr Bool]+generateConditions labels conds ps df =+    let+        newConds =+            [ p .<= q+            | p <- ps+            , q <- ps+            , p /= q+            ]+                ++ [ F.not p+                   | p <- conds+                   ]+        expandedConds =+            conds+                ++ newConds+                ++ [p .&& q | p <- newConds, q <- conds, p /= q]+                ++ [p .|| q | p <- newConds, q <- conds, p /= q]+     in+        pickTopNBool df labels (deduplicate df expandedConds)++generatePrograms ::+    Bool ->+    [Expr Bool] ->+    [Expr Double] ->+    [Expr Double] ->+    [Expr Double] ->+    [Expr Double]+generatePrograms _ _ vars' constants [] = vars' ++ constants+generatePrograms includeConds conds vars constants ps =+    let+        existingPrograms = ps ++ vars ++ constants+     in+        existingPrograms+            ++ [ transform p+               | p <- ps ++ vars+               , transform <-+                    [ sqrt+                    , abs+                    , log . (+ Lit 1)+                    , exp+                    , sin+                    , cos+                    , F.relu+                    , signum+                    ]+               ]+            ++ [ F.pow i p+               | p <- existingPrograms+               , i <- [2 .. 6]+               ]+            ++ [ p + q+               | (i, p) <- zip [0 ..] existingPrograms+               , (j, q) <- zip [0 ..] existingPrograms+               , Prelude.not (isLiteral p && isLiteral q)+               , i >= j+               ]+            ++ ( if includeConds+                    then+                        [ F.min p q+                        | (i, p) <- zip [0 ..] existingPrograms+                        , (j, q) <- zip [0 ..] existingPrograms+                        , Prelude.not (isLiteral p && isLiteral q)+                        , p /= q+                        , i > j+                        ]+                            ++ [ F.max p q+                               | (i, p) <- zip [0 ..] existingPrograms+                               , (j, q) <- zip [0 ..] existingPrograms+                               , Prelude.not (isLiteral p && isLiteral q)+                               , p /= q+                               , i > j+                               ]+                            ++ [ F.ifThenElse cond r s+                               | cond <- conds+                               , r <- existingPrograms+                               , s <- existingPrograms+                               , r /= s+                               ]+                    else []+               )+            ++ [ p - q+               | (i, p) <- zip [0 ..] existingPrograms+               , (j, q) <- zip [0 ..] existingPrograms+               , Prelude.not (isLiteral p && isLiteral q)+               , i /= j+               ]+            ++ [ p * q+               | (i, p) <- zip [0 ..] existingPrograms+               , (j, q) <- zip [0 ..] existingPrograms+               , Prelude.not (isLiteral p && isLiteral q)+               , i >= j+               ]+            ++ [ p / q+               | p <- existingPrograms+               , q <- existingPrograms+               , Prelude.not (isLiteral p && isLiteral q)+               , p /= q+               ]++isLiteral :: Expr a -> Bool+isLiteral (Lit _) = True+isLiteral _ = False++deduplicate ::+    forall a.+    (Columnable a) =>+    DataFrame ->+    [Expr a] ->+    [(Expr a, TypedColumn a)]+deduplicate df = go S.empty . nubOrd . L.sortBy (\e1 e2 -> compare (eSize e1) (eSize e2))+  where+    go _ [] = []+    go seen (x : xs)+        | hasInvalid = go seen xs+        | S.member res seen = go seen xs+        | otherwise = (x, res) : go (S.insert res seen) xs+      where+        res = case interpret @a df x of+            Left e -> throw e+            Right v -> v+        hasInvalid = case res of+            (TColumn (UnboxedColumn (col :: VU.Vector b))) -> case testEquality (typeRep @Double) (typeRep @b) of+                Just Refl -> VU.any (\n -> isNaN n || isInfinite n) col+                Nothing -> False+            _ -> False++-- | Checks if two programs generate the same outputs given all the same inputs.+equivalent :: DataFrame -> Expr Double -> Expr Double -> Bool+equivalent df p1 p2 = case (==) <$> interpret df p1 <*> interpret df p2 of+    Left e -> throw e+    Right v -> v++synthesizeFeatureExpr ::+    -- | Target expression+    T.Text ->+    BeamConfig ->+    DataFrame ->+    Either String (Expr Double)+synthesizeFeatureExpr target cfg df =+    let+        df' = exclude [target] df+        t = case interpret df (Col target) of+            Left e -> throw e+            Right v -> v+     in+        case beamSearch+            df'+            cfg+            t+            (percentiles df')+            []+            [] of+            Nothing -> Left "No programs found"+            Just p -> Right p++f1FromBinary :: VU.Vector Double -> VU.Vector Double -> Maybe Double+f1FromBinary trues preds =+    let (!tp, !fp, !fn) =+            VU.foldl' step (0 :: Int, 0 :: Int, 0 :: Int) $+                VU.zip (VU.map (> 0) preds) (VU.map (> 0) trues)+     in f1FromCounts tp fp fn+  where+    step (!tp, !fp, !fn) (!p, !t) =+        case (p, t) of+            (True, True) -> (tp + 1, fp, fn)+            (True, False) -> (tp, fp + 1, fn)+            (False, True) -> (tp, fp, fn + 1)+            (False, False) -> (tp, fp, fn)++f1FromCounts :: Int -> Int -> Int -> Maybe Double+f1FromCounts tp fp fn =+    let tp' = fromIntegral tp+        fp' = fromIntegral fp+        fn' = fromIntegral fn+        precision = if tp' + fp' == 0 then 0 else tp' / (tp' + fp')+        recall = if tp' + fn' == 0 then 0 else tp' / (tp' + fn')+     in if precision + recall == 0+            then Nothing+            else Just (2 * precision * recall / (precision + recall))++fitClassifier ::+    -- | Target expression+    T.Text ->+    -- | Depth of search (Roughly, how many terms in the final expression)+    Int ->+    -- | Beam size - the number of candidate expressions to consider at a time.+    Int ->+    DataFrame ->+    Either String (Expr Int)+fitClassifier target d b df =+    let+        df' = exclude [target] df+        t = case interpret df (Col target) of+            Left e -> throw e+            Right v -> v+     in+        case beamSearch+            df'+            (BeamConfig d b F1 True)+            t+            (percentiles df' ++ [Lit 1, Lit 0, Lit (-1)])+            []+            [] of+            Nothing -> Left "No programs found"+            Just p -> Right (F.ifThenElse (p .> 0) 1 0)++percentiles :: DataFrame -> [Expr Double]+percentiles df =+    let+        doubleColumns = map (either throw id . (`columnAsDoubleVector` df)) (D.columnNames df)+     in+        concatMap+            (\c -> map (Lit . roundTo2SigDigits . (`percentile'` c)) [1, 25, 75, 99])+            doubleColumns+            ++ map (Lit . roundTo2SigDigits . variance') doubleColumns+            ++ map (Lit . roundTo2SigDigits . sqrt . variance') doubleColumns++roundToSigDigits :: Int -> Double -> Double+roundToSigDigits n x+    | x == 0 = 0+    | otherwise =+        let magnitude = floor (logBase 10 (abs x))+            scale = 10 ** fromIntegral (n - 1 - magnitude)+         in fromIntegral (round (x * scale)) / scale++roundTo2SigDigits :: Double -> Double+roundTo2SigDigits = roundToSigDigits 2++fitRegression ::+    -- | Target expression+    T.Text ->+    -- | Depth of search (Roughly, how many terms in the final expression)+    Int ->+    -- | Beam size - the number of candidate expressions to consider at a time.+    Int ->+    DataFrame ->+    Either String (Expr Double)+fitRegression target d b df =+    let+        df' = exclude [target] df+        targetMean = Stats.mean (Col @Double target) df+        t = case interpret df (Col target) of+            Left e -> throw e+            Right v -> v+     in+        case beamSearch+            df'+            ( BeamConfig+                d+                b+                MutualInformation+                False+            )+            t+            (percentiles df')+            []+            [] of+            Nothing -> Left "No programs found"+            Just p ->+                trace (show p) $+                    let+                     in case beamSearch+                            ( D.derive "_generated_regression_feature_" p df+                                & select ["_generated_regression_feature_"]+                            )+                            (BeamConfig d b MeanSquaredError False)+                            t+                            (percentiles df' ++ [Lit targetMean, Lit 10])+                            []+                            [Col "_generated_regression_feature_"] of+                            Nothing -> Left "Could not find coefficients"+                            Just p' -> Right (replaceExpr p (Col @Double "_generated_regression_feature_") p')++data LossFunction+    = PearsonCorrelation+    | MutualInformation+    | MeanSquaredError+    | F1++getLossFunction ::+    LossFunction -> (VU.Vector Double -> VU.Vector Double -> Maybe Double)+getLossFunction f = case f of+    MutualInformation ->+        ( \l r ->+            mutualInformationBinned+                (Prelude.max 10 (ceiling (sqrt (fromIntegral (VU.length l)))))+                l+                r+        )+    PearsonCorrelation -> (\l r -> (^ 2) <$> correlation' l r)+    MeanSquaredError -> (\l r -> fmap negate (meanSquaredError l r))+    F1 -> f1FromBinary++data BeamConfig = BeamConfig+    { searchDepth :: Int+    , beamLength :: Int+    , lossFunction :: LossFunction+    , includeConditionals :: Bool+    }++defaultBeamConfig :: BeamConfig+defaultBeamConfig = BeamConfig 2 100 PearsonCorrelation False++beamSearch ::+    DataFrame ->+    -- | Parameters of the beam search.+    BeamConfig ->+    -- | Examples+    TypedColumn Double ->+    -- | Constants+    [Expr Double] ->+    -- | Conditions+    [Expr Bool] ->+    -- | Programs+    [Expr Double] ->+    Maybe (Expr Double)+beamSearch df cfg outputs constants conds programs+    | searchDepth cfg == 0 = case ps of+        [] -> Nothing+        (x : _) -> Just x+    | otherwise =+        beamSearch+            df+            (cfg{searchDepth = searchDepth cfg - 1})+            outputs+            constants+            conditions+            (generatePrograms (includeConditionals cfg) conditions vars constants ps)+  where+    vars = map Col names+    conditions = generateConditions outputs conds (vars ++ constants ++ ps) df+    ps = pickTopN df outputs cfg $ deduplicate df programs+    names = (map fst . L.sortBy (compare `on` snd) . M.toList . columnIndices) df++pickTopN ::+    DataFrame ->+    TypedColumn Double ->+    BeamConfig ->+    [(Expr Double, TypedColumn a)] ->+    [Expr Double]+pickTopN _ _ _ [] = []+pickTopN df (TColumn col) cfg ps =+    let+        l = case toVector @Double @VU.Vector col of+            Left e -> throw e+            Right v -> v+        ordered =+            Prelude.take+                (beamLength cfg)+                ( map fst $+                    L.sortBy+                        ( \(_, c2) (_, c1) ->+                            if maybe False isInfinite c1+                                || maybe False isInfinite c2+                                || maybe False isNaN c1+                                || maybe False isNaN c2+                                then LT+                                else compare c1 c2+                        )+                        ( map+                            (\(e, res) -> (e, getLossFunction (lossFunction cfg) l (asDoubleVector res)))+                            ps+                        )+                )+        asDoubleVector c =+            let+                (TColumn col') = c+             in+                case toVector @Double @VU.Vector col' of+                    Left e -> throw e+                    Right v -> VU.convert v+        interpretDoubleVector e =+            let+                (TColumn col') = case interpret df e of+                    Left e -> throw e+                    Right v -> v+             in+                case toVector @Double @VU.Vector col' of+                    Left e -> throw e+                    Right v -> VU.convert v+     in+        trace+            ( "Best loss: "+                ++ show+                    ( getLossFunction (lossFunction cfg) l . interpretDoubleVector+                        <$> listToMaybe ordered+                    )+                ++ " "+                ++ (if null ordered then "empty" else show (listToMaybe ordered))+            )+            ordered++pickTopNBool ::+    DataFrame ->+    TypedColumn Double ->+    [(Expr Bool, TypedColumn Bool)] ->+    [Expr Bool]+pickTopNBool _ _ [] = []+pickTopNBool df (TColumn col) ps =+    let+        l = case toVector @Double @VU.Vector col of+            Left e -> throw e+            Right v -> v+        ordered =+            Prelude.take+                10+                ( map fst $+                    L.sortBy+                        ( \(_, c2) (_, c1) ->+                            if maybe False isInfinite c1+                                || maybe False isInfinite c2+                                || maybe False isNaN c1+                                || maybe False isNaN c2+                                then LT+                                else compare c1 c2+                        )+                        ( map+                            (\(e, res) -> (e, getLossFunction MutualInformation l (asDoubleVector res)))+                            ps+                        )+                )+        asDoubleVector c =+            let+                (TColumn col') = c+             in+                case toVector @Bool @VU.Vector col' of+                    Left e -> throw e+                    Right v -> VU.map (fromIntegral @Int @Double . fromEnum) v+     in+        ordered++satisfiesExamples :: DataFrame -> TypedColumn Double -> Expr Double -> Bool+satisfiesExamples df col expr =+    let+        result = case interpret df expr of+            Left e -> throw e+            Right v -> v+     in+        result == col
tests/Functions.hs view
@@ -5,9 +5,9 @@  import DataFrame.Functions (     col,-    generatePrograms,     sanitize,  )+import DataFrame.Synthesis (generatePrograms) import Test.HUnit  -- Test cases for the sanitize function