packages feed

dataframe 0.3.3.5 → 0.3.3.6

raw patch · 11 files changed

+372/−279 lines, 11 filesdep ~parallelPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: parallel

API changes (from Hackage documentation)

- 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, 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.Operations.Core: [partiallyParsedValues] :: ColumnInfo -> !Int
- DataFrame.Operations.Core: [uniqueValues] :: ColumnInfo -> !Int
+ DataFrame.Functions: (.&&) :: Expr Bool -> Expr Bool -> 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, 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: collect :: Columnable a => Expr a -> Expr [a]
+ DataFrame.Operations.Typing: countNonEmpty :: Vector (Maybe a) -> Int
+ DataFrame.Operations.Typing: equalNonEmpty :: Vector (Maybe a) -> Vector (Maybe b) -> Bool
- DataFrame.Operations.Core: ColumnInfo :: !Text -> !Int -> !Int -> !Int -> !Int -> !Text -> ColumnInfo
+ DataFrame.Operations.Core: ColumnInfo :: !Text -> !Int -> !Int -> !Text -> ColumnInfo

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for dataframe +## 0.3.3.6+* Fix bug where doubles were parsing as ints+* Fix bugs where optionals were left in boxed column (instead of optionals)+* Change syntax for conditional operations so it doesn't clash with regular operations.+ ## 0.3.3.5 * Fix parsing logic for doubles. Entire parsing logic is still a work in progress. * Speed up index selection by using backPermute.
README.md view
@@ -31,4 +31,4 @@ * Designed for interactivity: expressive syntax, helpful error messages, and sensible defaults. * Works seamlessly in both command-line and notebook environments—great for exploration and scripting alike. -For an installation guide and tutorials checkout the [project documentation](https://dataframe.readthedocs.io/) and for an API reference checkout the [hackage documentation](https://hackage-content.haskell.org/package/dataframe-0.3.3.5/docs/DataFrame.html).+For an installation guide and tutorials checkout the [project documentation](https://dataframe.readthedocs.io/) and for an API reference checkout the [hackage documentation](https://hackage-content.haskell.org/package/dataframe-0.3.3.6/docs/DataFrame.html).
dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               dataframe-version:            0.3.3.5+version:            0.3.3.6  synopsis: A fast, safe, and intuitive DataFrame library. @@ -101,13 +101,12 @@                       vector-algorithms ^>= 0.9,                       zstd >= 0.1.2.0 && < 0.2,                       mmap >= 0.5.8 && <= 0.5.9,-                      parallel >= 3.2.2.0+                      parallel >= 3.2.2.0 && < 5     hs-source-dirs:   src     c-sources:        cbits/process_csv.c     include-dirs:     cbits     includes:         process_csv.h     install-includes: process_csv.h-    cc-options:       -O3     default-language: Haskell2010  executable dataframe-benchmark-example@@ -146,7 +145,6 @@                    dataframe ^>= 0.3     default-language: Haskell2010     ghc-options:-      -O2       -threaded       -rtsopts       -with-rtsopts=-N
src/DataFrame.hs view
@@ -53,25 +53,25 @@ -- 1) Load data ghci> df0 <- D.readCsv "data/housing.csv" ghci> D.describeColumns df0----------------------------------------------------------------------------------------------------------------------index |    Column Name     | # Non-null Values | # Null Values | # Partially parsed | # Unique Values |     Type-------|--------------------|-------------------|---------------|--------------------|-----------------|-------------- Int  |        Text        |        Int        |      Int      |        Int         |       Int       |     Text-------|--------------------|-------------------|---------------|--------------------|-----------------|--------------0     | ocean_proximity    | 20640             | 0             | 0                  | 5               | Text-1     | median_house_value | 20640             | 0             | 0                  | 3842            | Double-2     | median_income      | 20640             | 0             | 0                  | 12928           | Double-3     | households         | 20640             | 0             | 0                  | 1815            | Double-4     | population         | 20640             | 0             | 0                  | 3888            | Double-5     | total_bedrooms     | 20640             | 0             | 0                  | 1924            | Maybe Double-6     | total_rooms        | 20640             | 0             | 0                  | 5926            | Double-7     | housing_median_age | 20640             | 0             | 0                  | 52              | Double-8     | latitude           | 20640             | 0             | 0                  | 862             | Double-9     | longitude          | 20640             | 0             | 0                  | 844             | Double+-------------------------------------------------------------------------------------------------------------+    Column Name     | # Non-null Values | # Null Values | # Partially parsed | # Unique Values |     Type+--------------------|-------------------|---------------|--------------------|-----------------|-------------+        Text        |        Int        |      Int      |        Int         |       Int       |     Text+--------------------|-------------------|---------------|--------------------|-----------------|-------------+ ocean_proximity    | 20640             | 0             | 0                  | 5               | Text+ median_house_value | 20640             | 0             | 0                  | 3842            | Double+ median_income      | 20640             | 0             | 0                  | 12928           | Double+ households         | 20640             | 0             | 0                  | 1815            | Double+ population         | 20640             | 0             | 0                  | 3888            | Double+ total_bedrooms     | 20640             | 0             | 0                  | 1924            | Maybe Double+ total_rooms        | 20640             | 0             | 0                  | 5926            | Double+ housing_median_age | 20640             | 0             | 0                  | 52              | Double+ latitude           | 20640             | 0             | 0                  | 862             | Double+ longitude          | 20640             | 0             | 0                  | 844             | Double  -- 2) Project & filter ghci> :exposeColumn df-ghci> df1 = D.filterWhere (ocean_proximity F.== F.lit \"ISLAND\") df0 D.|> D.select [F.name median_house_value, F.name median_income, F.name ocean_proximity]+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 --    (col types are explicit via TypeApplications)@@ -84,16 +84,16 @@              [ F.maximum median_house_value \`F.as\` "max_house_value"]              grouped ghci> D.take 5 summary-------------------------------------------index | ocean_proximity | max_house_value-------|-----------------|----------------- Int  |      Text       |     Double-------|-----------------|-----------------0     | <1H OCEAN       | 500001.0-1     | INLAND          | 500001.0-2     | ISLAND          | 450000.0-3     | NEAR BAY        | 500001.0-4     | NEAR OCEAN      | 500001.0+----------------------------------+ ocean_proximity | max_house_value+-----------------|----------------+      Text       |     Double+-----------------|----------------+ <1H OCEAN       | 500001.0+ INLAND          | 500001.0+ ISLAND          | 450000.0+ NEAR BAY        | 500001.0+ NEAR OCEAN      | 500001.0 @  == Simple operations (cheat sheet)@@ -148,6 +148,7 @@ @ (+), (-), (*), (/), abs, log, exp, round (F.eq), (F.gt), (F.geq), (F.lt), (F.leq)+(.==), (.>), (.>=), (.<), (.<=) @  Aggregations (for D.'aggregate'):@@ -182,7 +183,7 @@ payment :: Expr Text  -- You can use them directly:-ghci> D.derive "fare_with_tip" (fare * F.lit 1.2)+ghci> D.derive "fare_with_tip" (fare * 1.2) @  Notes:
src/DataFrame/Functions.hs view
@@ -88,48 +88,57 @@ mod :: (Integral a, Columnable a) => Expr a -> Expr a -> Expr a mod = BinaryOp "mod" Prelude.mod -(==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool-(==) = BinaryOp "eq" (Prelude.==)+(.==) :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool+(.==) = BinaryOp "eq" (==)  eq :: (Columnable a, Eq a) => Expr a -> Expr a -> Expr Bool-eq = BinaryOp "eq" (Prelude.==)+eq = BinaryOp "eq" (==) -(<) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-(<) = BinaryOp "lt" (Prelude.<)+(.<) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool+(.<) = BinaryOp "lt" (<)  lt :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-lt = BinaryOp "lt" (Prelude.<)+lt = BinaryOp "lt" (<) -(>) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-(>) = BinaryOp "gt" (Prelude.>)+(.>) :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool+(.>) = BinaryOp "gt" (>)  gt :: (Columnable a, Ord a) => Expr a -> Expr a -> Expr Bool-gt = BinaryOp "gt" (Prelude.>)+gt = BinaryOp "gt" (>) -(<=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool-(<=) = BinaryOp "leq" (Prelude.<=)+(.<=) :: (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 = BinaryOp "leq" (Prelude.<=)+leq = BinaryOp "leq" (<=) -(>=) :: (Columnable a, Ord a, Eq a) => Expr a -> Expr a -> Expr Bool-(>=) = BinaryOp "geq" (Prelude.>=)+(.>=) :: (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" (Prelude.>=)+geq = BinaryOp "geq" (>=)  and :: Expr Bool -> Expr Bool -> Expr Bool and = BinaryOp "and" (&&) +(.&&) :: Expr Bool -> Expr Bool -> Expr Bool+(.&&) = BinaryOp "and" (&&)+ or :: Expr Bool -> Expr Bool -> Expr Bool or = BinaryOp "or" (||) +(.||) :: Expr Bool -> Expr Bool -> Expr Bool+(.||) = BinaryOp "or" (||)+ not :: Expr Bool -> Expr Bool not = UnaryOp "not" Prelude.not  count :: (Columnable a) => Expr a -> Expr Int count expr = AggFold expr "count" 0 (\acc _ -> acc + 1) +collect :: (Columnable a) => Expr a -> Expr [a]+collect expr = AggFold expr "collect" [] (flip (:))+ mode :: (Columnable a, Eq a) => Expr a -> Expr a mode expr =     AggVector@@ -196,7 +205,7 @@ generateConditions labels conds ps df =     let         newConds =-            [ p DataFrame.Functions.<= q+            [ p .<= q             | p <- ps             , q <- ps             , p /= q@@ -207,8 +216,8 @@         expandedConds =             conds                 ++ newConds-                ++ [p `DataFrame.Functions.and` q | p <- newConds, q <- conds, p /= q]-                ++ [p `DataFrame.Functions.or` q | p <- newConds, q <- conds, p /= q]+                ++ [p .&& q | p <- newConds, q <- conds, p /= q]+                ++ [p .|| q | p <- newConds, q <- conds, p /= q]      in         pickTopNBool df labels (deduplicate df expandedConds) @@ -241,21 +250,21 @@                    | (i, p) <- zip [0 ..] vars                    , (j, q) <- zip [0 ..] vars                    , Prelude.not (isLiteral p && isLiteral q)-                   , i Prelude.> j+                   , i > j                    ]                 ++ [ DataFrame.Functions.min p q                    | (i, p) <- zip [0 ..] vars                    , (j, q) <- zip [0 ..] vars                    , p /= q                    , Prelude.not (isLiteral p && isLiteral q)-                   , i Prelude.> j+                   , i > j                    ]                 ++ [ DataFrame.Functions.max p q                    | (i, p) <- zip [0 ..] vars                    , (j, q) <- zip [0 ..] vars                    , p /= q                    , Prelude.not (isLiteral p && isLiteral q)-                   , i Prelude.> j+                   , i > j                    ]                 ++ [ ifThenElse cond r s                    | cond <- conds@@ -273,7 +282,7 @@                    | (i, p) <- zip [0 ..] vars                    , (j, q) <- zip [0 ..] vars                    , Prelude.not (isLiteral p && isLiteral q)-                   , i Prelude.>= j+                   , i >= j                    ]                 ++ [ p / q                    | (i, p) <- zip [0 ..] vars@@ -307,21 +316,21 @@                | (i, p) <- zip [0 ..] existingPrograms                , (j, q) <- zip [0 ..] existingPrograms                , Prelude.not (isLiteral p && isLiteral q)-               , i Prelude.>= j+               , i >= j                ]             ++ [ DataFrame.Functions.min p q                | (i, p) <- zip [0 ..] existingPrograms                , (j, q) <- zip [0 ..] existingPrograms                , Prelude.not (isLiteral p && isLiteral q)                , p /= q-               , i Prelude.> j+               , 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 Prelude.> j+               , i > j                ]             ++ [ ifThenElse cond r s                | cond <- conds@@ -339,7 +348,7 @@                | (i, p) <- zip [0 ..] existingPrograms                , (j, q) <- zip [0 ..] existingPrograms                , Prelude.not (isLiteral p && isLiteral q)-               , i Prelude.>= j+               , i >= j                ]             ++ [ p / q                | p <- existingPrograms@@ -377,7 +386,7 @@  -- | 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 (Prelude.==) <$> interpret df p1 <*> interpret df p2 of+equivalent df p1 p2 = case (==) <$> interpret df p1 <*> interpret df p2 of     Left e -> throw e     Right v -> v @@ -408,7 +417,7 @@ f1FromBinary trues preds =     let (!tp, !fp, !fn) =             VU.foldl' step (0 :: Int, 0 :: Int, 0 :: Int) $-                VU.zip (VU.map (Prelude.> 0) preds) (VU.map (Prelude.> 0) trues)+                VU.zip (VU.map (> 0) preds) (VU.map (> 0) trues)      in f1FromCounts tp fp fn   where     step (!tp, !fp, !fn) (!p, !t) =@@ -423,9 +432,9 @@     let tp' = fromIntegral tp         fp' = fromIntegral fp         fn' = fromIntegral fn-        precision = if tp' + fp' Prelude.== 0 then 0 else tp' / (tp' + fp')-        recall = if tp' + fn' Prelude.== 0 then 0 else tp' / (tp' + fn')-     in if precision + recall Prelude.== 0+        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)) @@ -453,7 +462,7 @@             []             [] of             Nothing -> Left "No programs found"-            Just p -> Right (ifThenElse (p DataFrame.Functions.> 0) 1 0)+            Just p -> Right (ifThenElse (p .> 0) 1 0)  percentiles :: DataFrame -> [Expr Double] percentiles df =@@ -551,7 +560,7 @@     [Expr Double] ->     Maybe (Expr Double) beamSearch df cfg outputs constants conds programs-    | searchDepth cfg Prelude.== 0 = case ps of+    | searchDepth cfg == 0 = case ps of         [] -> Nothing         (x : _) -> Just x     | otherwise =@@ -672,7 +681,7 @@             Left e -> throw e             Right v -> v      in-        result Prelude.== col+        result == col  -- See Section 2.4 of the Haskell Report https://www.haskell.org/definition/haskell2010.pdf isReservedId :: T.Text -> Bool
src/DataFrame/Internal/DataFrame.hs view
@@ -70,19 +70,21 @@     show :: DataFrame -> String     show d =         let+            rows = 20             (r, c) = dataframeDimensions d             d' =                 d-                    { columns = V.map (takeColumn 10) (columns d)-                    , dataframeDimensions = (min 10 r, c)+                    { columns = V.map (takeColumn rows) (columns d)+                    , dataframeDimensions = (min rows r, c)                     }+            truncationInfo =+                "\n"+                    ++ "Showing "+                    ++ show (min rows r)+                    ++ " rows out of "+                    ++ show r          in-            T.unpack (asText d' False)-                ++ "\n"-                ++ "Showing "-                ++ show (min 10 r)-                ++ " rows out of "-                ++ show r+            T.unpack (asText d' False) ++ (if r > rows then truncationInfo else "")  -- | For showing the dataframe as markdown in notebooks. toMarkdownTable :: DataFrame -> T.Text
src/DataFrame/Internal/Parsing.hs view
@@ -13,7 +13,9 @@ import Text.Read (readMaybe)  isNullish :: T.Text -> Bool-isNullish s = s `S.member` S.fromList ["Nothing", "NULL", "", " ", "nan", "null"]+isNullish s =+    s+        `S.member` S.fromList ["Nothing", "NULL", "", " ", "nan", "null", "N/A", "NaN", "NAN"]  readValue :: (HasCallStack, Read a) => T.Text -> a readValue s = case readMaybe (T.unpack s) of
src/DataFrame/Operations/Core.hs view
@@ -11,7 +11,6 @@ import qualified Data.List as L import qualified Data.Map as M import qualified Data.Map.Strict as MS-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@@ -75,21 +74,21 @@  ghci> D.insertVector "numbers" (V.fromList [1..10]) D.empty -----------------index | numbers-------|--------- Int  |   Int-------|---------0     | 1-1     | 2-2     | 3-3     | 4-4     | 5-5     | 6-6     | 7-7     | 8-8     | 9-9     | 10+--------+ numbers+--------+   Int+--------+ 1+ 2+ 3+ 4+ 5+ 6+ 7+ 8+ 9+ 10  @ -}@@ -151,21 +150,21 @@ @ ghci> D.insertColumn "numbers" (D.fromList [1..10]) D.empty -----------------index | numbers-------|--------- Int  |   Int-------|---------0     | 1-1     | 2-2     | 3-3     | 4-4     | 5-5     | 6-6     | 7-7     | 8-8     | 9-9     | 10+--------+ numbers+--------+   Int+--------+ 1+ 2+ 3+ 4+ 5+ 6+ 7+ 8+ 9+ 10  @ -}@@ -204,21 +203,21 @@  ghci> D.cloneColumn "numbers" "others" df --------------------------index | numbers | others-------|---------|-------- Int  |   Int   |  Int-------|---------|--------0     | 1       | 1-1     | 2       | 2-2     | 3       | 3-3     | 4       | 4-4     | 5       | 5-5     | 6       | 6-6     | 7       | 7-7     | 8       | 8-8     | 9       | 9-9     | 10      | 10+-----------------+ numbers | others+---------|-------+   Int   |  Int+---------|-------+ 1       | 1+ 2       | 2+ 3       | 3+ 4       | 4+ 5       | 5+ 6       | 6+ 7       | 7+ 8       | 8+ 9       | 9+ 10      | 10  @ -}@@ -241,21 +240,21 @@  ghci> D.rename "numbers" "others" df ----------------index | others-------|-------- Int  |  Int-------|--------0     | 1-1     | 2-2     | 3-3     | 4-4     | 5-5     | 6-6     | 7-7     | 8-8     | 9-9     | 10+-------+ others+-------+  Int+-------+ 1+ 2+ 3+ 4+ 5+ 6+ 7+ 8+ 9+ 10  @ -}@@ -272,39 +271,39 @@  ghci> df --------------------------index | numbers | others-------|---------|-------- Int  |   Int   |  Int-------|---------|--------0     | 1       | 11-1     | 2       | 12-2     | 3       | 13-3     | 4       | 14-4     | 5       | 15-5     | 6       | 16-6     | 7       | 17-7     | 8       | 18-8     | 9       | 19-9     | 10      | 20+-----------------+ numbers | others+---------|-------+   Int   |  Int+---------|-------+ 1       | 11+ 2       | 12+ 3       | 13+ 4       | 14+ 5       | 15+ 6       | 16+ 7       | 17+ 8       | 18+ 9       | 19+ 10      | 20  ghci> D.renameMany [("numbers", "first_10"), ("others", "next_10")] df ----------------------------index | first_10 | next_10-------|----------|--------- Int  |   Int    |   Int-------|----------|---------0     | 1        | 11-1     | 2        | 12-2     | 3        | 13-3     | 4        | 14-4     | 5        | 15-5     | 6        | 16-6     | 7        | 17-7     | 8        | 18-8     | 9        | 19-9     | 10       | 20+-------------------+ first_10 | next_10+----------|--------+   Int    |   Int+----------|--------+ 1        | 11+ 2        | 12+ 3        | 13+ 4        | 14+ 5        | 15+ 6        | 16+ 7        | 17+ 8        | 18+ 9        | 19+ 10       | 20  @ -}@@ -325,8 +324,6 @@     { nameOfColumn :: !T.Text     , nonNullValues :: !Int     , nullValues :: !Int-    , partiallyParsedValues :: !Int-    , uniqueValues :: !Int     , typeOfColumn :: !T.Text     } @@ -340,13 +337,13 @@  ghci> D.describeColumns df -------------------------------------------------------------------------------------------------------index | Column Name | # Non-null Values | # Null Values | # Partially parsed | # Unique Values | Type-------|-------------|-------------------|---------------|--------------------|-----------------|------ Int  |    Text     |        Int        |      Int      |        Int         |       Int       | Text-------|-------------|-------------------|---------------|--------------------|-----------------|------0     | others      | 10                | 0             | 0                  | 10              | Int-1     | numbers     | 10                | 0             | 0                  | 10              | Int+--------------------------------------------------------+ Column Name | # Non-null Values | # Null Values | Type+-------------|-------------------|---------------|-----+    Text     |        Int        |      Int      | Text+-------------|-------------------|---------------|-----+ others      | 10                | 0             | Int+ numbers     | 10                | 0             | Int  @ -}@@ -356,8 +353,6 @@         & insertColumn "Column Name" (fromList (map nameOfColumn infos))         & insertColumn "# Non-null Values" (fromList (map nonNullValues infos))         & insertColumn "# Null Values" (fromList (map nullValues infos))-        & insertColumn "# Partially parsed" (fromList (map partiallyParsedValues infos))-        & insertColumn "# Unique Values" (fromList (map uniqueValues infos))         & insertColumn "Type" (fromList (map typeOfColumn infos))   where     infos =@@ -369,9 +364,7 @@         let             cname = columnName i             countNulls = nulls col-            countPartial = partiallyParsed col             columnType = T.pack $ show $ typeRep @a-            unique = S.size $ VG.foldr S.insert S.empty c          in             if isNothing cname                 then acc@@ -380,16 +373,12 @@                         (fromMaybe "" cname)                         (columnLength col - countNulls)                         countNulls-                        countPartial-                        unique                         columnType                         : acc     go acc i col@(BoxedColumn (c :: V.Vector a)) =         let             cname = columnName i-            countPartial = partiallyParsed col             columnType = T.pack $ show $ typeRep @a-            unique = S.size $ VG.foldr S.insert S.empty c          in             if isNothing cname                 then acc@@ -398,22 +387,19 @@                         (fromMaybe "" cname)                         (columnLength col)                         0-                        countPartial-                        unique                         columnType                         : acc     go acc i col@(UnboxedColumn c) =         let             cname = columnName i             columnType = T.pack $ columnTypeString col-            unique = S.size $ VG.foldr S.insert S.empty c          in             -- Unboxed columns cannot have nulls since Maybe             -- is not an instance of Unbox a             if isNothing cname                 then acc                 else-                    ColumnInfo (fromMaybe "" cname) (columnLength col) 0 0 unique columnType : acc+                    ColumnInfo (fromMaybe "" cname) (columnLength col) 0 columnType : acc  nulls :: Column -> Int nulls (OptionalColumn xs) = VG.length $ VG.filter isNothing xs@@ -445,21 +431,21 @@  ghci> df --------------------------index | numbers | others-------|---------|-------- Int  |   Int   |  Int-------|---------|--------0     | 1       | 11-1     | 2       | 12-2     | 3       | 13-3     | 4       | 14-4     | 5       | 15-5     | 6       | 16-6     | 7       | 17-7     | 8       | 18-8     | 9       | 19-9     | 10      | 20+-----------------+ numbers | others+---------|-------+   Int   |  Int+---------|-------+ 1       | 11+ 2       | 12+ 3       | 13+ 4       | 14+ 5       | 15+ 6       | 16+ 7       | 17+ 8       | 18+ 9       | 19+ 10      | 20  @ -}@@ -477,20 +463,20 @@ ghci> df  ------------------index |  0  |  1-------|-----|----- Int  | Int | Int-------|-----|-----0     | 1   | 11-1     | 2   | 12-2     | 3   | 13-3     | 4   | 14-4     | 5   | 15-5     | 6   | 16-6     | 7   | 17-7     | 8   | 18-8     | 9   | 19-9     | 10  | 20+  0  |  1+-----|----+ Int | Int+-----|----+ 1   | 11+ 2   | 12+ 3   | 13+ 4   | 14+ 5   | 15+ 6   | 16+ 7   | 17+ 8   | 18+ 9   | 19+ 10  | 20  @ -}@@ -505,14 +491,14 @@  ghci> df -------------------index |  A  |  B-------|-----|----- Int  | Int | Int-------|-----|-----0     | 1   | 11-1     | 2   | 12-2     | 3   | 13+----------+  A  |  B+-----|----+ Int | Int+-----|----+ 1   | 11+ 2   | 12+ 3   | 13  @ -}@@ -597,21 +583,21 @@ @ ghci> D.fold (const id) [1..5] df -------------------index |  0  |  1-------|-----|----- Int  | Int | Int-------|-----|-----0     | 1   | 11-1     | 2   | 12-2     | 3   | 13-3     | 4   | 14-4     | 5   | 15-5     | 6   | 16-6     | 7   | 17-7     | 8   | 18-8     | 9   | 19-9     | 10  | 20+----------+  0  |  1+-----|----+ Int | Int+-----|----+ 1   | 11+ 2   | 12+ 3   | 13+ 4   | 14+ 5   | 15+ 6   | 16+ 7   | 17+ 8   | 18+ 9   | 19+ 10  | 20  @ -}
src/DataFrame/Operations/Statistics.hs view
@@ -41,13 +41,13 @@  ghci> D.frequencies "ocean_proximity" df ------------------------------------------------------------------------------index |   Statistic    | <1H OCEAN | INLAND | ISLAND | NEAR BAY | NEAR OCEAN-------|----------------|-----------|--------|--------|----------|------------ Int  |      Text      |    Any    |  Any   |  Any   |   Any    |    Any-------|----------------|-----------|--------|--------|----------|------------0     | Count          | 9136      | 6551   | 5      | 2290     | 2658-1     | Percentage (%) | 44.26%    | 31.74% | 0.02%  | 11.09%   | 12.88%+---------------------------------------------------------------------+   Statistic    | <1H OCEAN | INLAND | ISLAND | NEAR BAY | NEAR OCEAN+----------------|-----------|--------|--------|----------|-----------+      Text      |    Any    |  Any   |  Any   |   Any    |    Any+----------------|-----------|--------|--------|----------|-----------+ Count          | 9136      | 6551   | 5      | 2290     | 2658+ Percentage (%) | 44.26%    | 31.74% | 0.02%  | 11.09%   | 12.88% @ -} frequencies :: T.Text -> DataFrame -> DataFrame
src/DataFrame/Operations/Typing.hs view
@@ -37,39 +37,31 @@         Just Refl -> parseFromExamples n dateFormat safeRead (V.map (fromMaybe "") c) parseDefault _ _ _ column = column -emptyToNothing :: T.Text -> Maybe T.Text-emptyToNothing v = if isNullish v then Nothing else Just v--parseTimeOpt :: String -> T.Text -> Maybe Day-parseTimeOpt dateFormat s =-    parseTimeM {- Accept leading/trailing whitespace -}-        True-        defaultTimeLocale-        dateFormat-        (T.unpack s)--unsafeParseTime :: String -> T.Text -> Day-unsafeParseTime dateFormat s =-    parseTimeOrError {- Accept leading/trailing whitespace -}-        True-        defaultTimeLocale-        dateFormat-        (T.unpack s)- parseFromExamples :: Int -> String -> Bool -> V.Vector T.Text -> Column parseFromExamples n dateFormat safeRead c-    | V.all isJust (V.map readInt examples) =-        let safeVector = V.map ((=<<) readInt . emptyToNothing) c-            hasNulls = V.elem Nothing safeVector-         in if safeRead && hasNulls-                then BoxedColumn safeVector-                else UnboxedColumn (VU.generate (V.length c) (fromMaybe 0 . (safeVector V.!)))+    | V.any isJust (V.map readInt examples)+        && equalNonEmpty (V.map readInt examples) (V.map readDouble examples) =+        let safeVector = V.map readIntEither c+            notAllParsed = V.any isLeft safeVector+         in if safeRead && notAllParsed+                then+                    if V.all (either isNullish (const False)) (V.filter isLeft safeVector)+                        then OptionalColumn (V.map (either (const Nothing) Just) safeVector)+                        else BoxedColumn safeVector+                else+                    UnboxedColumn+                        (VU.generate (V.length c) (either undefined id . (safeVector V.!)))     | V.any isJust (V.map readDouble examples) =-        let safeVector = V.map ((=<<) readDouble . emptyToNothing) c-            hasNulls = V.elem Nothing safeVector-         in if safeRead && hasNulls-                then BoxedColumn safeVector-                else UnboxedColumn (VU.generate (V.length c) (fromMaybe 0 . (safeVector V.!)))+        let safeVector = V.map readDoubleEither c+            notAllParsed = V.any isLeft safeVector+         in if safeRead && notAllParsed+                then+                    if V.all (either isNullish (const False)) (V.filter isLeft safeVector)+                        then OptionalColumn (V.map (either (const Nothing) Just) safeVector)+                        else BoxedColumn safeVector+                else+                    UnboxedColumn+                        (VU.generate (V.length c) (either undefined id . (safeVector V.!)))     | V.any isJust (V.map (parseTimeOpt dateFormat) examples) =         let             -- failed parse should be Either, nullish should be Maybe@@ -86,7 +78,7 @@             if safeRead                 then                     if onlyNulls-                        then BoxedColumn (V.map toMaybe safeVector)+                        then OptionalColumn (V.map toMaybe safeVector)                         else                             if V.any isLeft safeVector                                 then BoxedColumn safeVector@@ -97,6 +89,31 @@             safeVector = V.map emptyToNothing c             hasNulls = V.any isNullish c          in-            if safeRead && hasNulls then BoxedColumn safeVector else BoxedColumn c+            if safeRead && hasNulls then OptionalColumn safeVector else BoxedColumn c   where     examples = V.take n c++emptyToNothing :: T.Text -> Maybe T.Text+emptyToNothing v = if isNullish v then Nothing else Just v++parseTimeOpt :: String -> T.Text -> Maybe Day+parseTimeOpt dateFormat s =+    parseTimeM {- Accept leading/trailing whitespace -}+        True+        defaultTimeLocale+        dateFormat+        (T.unpack s)++unsafeParseTime :: String -> T.Text -> Day+unsafeParseTime dateFormat s =+    parseTimeOrError {- Accept leading/trailing whitespace -}+        True+        defaultTimeLocale+        dateFormat+        (T.unpack s)++countNonEmpty :: V.Vector (Maybe a) -> Int+countNonEmpty xs = V.length (V.filter isJust xs)++equalNonEmpty :: V.Vector (Maybe a) -> V.Vector (Maybe b) -> Bool+equalNonEmpty xs ys = countNonEmpty xs == countNonEmpty ys
tests/Main.hs view
@@ -5,6 +5,7 @@  import qualified Data.Text as T import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as VU import qualified DataFrame as D import qualified DataFrame.Internal.Column as DI import qualified DataFrame.Operations.Typing as D@@ -66,6 +67,74 @@      in         TestCase (assertEqual "Correctly parses gregorian date" expected actual) +parseInt :: Test+parseInt =+    let+        expected =+            DI.UnboxedColumn+                ( VU.fromList+                    [1 :: Int .. 5]+                )+        actual =+            D.parseDefault+                10+                True+                "%Y-%m-%d"+                (DI.fromVector (V.fromList ["1" :: T.Text, "2", "3", "4", "5"]))+     in+        TestCase (assertEqual "Correctly parses integers" expected actual)++parseMaybeInt :: Test+parseMaybeInt =+    let+        expected =+            DI.OptionalColumn+                ( V.fromList+                    [Just (1 :: Int), Nothing, Just 3, Just 4, Just 5]+                )+        actual =+            D.parseDefault+                10+                True+                "%Y-%m-%d"+                (DI.fromVector (V.fromList ["1" :: T.Text, "N/A", "3", "4", "5"]))+     in+        TestCase (assertEqual "Correctly parses optional integers" expected actual)++parseDouble :: Test+parseDouble =+    let+        expected =+            DI.UnboxedColumn+                ( VU.fromList+                    [1 :: Double, 2, 3, 4, 5]+                )+        actual =+            D.parseDefault+                10+                True+                "%Y-%m-%d"+                (DI.fromVector (V.fromList ["1" :: T.Text, "2.0", "3", "4", "5"]))+     in+        TestCase (assertEqual "Correctly parses doubles" expected actual)++parseMaybeDouble :: Test+parseMaybeDouble =+    let+        expected =+            DI.OptionalColumn+                ( V.fromList+                    [Just 1 :: Maybe Double, Nothing, Just 3, Just 4, Just 5]+                )+        actual =+            D.parseDefault+                10+                True+                "%Y-%m-%d"+                (DI.fromVector (V.fromList ["1" :: T.Text, "N/A", "3.0", "4", "5"]))+     in+        TestCase (assertEqual "Correctly parses optional doubles" expected actual)+ incompleteDataParseEither :: Test incompleteDataParseEither =     let@@ -90,7 +159,7 @@ incompleteDataParseMaybe =     let         expected =-            DI.BoxedColumn+            DI.OptionalColumn                 ( V.fromList                     [Just $ fromGregorian 2020 02 14, Nothing, Just $ fromGregorian 2022 02 14]                 )@@ -109,6 +178,10 @@     [ TestLabel "parseDate" parseDate     , TestLabel "incompleteDataParseMaybe" incompleteDataParseMaybe     , TestLabel "incompleteDataParseEither" incompleteDataParseEither+    , TestLabel "parseInt" parseInt+    , TestLabel "parseMaybeInt" parseMaybeInt+    , TestLabel "parseDouble" parseDouble+    , TestLabel "parseMaybeDouble" parseMaybeDouble     ]  tests :: Test