dataframe 0.3.3.6 → 0.3.3.7
raw patch · 31 files changed
+5954/−544 lines, 31 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- DataFrame.Operations.Sorting: Ascending :: SortOrder
- DataFrame.Operations.Sorting: Descending :: SortOrder
- DataFrame.Operations.Sorting: data SortOrder
- DataFrame.Operations.Sorting: instance GHC.Classes.Eq DataFrame.Operations.Sorting.SortOrder
- DataFrame.Operations.Sorting: sortBy :: SortOrder -> [Text] -> DataFrame -> DataFrame
- DataFrame.Operations.Typing: countNonEmpty :: Vector (Maybe a) -> Int
- DataFrame.Operations.Typing: emptyToNothing :: Text -> Maybe Text
- DataFrame.Operations.Typing: equalNonEmpty :: Vector (Maybe a) -> Vector (Maybe b) -> Bool
+ DataFrame: Ascending :: SortOrder
+ DataFrame: Descending :: SortOrder
+ DataFrame: data SortOrder
+ DataFrame: filterAllNothing :: DataFrame -> DataFrame
+ DataFrame: shuffle :: RandomGen g => g -> DataFrame -> DataFrame
+ DataFrame: sortBy :: SortOrder -> [Text] -> DataFrame -> DataFrame
+ DataFrame.Functions: (.=) :: Columnable a => Text -> Expr a -> NamedExpr
+ DataFrame.Functions: [includeConditionals] :: BeamConfig -> Bool
+ DataFrame.Functions: infixr 0 .=
+ DataFrame.Functions: recode :: (Columnable a, Columnable b) => [(a, b)] -> Expr a -> Expr (Maybe b)
+ DataFrame.Functions: roundTo2SigDigits :: Double -> Double
+ DataFrame.Functions: roundToSigDigits :: Int -> Double -> Double
+ DataFrame.Functions: whenBothPresent :: (Columnable a, Columnable b, Columnable c) => (a -> b -> c) -> Expr (Maybe a) -> Expr (Maybe b) -> Expr (Maybe c)
+ DataFrame.Functions: whenPresent :: (Columnable a, Columnable b) => (a -> b) -> Expr (Maybe a) -> Expr (Maybe b)
+ DataFrame.IO.CSV: stripQuotes :: Text -> Text
+ DataFrame.Internal.Column: atIndicesWithNulls :: Vector (Maybe Int) -> Column -> Column
+ DataFrame.Internal.Column: toUnboxedVector :: (Columnable a, Unbox a) => Column -> Either DataFrameException (Vector a)
+ DataFrame.Internal.DataFrame: columnAsUnboxedVector :: (Columnable a, Unbox a) => Text -> DataFrame -> Either DataFrameException (Vector a)
+ DataFrame.Internal.Expression: type NamedExpr = (Text, UExpr)
+ DataFrame.Operations.Join: fullOuterJoin :: [Text] -> DataFrame -> DataFrame -> DataFrame
+ DataFrame.Operations.Join: leftJoin :: [Text] -> DataFrame -> DataFrame -> DataFrame
+ DataFrame.Operations.Join: rightJoin :: [Text] -> DataFrame -> DataFrame -> DataFrame
+ DataFrame.Operations.Permutation: Ascending :: SortOrder
+ DataFrame.Operations.Permutation: Descending :: SortOrder
+ DataFrame.Operations.Permutation: data SortOrder
+ DataFrame.Operations.Permutation: instance GHC.Classes.Eq DataFrame.Operations.Permutation.SortOrder
+ DataFrame.Operations.Permutation: shuffle :: RandomGen g => g -> DataFrame -> DataFrame
+ DataFrame.Operations.Permutation: shuffledIndices :: RandomGen g => g -> Int -> Vector Int
+ DataFrame.Operations.Permutation: sortBy :: SortOrder -> [Text] -> DataFrame -> DataFrame
+ DataFrame.Operations.Subset: filterAllNothing :: DataFrame -> DataFrame
+ DataFrame.Operations.Transformations: deriveMany :: [NamedExpr] -> DataFrame -> DataFrame
+ DataFrame.Operations.Typing: DateAssumption :: ParsingAssumption
+ DataFrame.Operations.Typing: DoubleAssumption :: ParsingAssumption
+ DataFrame.Operations.Typing: IntAssumption :: ParsingAssumption
+ DataFrame.Operations.Typing: NoAssumption :: ParsingAssumption
+ DataFrame.Operations.Typing: TextAssumption :: ParsingAssumption
+ DataFrame.Operations.Typing: convertNullish :: Text -> Maybe Text
+ DataFrame.Operations.Typing: convertOnlyEmpty :: Text -> Maybe Text
+ DataFrame.Operations.Typing: data ParsingAssumption
+ DataFrame.Operations.Typing: handleDateAssumption :: DateFormat -> Vector (Maybe Text) -> Column
+ DataFrame.Operations.Typing: handleDoubleAssumption :: Vector (Maybe Text) -> Column
+ DataFrame.Operations.Typing: handleIntAssumption :: Vector (Maybe Text) -> Column
+ DataFrame.Operations.Typing: handleNoAssumption :: DateFormat -> Vector (Maybe Text) -> Column
+ DataFrame.Operations.Typing: handleTextAssumption :: Vector (Maybe Text) -> Column
+ DataFrame.Operations.Typing: hasNullValues :: Eq a => Vector (Maybe a) -> Bool
+ DataFrame.Operations.Typing: makeParsingAssumption :: DateFormat -> Vector (Maybe Text) -> ParsingAssumption
+ DataFrame.Operations.Typing: type DateFormat = String
+ DataFrame.Operations.Typing: vecSameConstructor :: Vector (Maybe a) -> Vector (Maybe b) -> Bool
- DataFrame: aggregate :: [(Text, UExpr)] -> GroupedDataFrame -> DataFrame
+ DataFrame: aggregate :: [NamedExpr] -> GroupedDataFrame -> DataFrame
- DataFrame: filter :: Columnable a => Text -> (a -> Bool) -> DataFrame -> DataFrame
+ DataFrame: filter :: Columnable a => Expr a -> (a -> Bool) -> DataFrame -> DataFrame
- DataFrame: filterBy :: Columnable a => (a -> Bool) -> Text -> DataFrame -> DataFrame
+ DataFrame: filterBy :: Columnable a => (a -> Bool) -> Expr a -> DataFrame -> DataFrame
- DataFrame: interQuartileRange :: Text -> DataFrame -> Maybe Double
+ DataFrame: interQuartileRange :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame: mean :: Text -> DataFrame -> Maybe Double
+ DataFrame: mean :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame: median :: Text -> DataFrame -> Maybe Double
+ DataFrame: median :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame: skewness :: Text -> DataFrame -> Maybe Double
+ DataFrame: skewness :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame: standardDeviation :: Text -> DataFrame -> Maybe Double
+ DataFrame: standardDeviation :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame: sum :: (Columnable a, Num a, Unbox a) => Text -> DataFrame -> Maybe a
+ DataFrame: sum :: (Columnable a, Num a) => Expr a -> DataFrame -> a
- DataFrame: valueCounts :: Columnable a => Text -> DataFrame -> [(a, Int)]
+ DataFrame: valueCounts :: Columnable a => Expr a -> DataFrame -> [(a, Int)]
- DataFrame: variance :: Text -> DataFrame -> Maybe Double
+ DataFrame: variance :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Display.Terminal.Plot: plotHistogramWith :: HasCallStack => Text -> PlotConfig -> DataFrame -> IO ()
+ DataFrame.Display.Terminal.Plot: plotHistogramWith :: HasCallStack => Text -> Int -> PlotConfig -> DataFrame -> IO ()
- DataFrame.Display.Web.Plot: plotHistogramWith :: HasCallStack => Text -> PlotConfig -> DataFrame -> IO HtmlPlot
+ DataFrame.Display.Web.Plot: plotHistogramWith :: HasCallStack => Text -> Int -> PlotConfig -> DataFrame -> IO HtmlPlot
- DataFrame.Functions: BeamConfig :: Int -> Int -> LossFunction -> BeamConfig
+ DataFrame.Functions: BeamConfig :: Int -> Int -> LossFunction -> Bool -> BeamConfig
- DataFrame.Functions: as :: Columnable a => Expr a -> Text -> (Text, UExpr)
+ DataFrame.Functions: as :: Columnable a => Expr a -> Text -> NamedExpr
- DataFrame.Functions: generatePrograms :: [Expr Bool] -> [Expr Double] -> [Expr Double] -> [Expr Double] -> [Expr Double]
+ DataFrame.Functions: generatePrograms :: Bool -> [Expr Bool] -> [Expr Double] -> [Expr Double] -> [Expr Double] -> [Expr Double]
- DataFrame.Internal.Statistics: interQuartileRange' :: Vector Double -> Double
+ DataFrame.Internal.Statistics: interQuartileRange' :: (Unbox a, Num a, Real a) => Vector a -> Double
- DataFrame.Internal.Statistics: percentile' :: Int -> Vector Double -> Double
+ DataFrame.Internal.Statistics: percentile' :: (Unbox a, Num a, Real a) => Int -> Vector a -> Double
- DataFrame.Internal.Statistics: quantiles' :: Vector Int -> Int -> Vector Double -> Vector Double
+ DataFrame.Internal.Statistics: quantiles' :: (Unbox a, Num a, Real a) => Vector Int -> Int -> Vector a -> Vector Double
- DataFrame.Internal.Statistics: skewness' :: Vector Double -> Double
+ DataFrame.Internal.Statistics: skewness' :: (Unbox a, Real a, Num a) => Vector a -> Double
- DataFrame.Internal.Statistics: skewnessStep :: SkewAcc -> Double -> SkewAcc
+ DataFrame.Internal.Statistics: skewnessStep :: (Unbox a, Num a, Real a) => SkewAcc -> a -> SkewAcc
- DataFrame.Operations.Aggregation: aggregate :: [(Text, UExpr)] -> GroupedDataFrame -> DataFrame
+ DataFrame.Operations.Aggregation: aggregate :: [NamedExpr] -> GroupedDataFrame -> DataFrame
- DataFrame.Operations.Core: valueCounts :: Columnable a => Text -> DataFrame -> [(a, Int)]
+ DataFrame.Operations.Core: valueCounts :: Columnable a => Expr a -> DataFrame -> [(a, Int)]
- DataFrame.Operations.Statistics: interQuartileRange :: Text -> DataFrame -> Maybe Double
+ DataFrame.Operations.Statistics: interQuartileRange :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Operations.Statistics: mean :: Text -> DataFrame -> Maybe Double
+ DataFrame.Operations.Statistics: mean :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Operations.Statistics: median :: Text -> DataFrame -> Maybe Double
+ DataFrame.Operations.Statistics: median :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Operations.Statistics: skewness :: Text -> DataFrame -> Maybe Double
+ DataFrame.Operations.Statistics: skewness :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Operations.Statistics: standardDeviation :: Text -> DataFrame -> Maybe Double
+ DataFrame.Operations.Statistics: standardDeviation :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Operations.Statistics: sum :: (Columnable a, Num a, Unbox a) => Text -> DataFrame -> Maybe a
+ DataFrame.Operations.Statistics: sum :: (Columnable a, Num a) => Expr a -> DataFrame -> a
- DataFrame.Operations.Statistics: variance :: Text -> DataFrame -> Maybe Double
+ DataFrame.Operations.Statistics: variance :: (Columnable a, Real a, Unbox a) => Expr a -> DataFrame -> Double
- DataFrame.Operations.Subset: filter :: Columnable a => Text -> (a -> Bool) -> DataFrame -> DataFrame
+ DataFrame.Operations.Subset: filter :: Columnable a => Expr a -> (a -> Bool) -> DataFrame -> DataFrame
- DataFrame.Operations.Subset: filterBy :: Columnable a => (a -> Bool) -> Text -> DataFrame -> DataFrame
+ DataFrame.Operations.Subset: filterBy :: Columnable a => (a -> Bool) -> Expr a -> DataFrame -> DataFrame
- DataFrame.Operations.Transformations: impute :: Columnable b => Text -> b -> DataFrame -> DataFrame
+ DataFrame.Operations.Transformations: impute :: Columnable b => Expr (Maybe b) -> b -> DataFrame -> DataFrame
- DataFrame.Operations.Typing: parseDefault :: Int -> Bool -> String -> Column -> Column
+ DataFrame.Operations.Typing: parseDefault :: Int -> Bool -> DateFormat -> Column -> Column
- DataFrame.Operations.Typing: parseDefaults :: Int -> Bool -> String -> DataFrame -> DataFrame
+ DataFrame.Operations.Typing: parseDefaults :: Int -> Bool -> DateFormat -> DataFrame -> DataFrame
- DataFrame.Operations.Typing: parseFromExamples :: Int -> String -> Bool -> Vector Text -> Column
+ DataFrame.Operations.Typing: parseFromExamples :: Int -> Bool -> DateFormat -> Vector Text -> Column
- DataFrame.Operations.Typing: parseTimeOpt :: String -> Text -> Maybe Day
+ DataFrame.Operations.Typing: parseTimeOpt :: DateFormat -> Text -> Maybe Day
- DataFrame.Operations.Typing: unsafeParseTime :: String -> Text -> Day
+ DataFrame.Operations.Typing: unsafeParseTime :: DateFormat -> Text -> Day
Files
- CHANGELOG.md +12/−0
- README.md +64/−2
- app/Benchmark.hs +5/−3
- app/Main.hs +2/−0
- dataframe.cabal +3/−2
- src/DataFrame.hs +12/−7
- src/DataFrame/Display/Terminal/Plot.hs +6/−5
- src/DataFrame/Display/Web/Plot.hs +70/−41
- src/DataFrame/Functions.hs +80/−101
- src/DataFrame/IO/CSV.hs +11/−2
- src/DataFrame/IO/Unstable/CSV.hs +4/−3
- src/DataFrame/Internal/Column.hs +42/−0
- src/DataFrame/Internal/DataFrame.hs +6/−0
- src/DataFrame/Internal/Expression.hs +2/−0
- src/DataFrame/Internal/Parsing.hs +5/−3
- src/DataFrame/Internal/Statistics.hs +11/−8
- src/DataFrame/Operations/Aggregation.hs +1/−1
- src/DataFrame/Operations/Core.hs +4/−2
- src/DataFrame/Operations/Join.hs +253/−6
- src/DataFrame/Operations/Permutation.hs +55/−0
- src/DataFrame/Operations/Sorting.hs +0/−39
- src/DataFrame/Operations/Statistics.hs +84/−25
- src/DataFrame/Operations/Subset.hs +26/−5
- src/DataFrame/Operations/Transformations.hs +15/−2
- src/DataFrame/Operations/Typing.hs +125/−72
- tests/Functions.hs +2/−21
- tests/Main.hs +4940/−173
- tests/Operations/Filter.hs +7/−6
- tests/Operations/Join.hs +85/−0
- tests/Operations/Statistics.hs +18/−13
- tests/Parquet.hs +4/−2
CHANGELOG.md view
@@ -1,5 +1,17 @@ # Revision history for dataframe +## 0.3.3.7+* Many functions how rely on expressions (not strings).+* full, left, and right join now implemented.+* fastCsv now strips quotations from text.+* Add "NA" as a nullish pattern.+* Add bin parameter to terminal plotting.+* Implement filterAllNothing for null handling.+* Remove behaviour where we parse mixed types as `Either`+* Add `whenPresent`, `whenBothPresent` and `recode` functions.+* Web charts now show on first load.+* Add deriveMay function for multiple column derivations.+ ## 0.3.3.6 * Fix bug where doubles were parsing as ints * Fix bugs where optionals were left in boxed column (instead of optionals)
README.md view
@@ -8,7 +8,7 @@ <a href="https://hackage.haskell.org/package/dataframe"> <img src="https://img.shields.io/hackage/v/dataframe" alt="hackage Latest Release"/> </a>- <a href="https://github.com/mchav/dataframe/actions/workflows/haskel-ci.yml">+ <a href="https://github.com/mchav/dataframe/actions/workflows/haskell-ci.yml"> <img src="https://github.com/mchav/dataframe/actions/workflows/haskell-ci.yml/badge.svg" alt="C/I"/> </a> </div>@@ -31,4 +31,66 @@ * 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.6/docs/DataFrame.html).+## Features+- Type-safe column operations with compile-time guarantees+- Familiar, approachable API designed to feel easy coming from other languages.+- Interactive REPL for data exploration and plotting.++## Quick start+Browse through some examples in [binder](https://mybinder.org/v2/gh/mchav/ihaskell-dataframe/HEAD) or in our [playground](https://ulwazi-exh9dbh2exbzgbc9.westus-01.azurewebsites.net/lab).++## Install++### Cabal+To use the CLI tool:+```bash+$ cabal update+$ cabal install dataframe+$ dataframe+```++As a prodject dependency add `dataframe` to your <project>.cabal file.++### Stack (in stack.yaml add to extra-deps if needed)+Add to your package.yaml dependencies:+```yaml+dependencies:+ - dataframe+```++Or manually to stack.yaml extra-deps if needed.++## Example++```haskell+dataframe> df = D.fromNamedColumns [("product_id", D.fromList [1,1,2,2,3,3]), ("sales", D.fromList [100,120,50,20,40,30])]+dataframe> df+------------------+product_id | sales+-----------|------+ Int | Int +-----------|------+1 | 100 +1 | 120 +2 | 50 +2 | 20 +3 | 40 +3 | 30 ++dataframe> :exposeColumns df+"product_id :: Expr Int"+"sales :: Expr Int"+dataframe> df |> D.groupBy [F.name product_id] |> D.aggregate [F.sum sales `as` "total_sales"]+------------------------+product_id | total_sales+-----------|------------+ Int | Int +-----------|------------+1 | 220 +2 | 70 +3 | 70 +```++## Documentation+* 📚 User guide: https://dataframe.readthedocs.io/en/latest/+* 📖 API reference: https://hackage.haskell.org/package/dataframe/docs/DataFrame.html
app/Benchmark.hs view
@@ -1,9 +1,11 @@ {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-} import Data.Time import qualified Data.Vector.Unboxed as VU import qualified DataFrame as D+import qualified DataFrame.Functions as F import System.Random.Stateful main :: IO ()@@ -21,14 +23,14 @@ let generationTime = diffUTCTime endGeneration startGeneration putStrLn $ "Data generation Time: " ++ show generationTime startCalculation <- getCurrentTime- print $ D.mean "0" df- print $ D.variance "1" df+ print $ D.mean (F.col @Double "0") df+ print $ D.variance (F.col @Double "1") df print $ D.correlation "1" "2" df endCalculation <- getCurrentTime let calculationTime = diffUTCTime endCalculation startCalculation putStrLn $ "Calculation Time: " ++ show calculationTime startFilter <- getCurrentTime- print $ D.filter "0" (> (0.971 :: Double)) df D.|> D.take 10+ print $ D.filter (F.col @Double "0") (> 0.971) df D.|> D.take 10 endFilter <- getCurrentTime let filterTime = diffUTCTime endFilter startFilter putStrLn $ "Filter Time: " ++ show filterTime
app/Main.hs view
@@ -32,6 +32,8 @@ "" putStrLn output + putStrLn =<< readProcess "cabal" ["update"] ""+ let command = "cabal" args = [ "repl"
dataframe.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: dataframe-version: 0.3.3.6+version: 0.3.3.7 synopsis: A fast, safe, and intuitive DataFrame library. @@ -59,8 +59,8 @@ DataFrame.Operations.Core, DataFrame.Operations.Join, DataFrame.Operations.Merge,+ DataFrame.Operations.Permutation, DataFrame.Operations.Subset,- DataFrame.Operations.Sorting, DataFrame.Operations.Statistics, DataFrame.Operations.Transformations, DataFrame.Operations.Typing,@@ -162,6 +162,7 @@ Operations.Filter, Operations.GroupBy, Operations.InsertColumn,+ Operations.Join, Operations.Merge, Operations.ReadCsv, Operations.Sort,
src/DataFrame.hs view
@@ -22,9 +22,8 @@ macros for writing safe code. @-\$ curl --output dataframe \"https:\/\/raw.githubusercontent.com\/mchav\/dataframe\/refs\/heads\/main\/scripts\/dataframe.sh\"-\$ chmod +x dataframe-\$ export PATH=$PATH:$PWD/dataframe+\$ cabal update+\$ cabal install dataframe \$ dataframe Configuring library for fake-package-0... Warning: No exposed modules@@ -115,6 +114,7 @@ __Row ops__ + * @D.filter :: Expr a -> (a -> Bool) -> DataFrame -> DataFrame@ * @D.filterWhere :: Expr Bool -> DataFrame -> DataFrame@ * @D.sortBy :: SortOrder -> [Text] -> DataFrame -> DataFrame@ @@ -128,11 +128,11 @@ __Group & aggregate__ * @D.groupBy :: [Text] -> DataFrame -> GroupedDataFrame@- * @D.aggregate :: [(Text, F.UExpr)] -> GroupedDataFrame -> DataFrame@+ * @D.aggregate :: [NamedExpr] -> GroupedDataFrame -> DataFrame@ __Joins__ - * @D.innerJoin \/ D.leftJoin \/ D.rightJoin \/ D.fullJoin@+ * @D.innerJoin \/ D.leftJoin \/ D.rightJoin \/ D.fullOuterJoin@ == Expression DSL (F.*) at a glance Columns (typed):@@ -216,7 +216,7 @@ module Subset, module Transformations, module Aggregation,- module Sorting,+ module Permutation, module Merge, module Join, module Statistics,@@ -305,7 +305,11 @@ ) import DataFrame.Operations.Join as Join import DataFrame.Operations.Merge as Merge-import DataFrame.Operations.Sorting as Sorting+import DataFrame.Operations.Permutation as Permutation (+ SortOrder (..),+ shuffle,+ sortBy,+ ) import DataFrame.Operations.Statistics as Statistics ( correlation, frequencies,@@ -331,6 +335,7 @@ exclude, filter, filterAllJust,+ filterAllNothing, filterBy, filterJust, filterNothing,
src/DataFrame/Display/Terminal/Plot.hs view
@@ -24,6 +24,7 @@ import DataFrame.Internal.Column (Column (..), isNumeric) import qualified DataFrame.Internal.Column as D import DataFrame.Internal.DataFrame (DataFrame (..), getColumn)+import DataFrame.Internal.Expression import DataFrame.Operations.Core import qualified DataFrame.Operations.Subset as D import Granite@@ -52,14 +53,14 @@ } plotHistogram :: (HasCallStack) => T.Text -> DataFrame -> IO ()-plotHistogram colName = plotHistogramWith colName (defaultPlotConfig Histogram)+plotHistogram colName = plotHistogramWith colName 30 (defaultPlotConfig Histogram) plotHistogramWith ::- (HasCallStack) => T.Text -> PlotConfig -> DataFrame -> IO ()-plotHistogramWith colName config df = do+ (HasCallStack) => T.Text -> Int -> PlotConfig -> DataFrame -> IO ()+plotHistogramWith colName numBins config df = do let values = extractNumericColumn colName df (minVal, maxVal) = if null values then (0, 1) else (minimum values, maximum values)- T.putStrLn $ histogram (bins 30 minVal maxVal) values (plotSettings config)+ T.putStrLn $ histogram (bins numBins minVal maxVal) values (plotSettings config) plotScatter :: (HasCallStack) => T.Text -> T.Text -> DataFrame -> IO () plotScatter xCol yCol = plotScatterWith xCol yCol (defaultPlotConfig Scatter)@@ -82,7 +83,7 @@ let vals = extractStringColumn grouping df let df' = insertColumn grouping (D.fromList vals) df xs <- forM (L.nub vals) $ \col -> do- let filtered = D.filter grouping (== col) df'+ let filtered = D.filter (Col grouping) (== col) df' xVals = extractNumericColumn xCol filtered yVals = extractNumericColumn yCol filtered points = zip xVals yVals
src/DataFrame/Display/Web/Plot.hs view
@@ -26,6 +26,7 @@ import DataFrame.Internal.Column (Column (..), isNumeric) import qualified DataFrame.Internal.Column as D import DataFrame.Internal.DataFrame (DataFrame (..), getColumn)+import DataFrame.Internal.Expression import DataFrame.Operations.Core import qualified DataFrame.Operations.Subset as D import System.Directory@@ -39,6 +40,7 @@ std_out, waitForProcess, )+import Text.Printf newtype HtmlPlot = HtmlPlot T.Text deriving (Show) @@ -94,18 +96,20 @@ , "px;height:" , T.pack (show height) , "px\"></canvas>\n"- , "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js\"></script>\n"+ , "<script src=\"https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js\"></script>\n" , "<script>\n"+ , "setTimeout(() => {" , content+ , "}, 200);" , "\n</script>\n" ] plotHistogram :: (HasCallStack) => T.Text -> DataFrame -> IO HtmlPlot-plotHistogram colName = plotHistogramWith colName (defaultPlotConfig Histogram)+plotHistogram colName = plotHistogramWith colName 30 (defaultPlotConfig Histogram) plotHistogramWith ::- (HasCallStack) => T.Text -> PlotConfig -> DataFrame -> IO HtmlPlot-plotHistogramWith colName config df = do+ (HasCallStack) => T.Text -> Int -> PlotConfig -> DataFrame -> IO HtmlPlot+plotHistogramWith colName numBins config df = do chartId <- generateChartId let values = extractNumericColumn colName df (minVal, maxVal) = if null values then (0, 1) else (minimum values, maximum values)@@ -115,7 +119,7 @@ counts = calculateHistogram values bins binWidth labels =- T.intercalate "," ["\"" <> T.pack (show (round b :: Int)) <> "\"" | b <- bins]+ T.intercalate "," ["\"" <> T.pack (printf "%.2f" b) <> "\"" | b <- bins] dataPoints = T.intercalate "," [T.pack (show c) | c <- counts] chartTitle =@@ -125,9 +129,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"bar\",\n" , " data: {\n" , " labels: ["@@ -153,7 +159,7 @@ , " yAxes: [{ ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $@@ -186,9 +192,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"scatter\",\n" , " data: {\n" , " datasets: [{\n"@@ -215,7 +223,7 @@ , "\" } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $@@ -246,7 +254,7 @@ ] datasets <- forM (zip uniqueVals colors) $ \(val, color) -> do- let filtered = D.filter grouping (== val) df'+ let filtered = D.filter (Col grouping) (== val) df' xVals = extractNumericColumn xCol filtered yVals = extractNumericColumn yCol filtered points = zip xVals yVals@@ -279,9 +287,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"scatter\",\n" , " data: {\n" , " datasets: [\n"@@ -301,7 +311,7 @@ , "\" } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $@@ -353,9 +363,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"line\",\n" , " data: {\n" , " labels: ["@@ -375,7 +387,7 @@ , "\" } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $@@ -407,9 +419,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"bar\",\n" , " data: {\n" , " labels: ["@@ -433,7 +447,7 @@ , " yAxes: [{ ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $ HtmlPlot $@@ -451,8 +465,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , "\", {\n" , " type: \"bar\",\n" , " data: {\n"@@ -477,7 +494,7 @@ , " yAxes: [{ ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $ HtmlPlot $@@ -502,9 +519,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"pie\",\n" , " data: {\n" , " labels: ["@@ -524,7 +543,7 @@ , chartTitle , "\" }\n" , " }\n"- , "});"+ , "});});" ] return $ HtmlPlot $@@ -546,9 +565,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"pie\",\n" , " data: {\n" , " labels: ["@@ -568,7 +589,7 @@ , chartTitle , "\" }\n" , " }\n"- , "});"+ , "});});" ] return $ HtmlPlot $@@ -637,9 +658,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"bar\",\n" , " data: {\n" , " labels: ["@@ -658,7 +681,7 @@ , " yAxes: [{ stacked: true, ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $@@ -689,9 +712,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"bar\",\n" , " data: {\n" , " labels: ["@@ -715,7 +740,7 @@ , " yAxes: [{ ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $@@ -748,9 +773,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"bar\",\n" , " data: {\n" , " labels: ["@@ -776,7 +803,7 @@ , " yAxes: [{ ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $ HtmlPlot $@@ -800,9 +827,11 @@ jsCode = T.concat- [ "new Chart(\""+ [ "requirejs(['https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js'], function (Chart) {\n"+ , "var ctx = document.getElementById('" , chartId- , "\", {\n"+ , "').getContext('2d');\n"+ , "new Chart(ctx , {\n" , " type: \"bar\",\n" , " data: {\n" , " labels: ["@@ -826,7 +855,7 @@ , " yAxes: [{ ticks: { beginAtZero: true } }]\n" , " }\n" , " }\n"- , "});"+ , "});});" ] return $ HtmlPlot $
src/DataFrame/Functions.hs view
@@ -22,6 +22,7 @@ ) import DataFrame.Internal.Expression ( Expr (..),+ NamedExpr, UExpr (..), eSize, interpret,@@ -38,7 +39,7 @@ import Data.Function import qualified Data.List as L import qualified Data.Map as M-import Data.Maybe (fromMaybe, listToMaybe)+import Data.Maybe (listToMaybe) import qualified Data.Set as S import qualified Data.Text as T import Data.Type.Equality@@ -62,9 +63,13 @@ col :: (Columnable a) => T.Text -> Expr a col = Col -as :: (Columnable a) => Expr a -> T.Text -> (T.Text, UExpr)+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 @@ -200,6 +205,22 @@ (Columnable a, Columnable b) => Expr b -> a -> (a -> b -> a) -> Expr a reduce expr = AggFold expr "foldUdf" +whenPresent ::+ forall a b.+ (Columnable a, Columnable b) => (a -> b) -> Expr (Maybe a) -> Expr (Maybe b)+whenPresent f = lift (fmap f)++whenBothPresent ::+ forall a b c.+ (Columnable a, Columnable b, Columnable c) =>+ (a -> b -> c) -> Expr (Maybe a) -> Expr (Maybe b) -> Expr (Maybe c)+whenBothPresent f = lift2 (\l r -> f <$> l <*> r)++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)+ generateConditions :: TypedColumn Double -> [Expr Bool] -> [Expr Double] -> DataFrame -> [Expr Bool] generateConditions labels conds ps df =@@ -222,75 +243,14 @@ pickTopNBool df labels (deduplicate df expandedConds) generatePrograms ::- [Expr Bool] -> [Expr Double] -> [Expr Double] -> [Expr Double] -> [Expr Double]-generatePrograms conds vars' constants [] =- let- vars = vars' ++ constants- in- nubOrd $- vars- ++ [ transform p- | p <- vars'- , transform <-- [ abs- , sqrt- , log . (+ Lit 1)- , exp- , sin- , cos- , relu- , signum- ]- ]- ++ [ pow i p- | p <- vars- , i <- [2 .. 6]- ]- ++ [ p + q- | (i, p) <- zip [0 ..] vars- , (j, q) <- zip [0 ..] vars- , Prelude.not (isLiteral p && isLiteral q)- , 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 > 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 > j- ]- ++ [ ifThenElse cond r s- | cond <- conds- , r <- vars- , s <- vars- , r /= s- ]- ++ [ p - q- | (i, p) <- zip [0 ..] vars- , (j, q) <- zip [0 ..] vars- , Prelude.not (isLiteral p && isLiteral q)- , i /= j- ]- ++ [ p * q- | (i, p) <- zip [0 ..] vars- , (j, q) <- zip [0 ..] vars- , Prelude.not (isLiteral p && isLiteral q)- , i >= j- ]- ++ [ p / q- | (i, p) <- zip [0 ..] vars- , (j, q) <- zip [0 ..] vars- , Prelude.not (isLiteral p && isLiteral q)- , i /= j- ]-generatePrograms conds vars constants ps =+ 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@@ -318,26 +278,30 @@ , Prelude.not (isLiteral p && isLiteral q) , 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 > 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- ]+ ++ ( 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@@ -456,7 +420,7 @@ in case beamSearch df'- (BeamConfig d b F1)+ (BeamConfig d b F1 True) t (percentiles df' ++ [lit 1, lit 0, lit (-1)]) []@@ -469,10 +433,23 @@ let doubleColumns = map (either throw id . (`columnAsDoubleVector` df)) (D.columnNames df) in- concatMap (\c -> map (lit . (`percentile'` c)) [1, 23, 75, 99]) doubleColumns- ++ map (lit . variance') doubleColumns- ++ map (lit . sqrt . variance') doubleColumns+ 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 ->@@ -485,7 +462,7 @@ fitRegression target d b df = let df' = exclude [target] df- targetMean = fromMaybe 0 $ Stats.mean target df+ targetMean = Stats.mean (Col @Double target) df t = case interpret df (Col target) of Left e -> throw e Right v -> v@@ -496,6 +473,7 @@ d b MutualInformation+ False ) t (percentiles df')@@ -509,7 +487,7 @@ ( D.derive "_generated_regression_feature_" p df & select ["_generated_regression_feature_"] )- (BeamConfig d b MeanSquaredError)+ (BeamConfig d b MeanSquaredError False) t (percentiles df' ++ [lit targetMean, lit 10]) []@@ -541,10 +519,11 @@ { searchDepth :: Int , beamLength :: Int , lossFunction :: LossFunction+ , includeConditionals :: Bool } defaultBeamConfig :: BeamConfig-defaultBeamConfig = BeamConfig 2 100 PearsonCorrelation+defaultBeamConfig = BeamConfig 2 100 PearsonCorrelation False beamSearch :: DataFrame ->@@ -570,7 +549,7 @@ outputs constants conditions- (generatePrograms conditions vars constants ps)+ (generatePrograms (includeConditionals cfg) conditions vars constants ps) where vars = map col names conditions = generateConditions outputs conds (vars ++ constants ++ ps) df@@ -648,7 +627,7 @@ Right v -> v ordered = Prelude.take- 100+ 10 ( map fst $ L.sortBy ( \(_, c2) (_, c1) ->@@ -766,7 +745,7 @@ lhs <- typeFromString [t1] rhs <- typeFromString [t2] return (AppT (AppT outer lhs) rhs)-typeFromString s = fail $ "Unsupported type: " ++ unwords s+typeFromString s = fail $ "Unsupported types: " ++ unwords s declareColumns :: DataFrame -> DecsQ declareColumns df =
src/DataFrame/IO/CSV.hs view
@@ -228,7 +228,7 @@ let firstRow = parseLine sep firstLine columnNames = case headerSpec opts of NoHeader -> map (T.pack . show) [0 .. length firstRow - 1]- UseFirstRow -> map (T.filter (/= '\"') . TE.decodeUtf8Lenient) firstRow+ UseFirstRow -> map (stripQuotes . TE.decodeUtf8Lenient) firstRow ProvideNames ns -> ns unless (headerSpec opts == UseFirstRow) $ hSeek handle AbsoluteSeek 0@@ -291,7 +291,7 @@ where processValue :: Int -> BS.ByteString -> GrowingColumn -> IO () processValue !idx !bs !col = do- let !val = TE.decodeUtf8Lenient bs+ let !val = (stripQuotes . TE.decodeUtf8Lenient) bs case col of GrowingInt gv nulls -> case readByteStringInt bs of@@ -478,3 +478,12 @@ ++ " has less items than " ++ "the other columns at index " ++ show i++stripQuotes :: T.Text -> T.Text+stripQuotes txt =+ case T.uncons txt of+ Just ('"', rest) ->+ case T.unsnoc rest of+ Just (middle, '"') -> middle+ _ -> txt+ _ -> txt
src/DataFrame/IO/Unstable/CSV.hs view
@@ -46,6 +46,7 @@ ReadOptions (..), defaultReadOptions, shouldInferFromSample,+ stripQuotes, typeInferenceSampleSize, ) import DataFrame.Internal.DataFrame (DataFrame (..))@@ -94,7 +95,7 @@ ) UseFirstRow -> ( Vector.fromList $- map extractField' [0 .. numCol - 1]+ map (stripQuotes . extractField') [0 .. numCol - 1] , 1 ) ProvideNames ns ->@@ -107,8 +108,8 @@ else 0 in parseFromExamples n- (dateFormat opts) (safeRead opts)+ (dateFormat opts) col generateColumn col = parseTypes $@@ -116,7 +117,7 @@ numRow ( map ( \row ->- extractField'+ (stripQuotes . extractField') (row * numCol + col) ) [dataStartRow .. totalRows - 1]
src/DataFrame/Internal/Column.hs view
@@ -334,6 +334,20 @@ atIndicesStable indexes (OptionalColumn column) = OptionalColumn $ VG.unsafeBackpermute column (VG.convert indexes) {-# 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+ BoxedColumn col ->+ OptionalColumn $ VB.map (fmap (col VB.!)) indices+ UnboxedColumn col ->+ OptionalColumn $ VB.map (fmap (col VU.!)) indices+ OptionalColumn col ->+ OptionalColumn $ VB.map (\ix -> ix >>= (col VB.!)) indices+ -- | Internal helper to get indices in a boxed vector. getIndices :: VU.Vector Int -> VB.Vector a -> VB.Vector a getIndices indices xs = VB.generate (VU.length indices) (\i -> xs VB.! (indices VU.! i))@@ -1229,6 +1243,34 @@ { userType = Right (typeRep @Int) , expectedType = Left (columnTypeString column) :: Either String (TypeRep ()) , callingFunctionName = Just "toIntVector"+ , errorColumnName = Nothing+ }+ )++toUnboxedVector ::+ forall a.+ (Columnable a, VU.Unbox a) => Column -> Either DataFrameException (VU.Vector a)+toUnboxedVector column =+ case column of+ UnboxedColumn (f :: VU.Vector b) -> case testEquality (typeRep @a) (typeRep @b) of+ Just Refl -> Right f+ Nothing ->+ Left $+ TypeMismatchException+ ( MkTypeErrorContext+ { userType = Right (typeRep @Int)+ , expectedType = Right (typeRep @a)+ , callingFunctionName = Just "toIntVector"+ , errorColumnName = Nothing+ }+ )+ _ ->+ Left $+ TypeMismatchException+ ( MkTypeErrorContext+ { userType = Right (typeRep @a)+ , expectedType = Left (columnTypeString column) :: Either String (TypeRep ())+ , callingFunctionName = Just "toUnboxedVector" , errorColumnName = Nothing } )
src/DataFrame/Internal/DataFrame.hs view
@@ -297,3 +297,9 @@ columnAsFloatVector :: T.Text -> DataFrame -> Either DataFrameException (VU.Vector Float) columnAsFloatVector name df = toFloatVector (unsafeGetColumn name df)++columnAsUnboxedVector ::+ forall a.+ (Columnable a, VU.Unbox a) =>+ T.Text -> DataFrame -> Either DataFrameException (VU.Vector a)+columnAsUnboxedVector name df = toUnboxedVector @a (unsafeGetColumn name df)
src/DataFrame/Internal/Expression.hs view
@@ -95,6 +95,8 @@ data UExpr where Wrap :: (Columnable a) => Expr a -> UExpr +type NamedExpr = (T.Text, UExpr)+ interpret :: forall a. (Columnable a) =>
src/DataFrame/Internal/Parsing.hs view
@@ -13,9 +13,11 @@ import Text.Read (readMaybe) isNullish :: T.Text -> Bool-isNullish s =- s- `S.member` S.fromList ["Nothing", "NULL", "", " ", "nan", "null", "N/A", "NaN", "NAN"]+isNullish =+ ( `S.member`+ S.fromList+ ["Nothing", "NULL", "", " ", "nan", "null", "N/A", "NaN", "NAN", "NA"]+ ) readValue :: (HasCallStack, Read a) => T.Text -> a readValue s = case readMaybe (T.unpack s) of
src/DataFrame/Internal/Statistics.hs view
@@ -58,9 +58,10 @@ -- accumulator: count, mean, m2, m3 data SkewAcc = SkewAcc !Int !Double !Double !Double deriving (Show) -skewnessStep :: SkewAcc -> Double -> SkewAcc-skewnessStep (SkewAcc !n !mean !m2 !m3) !x =+skewnessStep :: (VU.Unbox a, Num a, Real a) => SkewAcc -> a -> SkewAcc+skewnessStep (SkewAcc !n !mean !m2 !m3) !x' = let !n' = n + 1+ x = realToFrac x' !k = fromIntegral n' !delta = x - mean !mean' = mean + delta / k@@ -75,7 +76,7 @@ | otherwise = (sqrt (fromIntegral n - 1) * m3) / sqrt (m2 ^ 3) {-# INLINE computeSkewness #-} -skewness' :: VU.Vector Double -> Double+skewness' :: (VU.Unbox a, Real a, Num a) => VU.Vector a -> Double skewness' = computeSkewness . VU.foldl' skewnessStep (SkewAcc 0 0 0 0) {-# INLINE skewness' #-} @@ -106,7 +107,9 @@ | otherwise = (sumX, sumY, sumSquaredX, sumSquaredY, sumXY) {-# INLINE correlation' #-} -quantiles' :: VU.Vector Int -> Int -> VU.Vector Double -> VU.Vector Double+quantiles' ::+ (VU.Unbox a, Num a, Real a) =>+ VU.Vector Int -> Int -> VU.Vector a -> VU.Vector Double quantiles' qs q samp | VU.null samp = throw $ EmptyDataSetException "quantiles" | q < 2 = throw $ WrongQuantileNumberException q@@ -121,20 +124,20 @@ !position = p * fromIntegral (n - 1) !index = floor position !f = position - fromIntegral index- x <- VUM.read mutableSamp index+ x <- fmap realToFrac (VUM.read mutableSamp index) if f == 0 then return x else do- y <- VUM.read mutableSamp (index + 1)+ y <- fmap realToFrac (VUM.read mutableSamp (index + 1)) return $ (1 - f) * x + f * y ) qs {-# INLINE quantiles' #-} -percentile' :: Int -> VU.Vector Double -> Double+percentile' :: (VU.Unbox a, Num a, Real a) => Int -> VU.Vector a -> Double percentile' n = VU.head . quantiles' (VU.fromList [n]) 100 -interQuartileRange' :: VU.Vector Double -> Double+interQuartileRange' :: (VU.Unbox a, Num a, Real a) => VU.Vector a -> Double interQuartileRange' samp = let quartiles = quantiles' (VU.fromList [1, 3]) 4 samp in quartiles VU.! 1 - quartiles VU.! 0
src/DataFrame/Operations/Aggregation.hs view
@@ -113,7 +113,7 @@ {- | Aggregate a grouped dataframe using the expressions given. All ungrouped columns will be dropped. -}-aggregate :: [(T.Text, UExpr)] -> GroupedDataFrame -> DataFrame+aggregate :: [NamedExpr] -> GroupedDataFrame -> DataFrame aggregate aggs gdf@(Grouped df groupingColumns valueIndices offsets) = let df' =
src/DataFrame/Operations/Core.hs view
@@ -32,6 +32,7 @@ fromVector, ) import DataFrame.Internal.DataFrame (DataFrame (..), empty, getColumn)+import DataFrame.Internal.Expression import DataFrame.Internal.Parsing (isNullish) import DataFrame.Internal.Row (Any, mkColumnFromRow) import Type.Reflection@@ -521,8 +522,8 @@ @ -}-valueCounts :: forall a. (Columnable a) => T.Text -> DataFrame -> [(a, Int)]-valueCounts columnName df = case getColumn columnName df of+valueCounts :: forall a. (Columnable a) => Expr a -> DataFrame -> [(a, Int)]+valueCounts (Col columnName) df = case getColumn columnName df of Nothing -> throw $ ColumnNotFoundException columnName "valueCounts" (M.keys $ columnIndices df)@@ -575,6 +576,7 @@ } ) Just Refl -> M.toAscList column+valueCounts _ _ = error "Cannot call value counts on non-column reference" {- | A left fold for dataframes that takes the dataframe as the last object. This makes it easier to chain operations.
src/DataFrame/Operations/Join.hs view
@@ -1,4 +1,6 @@+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} module DataFrame.Operations.Join where @@ -29,14 +31,32 @@ DataFrame -> -- Left hand side DataFrame join INNER xs right = innerJoin xs right-join LEFT xs right = error "UNIMPLEMENTED"-join RIGHT xs right = error "UNIMPLEMENTED"-join FULL_OUTER xs right = error "UNIMPLEMENTED"+join LEFT xs right = leftJoin xs right+join RIGHT xs right = rightJoin xs right+join FULL_OUTER xs right = fullOuterJoin xs right -{- | Inner join of two dataframes. Note: for chaining, the left dataframe is actually-on the right side.+{- | Performs an inner join on two dataframes using the specified key columns.+Returns only rows where the key values exist in both dataframes.++==== __Example__+@+ghci> df = D.fromNamedColumns [("key", D.fromList ["K0", "K1", "K2", "K3"]), ("A", D.fromList ["A0", "A1", "A2", "A3"])]+ghci> other = D.fromNamedColumns [("key", D.fromList ["K0", "K1", "K2"]), ("B", D.fromList ["B0", "B1", "B2"])]+ghci> D.innerJoin ["key"] df other++-----------------+ key | A | B+------|-----|----+ Text | Text| Text+------|-----|----+ K0 | A0 | B0+ K1 | A1 | B1+ K2 | A2 | B2++@ -}-innerJoin :: [T.Text] -> DataFrame -> DataFrame -> DataFrame+innerJoin ::+ [T.Text] -> DataFrame -> DataFrame -> DataFrame innerJoin cs right left = let leftIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices left)@@ -90,6 +110,233 @@ (VU.length expandedRightIndicies, snd (D.dataframeDimensions right)) } -- [string]+ leftColumns = D.columnNames left+ rightColumns = D.columnNames right+ initDf = expandedLeft+ insertIfPresent _ Nothing df = df+ insertIfPresent name (Just c) df = D.insertColumn name c df+ in+ D.fold+ ( \name df ->+ if name `elem` cs+ then df+ else+ ( if name `elem` leftColumns+ then insertIfPresent ("Right_" <> name) (D.getColumn name expandedRight) df+ else insertIfPresent name (D.getColumn name expandedRight) df+ )+ )+ rightColumns+ initDf++{- | Performs a left join on two dataframes using the specified key columns.+Returns all rows from the left dataframe, with matching rows from the right dataframe.+Non-matching rows will have Nothing/null values for columns from the right dataframe.++==== __Example__+@+ghci> df = D.fromNamedColumns [("key", D.fromList ["K0", "K1", "K2", "K3"]), ("A", D.fromList ["A0", "A1", "A2", "A3"])]+ghci> other = D.fromNamedColumns [("key", D.fromList ["K0", "K1", "K2"]), ("B", D.fromList ["B0", "B1", "B2"])]+ghci> D.leftJoin ["key"] df other++------------------------+ key | A | B+------|-----|----------+ Text | Text| Maybe Text+------|-----|----------+ K0 | A0 | Just "B0"+ K1 | A1 | Just "B1"+ K2 | A2 | Just "B2"+ K3 | A3 | Nothing++@+-}+leftJoin ::+ [T.Text] -> DataFrame -> DataFrame -> DataFrame+leftJoin cs right left =+ let+ leftIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices left)+ leftRowRepresentations = VU.generate (fst (D.dimensions left)) (D.mkRowRep leftIndicesToGroup left)+ rightIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices right)+ rightRowRepresentations = VU.generate (fst (D.dimensions right)) (D.mkRowRep rightIndicesToGroup right)+ rightKeyCountsAndIndices =+ VU.foldr+ (\(i, v) acc -> M.insertWith (++) v [i] acc)+ M.empty+ (VU.indexed rightRowRepresentations)+ rightKeyCountsAndIndicesVec = M.map VU.fromList rightKeyCountsAndIndices+ leftRowCount = fst (D.dimensions left)+ pairs =+ [ (i, maybeRight)+ | i <- [0 .. leftRowCount - 1]+ , maybeRight <-+ case M.lookup (leftRowRepresentations VU.! i) rightKeyCountsAndIndicesVec of+ Nothing -> [Nothing]+ Just rVec -> map Just (VU.toList rVec)+ ]+ expandedLeftIndicies = VU.fromList (map fst pairs)+ expandedRightIndicies = VB.fromList (map snd pairs)+ expandedLeft =+ left+ { columns = VB.map (D.atIndicesStable expandedLeftIndicies) (D.columns left)+ , dataframeDimensions =+ (VU.length expandedLeftIndicies, snd (D.dataframeDimensions left))+ }+ expandedRight =+ right+ { columns = VB.map (D.atIndicesWithNulls expandedRightIndicies) (D.columns right)+ , dataframeDimensions =+ (VB.length expandedRightIndicies, snd (D.dataframeDimensions right))+ }+ leftColumns = D.columnNames left+ rightColumns = D.columnNames right+ initDf = expandedLeft+ insertIfPresent _ Nothing df = df+ insertIfPresent name (Just c) df = D.insertColumn name c df+ in+ D.fold+ ( \name df ->+ if name `elem` cs+ then df+ else+ ( if name `elem` leftColumns+ then insertIfPresent ("Right_" <> name) (D.getColumn name expandedRight) df+ else insertIfPresent name (D.getColumn name expandedRight) df+ )+ )+ rightColumns+ initDf++{- | Performs a right join on two dataframes using the specified key columns.+Returns all rows from the right dataframe, with matching rows from the left dataframe.+Non-matching rows will have Nothing/null values for columns from the left dataframe.++==== __Example__+@+ghci> df = D.fromNamedColumns [("key", D.fromList ["K0", "K1", "K2", "K3"]), ("A", D.fromList ["A0", "A1", "A2", "A3"])]+ghci> other = D.fromNamedColumns [("key", D.fromList ["K0", "K1"]), ("B", D.fromList ["B0", "B1"])]+ghci> D.rightJoin ["key"] df other++-----------------+ key | A | B+------|-----|----+ Text | Text| Text+------|-----|----+ K0 | A0 | B0+ K1 | A1 | B1++@+-}+rightJoin ::+ [T.Text] -> DataFrame -> DataFrame -> DataFrame+rightJoin cs right left =+ let+ leftIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices left)+ leftRowRepresentations = VU.generate (fst (D.dimensions left)) (D.mkRowRep leftIndicesToGroup left)+ leftKeyCountsAndIndicesVec =+ M.map VU.fromList $+ VU.foldr+ (\(i, v) acc -> M.insertWith (++) v [i] acc)+ M.empty+ (VU.indexed leftRowRepresentations)+ rightIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices right)+ rightRowRepresentations = VU.generate (fst (D.dimensions right)) (D.mkRowRep rightIndicesToGroup right)+ rightRowCount = fst (D.dimensions right)+ pairs =+ [ (maybeLeft, j)+ | j <- [0 .. rightRowCount - 1]+ , maybeLeft <-+ case M.lookup (rightRowRepresentations VU.! j) leftKeyCountsAndIndicesVec of+ Nothing -> [Nothing]+ Just lVec -> map Just (VU.toList lVec)+ ]+ expandedLeftIndicies = VB.fromList (map fst pairs)+ expandedRightIndicies = VU.fromList (map snd pairs)+ expandedLeft =+ left+ { columns = VB.map (D.atIndicesWithNulls expandedLeftIndicies) (D.columns left)+ , dataframeDimensions =+ (VB.length expandedLeftIndicies, snd (D.dataframeDimensions left))+ }+ expandedRight =+ right+ { columns = VB.map (D.atIndicesStable expandedRightIndicies) (D.columns right)+ , dataframeDimensions =+ (VU.length expandedRightIndicies, snd (D.dataframeDimensions right))+ }+ leftColumns = D.columnNames left+ rightColumns = D.columnNames right+ initDf = expandedLeft+ insertIfPresent _ Nothing df = df+ insertIfPresent name (Just c) df = D.insertColumn name c df+ in+ D.fold+ ( \name df ->+ if name `elem` cs+ then df+ else+ ( if name `elem` leftColumns+ then insertIfPresent ("Right_" <> name) (D.getColumn name expandedRight) df+ else insertIfPresent name (D.getColumn name expandedRight) df+ )+ )+ rightColumns+ initDf++fullOuterJoin ::+ [T.Text] -> DataFrame -> DataFrame -> DataFrame+fullOuterJoin cs right left =+ let+ leftIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices left)+ leftRowRepresentations = VU.generate (fst (D.dimensions left)) (D.mkRowRep leftIndicesToGroup left)+ leftKeyCountsAndIndices =+ VU.foldr+ (\(i, v) acc -> M.insertWith (++) v [i] acc)+ M.empty+ (VU.indexed leftRowRepresentations)+ leftKeyCountsAndIndicesVec = M.map VU.fromList leftKeyCountsAndIndices+ rightIndicesToGroup = M.elems $ M.filterWithKey (\k _ -> k `elem` cs) (D.columnIndices right)+ rightRowRepresentations = VU.generate (fst (D.dimensions right)) (D.mkRowRep rightIndicesToGroup right)+ rightKeyCountsAndIndices =+ VU.foldr+ (\(i, v) acc -> M.insertWith (++) v [i] acc)+ M.empty+ (VU.indexed rightRowRepresentations)+ rightKeyCountsAndIndicesVec = M.map VU.fromList rightKeyCountsAndIndices+ matchedPairs =+ concatMap+ ( \(lVec, rVec) ->+ [ (Just lIdx, Just rIdx)+ | lIdx <- VU.toList lVec+ , rIdx <- VU.toList rVec+ ]+ )+ ( M.elems+ (M.intersectionWith (,) leftKeyCountsAndIndicesVec rightKeyCountsAndIndicesVec)+ )+ leftOnlyPairs =+ concatMap+ (map (\lIdx -> (Just lIdx, Nothing)) . VU.toList)+ (M.elems (leftKeyCountsAndIndicesVec `M.difference` rightKeyCountsAndIndicesVec))+ rightOnlyPairs =+ concatMap+ (map (\rIdx -> (Nothing, Just rIdx)) . VU.toList)+ (M.elems (rightKeyCountsAndIndicesVec `M.difference` leftKeyCountsAndIndicesVec))+ pairs = matchedPairs ++ leftOnlyPairs ++ rightOnlyPairs+ expandedLeftIndicies = VB.fromList (map fst pairs)+ expandedRightIndicies = VB.fromList (map snd pairs)+ expandedLeft =+ left+ { columns = VB.map (D.atIndicesWithNulls expandedLeftIndicies) (D.columns left)+ , dataframeDimensions =+ (VB.length expandedLeftIndicies, snd (D.dataframeDimensions left))+ }+ expandedRight =+ right+ { columns = VB.map (D.atIndicesWithNulls expandedRightIndicies) (D.columns right)+ , dataframeDimensions =+ (VB.length expandedRightIndicies, snd (D.dataframeDimensions right))+ } leftColumns = D.columnNames left rightColumns = D.columnNames right initDf = expandedLeft
+ src/DataFrame/Operations/Permutation.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE OverloadedStrings #-}++module DataFrame.Operations.Permutation where++import qualified Data.List as L+import qualified Data.Text as T+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as VU++import Control.Exception (throw)+import DataFrame.Errors (DataFrameException (..))+import DataFrame.Internal.Column+import DataFrame.Internal.DataFrame (DataFrame (..))+import DataFrame.Internal.Row+import DataFrame.Operations.Core+import System.Random++-- | Sort order taken as a parameter by the 'sortBy' function.+data SortOrder = Ascending | Descending deriving (Eq)++{- | O(k log n) Sorts the dataframe by a given row.++> sortBy Ascending ["Age"] df+-}+sortBy ::+ SortOrder ->+ [T.Text] ->+ DataFrame ->+ DataFrame+sortBy order names df+ | any (`notElem` columnNames df) names =+ throw $+ ColumnNotFoundException+ (T.pack $ show $ names L.\\ columnNames df)+ "sortBy"+ (columnNames df)+ | otherwise =+ let+ indexes = sortedIndexes' (order == Ascending) (toRowVector names df)+ in+ df{columns = V.map (atIndicesStable indexes) (columns df)}++shuffle ::+ (RandomGen g) =>+ g ->+ DataFrame ->+ DataFrame+shuffle pureGen df =+ let+ indexes = shuffledIndices pureGen (fst (dimensions df))+ in+ 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))
− src/DataFrame/Operations/Sorting.hs
@@ -1,39 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module DataFrame.Operations.Sorting where--import qualified Data.List as L-import qualified Data.Text as T-import qualified Data.Vector as V--import Control.Exception (throw)-import DataFrame.Errors (DataFrameException (..))-import DataFrame.Internal.Column-import DataFrame.Internal.DataFrame (DataFrame (..))-import DataFrame.Internal.Row-import DataFrame.Operations.Core---- | Sort order taken as a parameter by the 'sortBy' function.-data SortOrder = Ascending | Descending deriving (Eq)--{- | O(k log n) Sorts the dataframe by a given row.--> sortBy Ascending ["Age"] df--}-sortBy ::- SortOrder ->- [T.Text] ->- DataFrame ->- DataFrame-sortBy order names df- | any (`notElem` columnNames df) names =- throw $- ColumnNotFoundException- (T.pack $ show $ names L.\\ columnNames df)- "sortBy"- (columnNames df)- | otherwise =- let- indexes = sortedIndexes' (order == Ascending) (toRowVector names df)- in- df{columns = V.map (atIndicesStable indexes) (columns df)}
src/DataFrame/Operations/Statistics.hs view
@@ -23,7 +23,13 @@ import Data.Type.Equality (TestEquality (testEquality), type (:~:) (Refl)) import DataFrame.Errors (DataFrameException (..)) import DataFrame.Internal.Column-import DataFrame.Internal.DataFrame (DataFrame (..), empty, getColumn)+import DataFrame.Internal.DataFrame (+ DataFrame (..),+ columnAsUnboxedVector,+ empty,+ getColumn,+ )+import DataFrame.Internal.Expression import DataFrame.Internal.Row (showValue, toAny) import DataFrame.Internal.Statistics import DataFrame.Internal.Types@@ -54,7 +60,7 @@ frequencies name df = let counts :: forall a. (Columnable a) => [(a, Int)]- counts = valueCounts name df+ counts = valueCounts (Col @a name) df calculatePercentage cs k = toAny $ toPct2dp (fromIntegral k / fromIntegral (P.sum $ map snd cs)) initDf = empty@@ -79,28 +85,76 @@ Just ((UnboxedColumn (column :: VU.Vector a))) -> freqs column -- | Calculates the mean of a given column as a standalone value.-mean :: T.Text -> DataFrame -> Maybe Double-mean = applyStatistic mean'+mean ::+ forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> DataFrame -> Double+mean (Col name) df = case columnAsUnboxedVector @a name df of+ Right xs -> mean' xs+ Left e -> throw e+mean expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn col) -> case toUnboxedVector @a col of+ Left e -> throw e+ Right xs -> mean' xs -- | Calculates the median of a given column as a standalone value.-median :: T.Text -> DataFrame -> Maybe Double-median = applyStatistic median'+median ::+ forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> DataFrame -> Double+median (Col name) df = case columnAsUnboxedVector @a name df of+ Right xs -> median' xs+ Left e -> throw e+median expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn col) -> case toUnboxedVector @a col of+ Left e -> throw e+ Right xs -> median' xs -- | Calculates the standard deviation of a given column as a standalone value.-standardDeviation :: T.Text -> DataFrame -> Maybe Double-standardDeviation = applyStatistic (sqrt . variance')+standardDeviation ::+ forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> DataFrame -> Double+standardDeviation (Col name) df = case columnAsUnboxedVector @a name df of+ Right xs -> (sqrt . variance') xs+ Left e -> throw e+standardDeviation expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn col) -> case toUnboxedVector @a col of+ Left e -> throw e+ Right xs -> (sqrt . variance') xs -- | Calculates the skewness of a given column as a standalone value.-skewness :: T.Text -> DataFrame -> Maybe Double-skewness = applyStatistic skewness'+skewness ::+ forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> DataFrame -> Double+skewness (Col name) df = case columnAsUnboxedVector @a name df of+ Right xs -> skewness' xs+ Left e -> throw e+skewness expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn col) -> case toUnboxedVector @a col of+ Left e -> throw e+ Right xs -> skewness' xs -- | Calculates the variance of a given column as a standalone value.-variance :: T.Text -> DataFrame -> Maybe Double-variance = applyStatistic variance'+variance ::+ forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> DataFrame -> Double+variance (Col name) df = case columnAsUnboxedVector @a name df of+ Right xs -> variance' xs+ Left e -> throw e+variance expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn col) -> case toUnboxedVector @a col of+ Left e -> throw e+ Right xs -> variance' xs -- | Calculates the inter-quartile range of a given column as a standalone value.-interQuartileRange :: T.Text -> DataFrame -> Maybe Double-interQuartileRange = applyStatistic interQuartileRange'+interQuartileRange ::+ forall a. (Columnable a, Real a, VU.Unbox a) => Expr a -> DataFrame -> Double+interQuartileRange (Col name) df = case columnAsUnboxedVector @a name df of+ Right xs -> interQuartileRange' xs+ Left e -> throw e+interQuartileRange expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn col) -> case toUnboxedVector @a col of+ Left e -> throw e+ Right xs -> interQuartileRange' xs -- | Calculates the Pearson's correlation coefficient between two given columns as a standalone value. correlation :: T.Text -> T.Text -> DataFrame -> Maybe Double@@ -126,18 +180,23 @@ -- | Calculates the sum of a given column as a standalone value. sum ::- forall a. (Columnable a, Num a, VU.Unbox a) => T.Text -> DataFrame -> Maybe a-sum name df = case getColumn name df of+ forall a. (Columnable a, Num a) => Expr a -> DataFrame -> a+sum (Col name) df = case getColumn name df of Nothing -> throw $ ColumnNotFoundException name "sum" (M.keys $ columnIndices df) Just ((UnboxedColumn (column :: VU.Vector a'))) -> case testEquality (typeRep @a') (typeRep @a) of- Just Refl -> Just $ VG.sum column- Nothing -> Nothing+ Just Refl -> VG.sum column+ Nothing -> 0 Just ((BoxedColumn (column :: V.Vector a'))) -> case testEquality (typeRep @a') (typeRep @a) of- Just Refl -> Just $ VG.sum column- Nothing -> Nothing+ Just Refl -> VG.sum column+ Nothing -> 0 Just ((OptionalColumn (column :: V.Vector (Maybe a')))) -> case testEquality (typeRep @a') (typeRep @a) of- Just Refl -> Just $ VG.sum (VG.map (fromMaybe 0) column)- Nothing -> Nothing+ Just Refl -> VG.sum (VG.map (fromMaybe 0) column)+ Nothing -> 0+sum expr df = case interpret df expr of+ Left e -> throw e+ Right (TColumn xs) -> case toVector @a @V.Vector xs of+ Left e -> throw e+ Right xs -> VG.sum xs applyStatistic :: (VU.Vector Double -> Double) -> T.Text -> DataFrame -> Maybe Double@@ -202,15 +261,15 @@ iqr = (-) <$> quartile3 <*> quartile1 in [ count- , mean name df+ , mean' <$> _getColumnAsDouble name df , min' , quartile1 , median' , quartile3 , max'- , standardDeviation name df+ , sqrt . variance' <$> _getColumnAsDouble name df , iqr- , skewness name df+ , skewness' <$> _getColumnAsDouble name df ] -- | Round a @Double@ to Specified Precision
src/DataFrame/Operations/Subset.hs view
@@ -89,19 +89,33 @@ forall a. (Columnable a) => -- | Column to filter by- T.Text ->+ Expr a -> -- | Filter condition (a -> Bool) -> -- | Dataframe to filter DataFrame -> DataFrame-filter filterColumnName condition df = case getColumn filterColumnName df of+filter (Col filterColumnName) condition df = case getColumn filterColumnName df of Nothing -> throw $ ColumnNotFoundException filterColumnName "filter" (M.keys $ columnIndices df) Just (BoxedColumn (column :: V.Vector b)) -> filterByVector filterColumnName column condition df Just (OptionalColumn (column :: V.Vector b)) -> filterByVector filterColumnName column condition df Just (UnboxedColumn (column :: VU.Vector b)) -> filterByVector filterColumnName column condition df+filter expr condition df =+ let+ (TColumn col) = case interpret @a df (normalize expr) of+ Left e -> throw e+ Right c -> c+ indexes = case findIndices condition col of+ Right ixs -> ixs+ Left e -> throw e+ c' = snd $ dataframeDimensions df+ in+ df+ { columns = V.map (atIndicesStable indexes) (columns df)+ , dataframeDimensions = (VU.length indexes, c')+ } filterByVector :: forall a b v.@@ -131,7 +145,7 @@ > filterBy even "x" df -}-filterBy :: (Columnable a) => (a -> Bool) -> T.Text -> DataFrame -> DataFrame+filterBy :: (Columnable a) => (a -> Bool) -> Expr a -> DataFrame -> DataFrame filterBy = flip filter {- | O(k) filters the dataframe with a boolean expression.@@ -162,7 +176,7 @@ filterJust name df = case getColumn name df of Nothing -> throw $ ColumnNotFoundException name "filterJust" (M.keys $ columnIndices df)- Just column@(OptionalColumn (col :: V.Vector (Maybe a))) -> filter @(Maybe a) name isJust df & apply @(Maybe a) fromJust name+ Just column@(OptionalColumn (col :: V.Vector (Maybe a))) -> filter (Col @(Maybe a) name) isJust df & apply @(Maybe a) fromJust name Just column -> df {- | O(k) returns all rows with `Nothing` in a give column.@@ -173,7 +187,7 @@ filterNothing name df = case getColumn name df of Nothing -> throw $ ColumnNotFoundException name "filterNothing" (M.keys $ columnIndices df)- Just (OptionalColumn (col :: V.Vector (Maybe a))) -> filter @(Maybe a) name isNothing df+ Just (OptionalColumn (col :: V.Vector (Maybe a))) -> filter (Col @(Maybe a) name) isNothing df _ -> df {- | O(n * k) removes all rows with `Nothing` from the dataframe.@@ -182,6 +196,13 @@ -} filterAllJust :: DataFrame -> DataFrame filterAllJust df = foldr filterJust df (columnNames df)++{- | O(n * k) keeps any row with a null value.++> filterAllNothing df+-}+filterAllNothing :: DataFrame -> DataFrame+filterAllNothing df = foldr filterNothing df (columnNames df) {- | O(k) cuts the dataframe in a cube of size (a, b) where a is the length and b is the width.
src/DataFrame/Operations/Transformations.hs view
@@ -1,4 +1,6 @@+{-# LANGUAGE ExplicitNamespaces #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -68,6 +70,16 @@ Left e -> throw e Right (TColumn value) -> insertColumn name value df +deriveMany :: [NamedExpr] -> DataFrame -> DataFrame+deriveMany exprs df =+ let+ f (name, Wrap (expr :: Expr a)) d =+ case interpret @a df expr of+ Left e -> throw e+ Right (TColumn value) -> insertColumn name value d+ in+ fold f exprs df+ -- | O(k * n) Apply a function to given column names in a dataframe. applyMany :: (Columnable b, Columnable c) =>@@ -162,11 +174,11 @@ impute :: forall b. (Columnable b) =>- T.Text ->+ Expr (Maybe b) -> b -> DataFrame -> DataFrame-impute columnName value df = case getColumn columnName df of+impute (Col columnName) value df = case getColumn columnName df of Nothing -> throw $ ColumnNotFoundException columnName "impute" (M.keys $ columnIndices df) Just (OptionalColumn _) -> case safeApply (fromMaybe value) columnName df of@@ -174,3 +186,4 @@ Left exception -> throw exception Right res -> res _ -> error $ "Cannot impute to a non-Empty column: " ++ T.unpack columnName+impute _ _ df = df
src/DataFrame/Operations/Typing.hs view
@@ -8,95 +8,112 @@ import qualified Data.Text as T import qualified Data.Vector as V-import qualified Data.Vector.Unboxed as VU -import Data.Either-import Data.Maybe+import Data.Maybe (fromMaybe) import Data.Time import Data.Type.Equality (TestEquality (..), type (:~:) (Refl))-import DataFrame.Internal.Column (Column (..))+import DataFrame.Internal.Column (Column (..), fromVector) import DataFrame.Internal.DataFrame (DataFrame (..)) import DataFrame.Internal.Parsing import Type.Reflection (typeRep) -parseDefaults :: Int -> Bool -> String -> DataFrame -> DataFrame+type DateFormat = String++parseDefaults :: Int -> Bool -> DateFormat -> DataFrame -> DataFrame parseDefaults n safeRead dateFormat df = df{columns = V.map (parseDefault n safeRead dateFormat) (columns df)} -parseDefault :: Int -> Bool -> String -> Column -> Column+parseDefault :: Int -> Bool -> DateFormat -> Column -> Column parseDefault n safeRead dateFormat (BoxedColumn (c :: V.Vector a)) = case (typeRep @a) `testEquality` (typeRep @T.Text) of Nothing -> case (typeRep @a) `testEquality` (typeRep @String) of- Just Refl -> parseFromExamples n dateFormat safeRead (V.map T.pack c)+ Just Refl -> parseFromExamples n safeRead dateFormat (V.map T.pack c) Nothing -> BoxedColumn c- Just Refl -> parseFromExamples n dateFormat safeRead c+ Just Refl -> parseFromExamples n safeRead dateFormat c parseDefault n safeRead dateFormat (OptionalColumn (c :: V.Vector (Maybe a))) = case (typeRep @a) `testEquality` (typeRep @T.Text) of Nothing -> case (typeRep @a) `testEquality` (typeRep @String) of- Just Refl -> parseFromExamples n dateFormat safeRead (V.map (T.pack . fromMaybe "") c)+ Just Refl -> parseFromExamples n safeRead dateFormat (V.map (T.pack . fromMaybe "") c) Nothing -> BoxedColumn c- Just Refl -> parseFromExamples n dateFormat safeRead (V.map (fromMaybe "") c)+ Just Refl -> parseFromExamples n safeRead dateFormat (V.map (fromMaybe "") c) parseDefault _ _ _ column = column -parseFromExamples :: Int -> String -> Bool -> V.Vector T.Text -> Column-parseFromExamples n dateFormat safeRead c- | 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 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- emptyToNothing' v = if isNullish v then Left v else Right v- parseTimeEither v = case parseTimeOpt dateFormat v of- Just v' -> Right v'- Nothing -> Left v- safeVector = V.map ((=<<) parseTimeEither . emptyToNothing') c- toMaybe (Left _) = Nothing- toMaybe (Right value) = Just value- lefts = V.filter isLeft safeVector- onlyNulls = (not (V.null lefts) && V.all (isNullish . fromLeft "non-null") lefts)- in- if safeRead- then- if onlyNulls- then OptionalColumn (V.map toMaybe safeVector)- else- if V.any isLeft safeVector- then BoxedColumn safeVector- else BoxedColumn (V.map (unsafeParseTime dateFormat) c)- else BoxedColumn (V.map (unsafeParseTime dateFormat) c)- | otherwise =- let- safeVector = V.map emptyToNothing c- hasNulls = V.any isNullish c- in- if safeRead && hasNulls then OptionalColumn safeVector else BoxedColumn c+parseFromExamples :: Int -> Bool -> DateFormat -> V.Vector T.Text -> Column+parseFromExamples n safeRead dateFormat cols =+ let+ converter = if safeRead then convertNullish else convertOnlyEmpty+ examples = V.map converter (V.take n cols)+ asMaybeText = V.map converter cols+ in+ case makeParsingAssumption dateFormat examples of+ IntAssumption -> handleIntAssumption asMaybeText+ DoubleAssumption -> handleDoubleAssumption asMaybeText+ TextAssumption -> handleTextAssumption asMaybeText+ DateAssumption -> handleDateAssumption dateFormat asMaybeText+ NoAssumption -> handleNoAssumption dateFormat asMaybeText++handleIntAssumption :: V.Vector (Maybe T.Text) -> Column+handleIntAssumption asMaybeText+ | parsableAsInt =+ maybe (fromVector asMaybeInt) fromVector (sequenceA asMaybeInt)+ | parsableAsDouble =+ maybe (fromVector asMaybeDouble) fromVector (sequenceA asMaybeDouble)+ | otherwise = maybe (fromVector asMaybeText) fromVector (sequenceA asMaybeText) where- examples = V.take n c+ asMaybeInt = V.map (>>= readInt) asMaybeText+ asMaybeDouble = V.map (>>= readDouble) asMaybeText+ parsableAsInt =+ vecSameConstructor asMaybeText asMaybeInt+ && vecSameConstructor asMaybeText asMaybeDouble+ parsableAsDouble = vecSameConstructor asMaybeText asMaybeDouble -emptyToNothing :: T.Text -> Maybe T.Text-emptyToNothing v = if isNullish v then Nothing else Just v+handleDoubleAssumption :: V.Vector (Maybe T.Text) -> Column+handleDoubleAssumption asMaybeText+ | parsableAsDouble =+ maybe (fromVector asMaybeDouble) fromVector (sequenceA asMaybeDouble)+ | otherwise = maybe (fromVector asMaybeText) fromVector (sequenceA asMaybeText)+ where+ asMaybeDouble = V.map (>>= readDouble) asMaybeText+ parsableAsDouble = vecSameConstructor asMaybeText asMaybeDouble -parseTimeOpt :: String -> T.Text -> Maybe Day+handleDateAssumption :: DateFormat -> V.Vector (Maybe T.Text) -> Column+handleDateAssumption dateFormat asMaybeText+ | parsableAsDate =+ maybe (fromVector asMaybeDate) fromVector (sequenceA asMaybeDate)+ | otherwise = maybe (fromVector asMaybeText) fromVector (sequenceA asMaybeText)+ where+ asMaybeDate = V.map (>>= parseTimeOpt dateFormat) asMaybeText+ parsableAsDate = vecSameConstructor asMaybeText asMaybeDate++handleTextAssumption :: V.Vector (Maybe T.Text) -> Column+handleTextAssumption asMaybeText = maybe (fromVector asMaybeText) fromVector (sequenceA asMaybeText)++handleNoAssumption :: DateFormat -> V.Vector (Maybe T.Text) -> Column+handleNoAssumption dateFormat asMaybeText+ -- No need to check for null values. If we are in this condition, that+ -- means that the examples consisted only of null values, so we can+ -- confidently know that this column must be an OptionalColumn+ | V.all (== Nothing) asMaybeText = fromVector asMaybeText+ | parsableAsInt = fromVector asMaybeInt+ | parsableAsDouble = fromVector asMaybeDouble+ | parsableAsDate = fromVector asMaybeDate+ | otherwise = fromVector asMaybeText+ where+ asMaybeInt = V.map (>>= readInt) asMaybeText+ asMaybeDouble = V.map (>>= readDouble) asMaybeText+ asMaybeDate = V.map (>>= parseTimeOpt dateFormat) asMaybeText+ parsableAsInt =+ vecSameConstructor asMaybeText asMaybeInt+ && vecSameConstructor asMaybeText asMaybeDouble+ parsableAsDouble = vecSameConstructor asMaybeText asMaybeDouble+ parsableAsDate = vecSameConstructor asMaybeText asMaybeDate++convertNullish :: T.Text -> Maybe T.Text+convertNullish v = if isNullish v then Nothing else Just v++convertOnlyEmpty :: T.Text -> Maybe T.Text+convertOnlyEmpty v = if v == "" then Nothing else Just v++parseTimeOpt :: DateFormat -> T.Text -> Maybe Day parseTimeOpt dateFormat s = parseTimeM {- Accept leading/trailing whitespace -} True@@ -104,7 +121,7 @@ dateFormat (T.unpack s) -unsafeParseTime :: String -> T.Text -> Day+unsafeParseTime :: DateFormat -> T.Text -> Day unsafeParseTime dateFormat s = parseTimeOrError {- Accept leading/trailing whitespace -} True@@ -112,8 +129,44 @@ dateFormat (T.unpack s) -countNonEmpty :: V.Vector (Maybe a) -> Int-countNonEmpty xs = V.length (V.filter isJust xs)+hasNullValues :: (Eq a) => V.Vector (Maybe a) -> Bool+hasNullValues = V.any (== Nothing) -equalNonEmpty :: V.Vector (Maybe a) -> V.Vector (Maybe b) -> Bool-equalNonEmpty xs ys = countNonEmpty xs == countNonEmpty ys+vecSameConstructor :: V.Vector (Maybe a) -> V.Vector (Maybe b) -> Bool+vecSameConstructor xs ys = (V.length xs == V.length ys) && V.and (V.zipWith hasSameConstructor xs ys)+ where+ hasSameConstructor :: Maybe a -> Maybe b -> Bool+ hasSameConstructor (Just _) (Just _) = True+ hasSameConstructor Nothing Nothing = True+ hasSameConstructor _ _ = False++makeParsingAssumption ::+ DateFormat -> V.Vector (Maybe T.Text) -> ParsingAssumption+makeParsingAssumption dateFormat asMaybeText+ -- All the examples are "NA", "Null", "", so we can't make any shortcut+ -- assumptions and just have to go the long way.+ | V.all (== Nothing) asMaybeText = NoAssumption+ -- After accounting for nulls, parsing for Ints and Doubles results in the+ -- same corresponding positions of Justs and Nothings, so we assume+ -- that the best way to parse is Int+ | vecSameConstructor asMaybeText asMaybeInt+ && vecSameConstructor asMaybeText asMaybeDouble =+ IntAssumption+ -- After accounting for nulls, the previous condition fails, so some (or none) can be parsed as Ints+ -- and some can be parsed as Doubles, so we make the assumpotion of doubles.+ | vecSameConstructor asMaybeText asMaybeDouble = DoubleAssumption+ -- After accounting for nulls, parsing for Dates results in the same corresponding+ -- positions of Justs and Nothings, so we assume that the best way to parse is Date.+ | vecSameConstructor asMaybeText asMaybeDate = DateAssumption+ | otherwise = TextAssumption+ where+ asMaybeInt = V.map (>>= readInt) asMaybeText+ asMaybeDouble = V.map (>>= readDouble) asMaybeText+ asMaybeDate = V.map (>>= parseTimeOpt dateFormat) asMaybeText++data ParsingAssumption+ = IntAssumption+ | DoubleAssumption+ | DateAssumption+ | NoAssumption+ | TextAssumption
tests/Functions.hs view
@@ -6,14 +6,9 @@ import DataFrame.Functions ( col, generatePrograms,- lit,- pow,- relu, sanitize, )-import DataFrame.Internal.Expression (add, mult) import Test.HUnit-import Prelude hiding (max, min) -- Test cases for the sanitize function sanitizeIdentifiers :: Test@@ -71,22 +66,8 @@ TestCase ( assertEqual "generatePrograms called with no existing programs"- [ col @Double "x"- , abs (col @Double "x")- , exp (mult (lit 0.5) (log (col @Double "x")))- , log (add (lit 1.0) (col @Double "x"))- , exp (col @Double "x")- , sin (col @Double "x")- , cos (col @Double "x")- , relu (col @Double "x")- , signum (col @Double "x")- , pow 2 (col @Double "x")- , pow 3 (col @Double "x")- , pow 4 (col @Double "x")- , pow 5 (col @Double "x")- , pow 6 (col @Double "x")- ]- (generatePrograms [] [col "x"] [] [])+ [col @Double "x"]+ (generatePrograms True [] [col "x"] [] []) ) tests :: [Test]
tests/Main.hs view
@@ -22,179 +22,4946 @@ import qualified Operations.Filter import qualified Operations.GroupBy import qualified Operations.InsertColumn-import qualified Operations.Merge-import qualified Operations.ReadCsv-import qualified Operations.Sort-import qualified Operations.Statistics-import qualified Operations.Take-import qualified Parquet--testData :: D.DataFrame-testData =- D.fromNamedColumns- [ ("test1", DI.fromList ([1 .. 26] :: [Int]))- , ("test2", DI.fromList ['a' .. 'z'])- ]---- Dimensions-correctDimensions :: Test-correctDimensions = TestCase (assertEqual "should be (26, 2)" (26, 2) (D.dimensions testData))--emptyDataframeDimensions :: Test-emptyDataframeDimensions = TestCase (assertEqual "should be (0, 0)" (0, 0) (D.dimensions D.empty))--dimensionsTest :: [Test]-dimensionsTest =- [ TestLabel "dimensions_correctDimensions" correctDimensions- , TestLabel "dimensions_emptyDataframeDimensions" emptyDataframeDimensions- ]---- parsing.-parseDate :: Test-parseDate =- let- expected =- DI.BoxedColumn- ( V.fromList- [fromGregorian 2020 02 14, fromGregorian 2021 02 14, fromGregorian 2022 02 14]- )- actual =- D.parseDefault- 10- True- "%Y-%m-%d"- (DI.fromVector (V.fromList ["2020-02-14" :: T.Text, "2021-02-14", "2022-02-14"]))- 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- expected =- DI.BoxedColumn- ( V.fromList- [ Right $ fromGregorian 2020 02 14- , Left ("2021-02-" :: T.Text)- , Right $ fromGregorian 2022 02 14- ]- )- actual =- D.parseDefault- 10- True- "%Y-%m-%d"- (DI.fromVector (V.fromList ["2020-02-14" :: T.Text, "2021-02-", "2022-02-14"]))- in- TestCase (assertEqual "Parses Either for gregorian date" expected actual)--incompleteDataParseMaybe :: Test-incompleteDataParseMaybe =- let- expected =- DI.OptionalColumn- ( V.fromList- [Just $ fromGregorian 2020 02 14, Nothing, Just $ fromGregorian 2022 02 14]- )- actual =- D.parseDefault- 10- True- "%Y-%m-%d"- (DI.fromVector (V.fromList ["2020-02-14" :: T.Text, "", "2022-02-14"]))- in- TestCase- (assertEqual "Parses Maybe for gregorian date with null/empty" expected actual)--parseTests :: [Test]-parseTests =- [ TestLabel "parseDate" parseDate- , TestLabel "incompleteDataParseMaybe" incompleteDataParseMaybe- , TestLabel "incompleteDataParseEither" incompleteDataParseEither- , TestLabel "parseInt" parseInt- , TestLabel "parseMaybeInt" parseMaybeInt- , TestLabel "parseDouble" parseDouble- , TestLabel "parseMaybeDouble" parseMaybeDouble- ]--tests :: Test-tests =- TestList $- dimensionsTest- ++ Operations.Aggregations.tests- ++ Operations.Apply.tests- ++ Operations.Core.tests- ++ Operations.Derive.tests- ++ Operations.Filter.tests- ++ Operations.GroupBy.tests- ++ Operations.InsertColumn.tests+import qualified Operations.Join+import qualified Operations.Merge+import qualified Operations.ReadCsv+import qualified Operations.Sort+import qualified Operations.Statistics+import qualified Operations.Take+import qualified Parquet++testData :: D.DataFrame+testData =+ D.fromNamedColumns+ [ ("test1", DI.fromList ([1 .. 26] :: [Int]))+ , ("test2", DI.fromList ['a' .. 'z'])+ ]++-- Dimensions+correctDimensions :: Test+correctDimensions = TestCase (assertEqual "should be (26, 2)" (26, 2) (D.dimensions testData))++emptyDataframeDimensions :: Test+emptyDataframeDimensions = TestCase (assertEqual "should be (0, 0)" (0, 0) (D.dimensions D.empty))++dimensionsTest :: [Test]+dimensionsTest =+ [ TestLabel "dimensions_correctDimensions" correctDimensions+ , TestLabel "dimensions_emptyDataframeDimensions" emptyDataframeDimensions+ ]++-- PARSING TESTS+------- 1. SIMPLE CASES+parseInts :: Test+parseInts =+ let afterParse :: [Int]+ afterParse = [1 .. 50]+ beforeParse :: [T.Text]+ beforeParse = T.pack . show <$> [1 .. 50]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints without missing values as UnboxedColumn of Ints"+ expected+ actual+ )++parseDoubles :: Test+parseDoubles =+ let afterParse :: [Double]+ afterParse = [1.0 .. 50.0] ++ [3.14, 2.22, 8.55, 23.3, 12.22222235049450945049504950]+ beforeParse :: [T.Text]+ beforeParse =+ T.pack . show+ <$> [1.0 .. 50.0] ++ [3.14, 2.22, 8.55, 23.3, 12.22222235049450945049504950]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Doubles without missing values as UnboxedColumn of Doubles"+ expected+ actual+ )++parseDates :: Test+parseDates =+ let afterParse :: [Day]+ afterParse =+ [ fromGregorian 2020 02 12+ , fromGregorian 2020 02 13+ , fromGregorian 2020 02 14+ , fromGregorian 2020 02 15+ , fromGregorian 2020 02 16+ , fromGregorian 2020 02 17+ , fromGregorian 2020 02 18+ , fromGregorian 2020 02 19+ , fromGregorian 2020 02 20+ , fromGregorian 2020 02 21+ , fromGregorian 2020 02 22+ , fromGregorian 2020 02 23+ , fromGregorian 2020 02 24+ , fromGregorian 2020 02 25+ , fromGregorian 2020 02 26+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Dates without missing values as BoxedColumn of Days"+ expected+ actual+ )++parseTexts :: Test+parseTexts =+ let afterParse :: [T.Text]+ afterParse =+ [ "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , "Surrender now or prepare to fight!"+ , "Meowth, that's right!"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , "Surrender now or prepare to fight!"+ , "Meowth, that's right!"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Text without missing values as BoxedColumn of Text"+ expected+ actual+ )++--- 2. COMBINATION CASES+parseIntsAndDoublesAsDoubles :: Test+parseIntsAndDoublesAsDoubles =+ let afterParse :: [Double]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ , 1.0+ , 2.0+ , 3.0+ , 4.0+ , 5.0+ , 6.0+ , 7.0+ , 8.0+ , 9.0+ , 10.0+ , 11.0+ , 12.0+ , 13.0+ , 14.0+ , 15.0+ , 16.0+ , 17.0+ , 18.0+ , 19.0+ , 20.0+ , 21.0+ , 22.0+ , 23.0+ , 24.0+ , 25.0+ , 26.0+ , 27.0+ , 28.0+ , 29.0+ , 30.0+ , 31.0+ , 32.0+ , 33.0+ , 34.0+ , 35.0+ , 36.0+ , 37.0+ , 38.0+ , 39.0+ , 40.0+ , 41.0+ , 42.0+ , 43.0+ , 44.0+ , 45.0+ , 46.0+ , 47.0+ , 48.0+ , 49.0+ , 50.0+ , 3.14+ , 2.22+ , 8.55+ , 23.3+ , 12.22222235049450945049504950+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints and Doubles as UnboxedColumn of Doubles"+ expected+ actual+ )++parseIntsAndDatesAsTexts :: Test+parseIntsAndDatesAsTexts =+ let afterParse :: [T.Text]+ afterParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints and Dates as BoxedColumn of Texts"+ expected+ actual+ )++parseTextsAndDoublesAsTexts :: Test+parseTextsAndDoublesAsTexts =+ let afterParse :: [T.Text]+ afterParse =+ [ "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Texts and Doubles as BoxedColumn of Texts"+ expected+ actual+ )++parseDatesAndTextsAsTexts :: Test+parseDatesAndTextsAsTexts =+ let afterParse :: [T.Text]+ afterParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ , "Jessie"+ , "James"+ , "Meowth"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ , "Jessie"+ , "James"+ , "Meowth"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Dates and Texts as BoxedColumn of Texts"+ expected+ actual+ )++-- 3A. PARSING WITH SAFEREAD OFF++parseIntsWithoutSafeRead :: Test+parseIntsWithoutSafeRead =+ let afterParse :: [Int]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints without missing values as UnboxedColumn of Ints, when safeRead is off"+ expected+ actual+ )++parseDoublesWithoutSafeRead :: Test+parseDoublesWithoutSafeRead =+ let afterParse :: [Double]+ afterParse =+ [ 1.0+ , 2.0+ , 3.0+ , 4.0+ , 5.0+ , 6.0+ , 7.0+ , 8.0+ , 9.0+ , 10.0+ , 11.0+ , 12.0+ , 13.0+ , 14.0+ , 15.0+ , 16.0+ , 17.0+ , 18.0+ , 19.0+ , 20.0+ , 21.0+ , 22.0+ , 23.0+ , 24.0+ , 25.0+ , 26.0+ , 27.0+ , 28.0+ , 29.0+ , 30.0+ , 31.0+ , 32.0+ , 33.0+ , 34.0+ , 35.0+ , 36.0+ , 37.0+ , 38.0+ , 39.0+ , 40.0+ , 41.0+ , 42.0+ , 43.0+ , 44.0+ , 45.0+ , 46.0+ , 47.0+ , 48.0+ , 49.0+ , 50.0+ , 3.14+ , 2.22+ , 8.55+ , 23.3+ , 12.22222235049450945049504950+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Doubles without missing values as UnboxedColumn of Doubles, when safeRead is off"+ expected+ actual+ )++parseDatesWithoutSafeRead :: Test+parseDatesWithoutSafeRead =+ let afterParse :: [Day]+ afterParse =+ [ fromGregorian 2020 02 12+ , fromGregorian 2020 02 13+ , fromGregorian 2020 02 14+ , fromGregorian 2020 02 15+ , fromGregorian 2020 02 16+ , fromGregorian 2020 02 17+ , fromGregorian 2020 02 18+ , fromGregorian 2020 02 19+ , fromGregorian 2020 02 20+ , fromGregorian 2020 02 21+ , fromGregorian 2020 02 22+ , fromGregorian 2020 02 23+ , fromGregorian 2020 02 24+ , fromGregorian 2020 02 25+ , fromGregorian 2020 02 26+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Dates without missing values as BoxedColumn of Days"+ expected+ actual+ )++parseTextsWithoutSafeRead :: Test+parseTextsWithoutSafeRead =+ let afterParse :: [T.Text]+ afterParse =+ [ "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , "Surrender now or prepare to fight!"+ , "Meowth, that's right!"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , "Surrender now or prepare to fight!"+ , "Meowth, that's right!"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Text without missing values as BoxedColumn of Text"+ expected+ actual+ )++parseIntsAndEmptyStringsWithoutSafeRead :: Test+parseIntsAndEmptyStringsWithoutSafeRead =+ let afterParse :: [Maybe Int]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1+ , Just 2+ , Just 3+ , Just 4+ , Just 5+ , Just 6+ , Just 7+ , Just 8+ , Just 9+ , Just 10+ , Just 11+ , Just 12+ , Just 13+ , Just 14+ , Just 15+ , Just 16+ , Just 17+ , Just 18+ , Just 19+ , Just 20+ , Just 21+ , Just 22+ , Just 23+ , Just 24+ , Just 25+ , Just 26+ , Just 27+ , Just 28+ , Just 29+ , Just 30+ , Just 31+ , Just 32+ , Just 33+ , Just 34+ , Just 35+ , Just 36+ , Just 37+ , Just 38+ , Just 39+ , Just 40+ , Just 41+ , Just 42+ , Just 43+ , Just 44+ , Just 45+ , Just 46+ , Just 47+ , Just 48+ , Just 49+ , Just 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , ""+ , ""+ , ""+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints and empty strings as OptionalColumn of Ints, when safeRead is off"+ expected+ actual+ )++parseIntsAndDoublesAndEmptyStringsWithoutSafeRead :: Test+parseIntsAndDoublesAndEmptyStringsWithoutSafeRead =+ let afterParse :: [Maybe Double]+ afterParse =+ [ Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Nothing+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Nothing+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Nothing+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Nothing+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Nothing+ , Just 3.14+ , Just 2.22+ , Just 8.55+ , Just 23.3+ , Just 12.22222235049451+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , ""+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , ""+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , ""+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , ""+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , ""+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses combination of Ints, Doubles and empty strings as OptionalColumn of Doubles, when safeRead is off"+ expected+ actual+ )++parseDatesAndEmptyStringsWithoutSafeRead :: Test+parseDatesAndEmptyStringsWithoutSafeRead =+ let afterParse :: [Maybe Day]+ afterParse =+ [ Just $ fromGregorian 2020 02 12+ , Just $ fromGregorian 2020 02 13+ , Just $ fromGregorian 2020 02 14+ , Nothing+ , Just $ fromGregorian 2020 02 15+ , Just $ fromGregorian 2020 02 16+ , Just $ fromGregorian 2020 02 17+ , Nothing+ , Just $ fromGregorian 2020 02 18+ , Just $ fromGregorian 2020 02 19+ , Just $ fromGregorian 2020 02 20+ , Nothing+ , Just $ fromGregorian 2020 02 21+ , Just $ fromGregorian 2020 02 22+ , Just $ fromGregorian 2020 02 23+ , Nothing+ , Just $ fromGregorian 2020 02 24+ , Just $ fromGregorian 2020 02 25+ , Just $ fromGregorian 2020 02 26+ , Nothing+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , ""+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , ""+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , ""+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , ""+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ , ""+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Dates and Empty Strings as OptionalColumn of Dates, with safeRead off"+ expected+ actual+ )++parseTextsAndEmptyStringsWithoutSafeRead :: Test+parseTextsAndEmptyStringsWithoutSafeRead =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Nothing+ , Just "To"+ , Just "protect"+ , Just "the"+ , Just "world"+ , Just "from"+ , Just "devastation"+ , Nothing+ , Just "To"+ , Just "unite"+ , Just "all"+ , Just "people"+ , Just "within"+ , Just "our"+ , Just "nation"+ , Nothing+ , Just "To"+ , Just "denounce"+ , Just "the"+ , Just "evils"+ , Just "of"+ , Just "truth"+ , Just "and"+ , Just "love"+ , Nothing+ , Just "To"+ , Just "extend"+ , Just "our"+ , Just "reach"+ , Just "to"+ , Just "the"+ , Just "stars"+ , Just "above"+ , Nothing+ , Just "JESSIE!"+ , Just "JAMES!"+ , Just "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , Nothing+ , Just "Surrender now or prepare to fight!"+ , Nothing+ , Just "Meowth, that's right!"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , ""+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , ""+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , ""+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , ""+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , ""+ , "Surrender now or prepare to fight!"+ , ""+ , "Meowth, that's right!"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Texts and Empty Strings as OptionalColumn of Text, with safeRead off"+ expected+ actual+ )++parseIntsAndNullishStringsWithoutSafeRead :: Test+parseIntsAndNullishStringsWithoutSafeRead =+ let afterParse :: [T.Text]+ afterParse =+ [ "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints with nullish values as BoxedColumn of Texts, when safeRead is off"+ expected+ actual+ )++parseIntsAndDoublesAndNullishStringsWithoutSafeRead :: Test+parseIntsAndDoublesAndNullishStringsWithoutSafeRead =+ let afterParse :: [T.Text]+ afterParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "Nothing"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "N/A"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "NULL"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "null"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "NAN"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "Nothing"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "N/A"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "NULL"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "null"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "NAN"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses combination of Ints, Doubles and empty strings as OptionalColumn of Doubles, when safeRead is off"+ expected+ actual+ )++parseIntsAndNullishAndEmptyStringsWithoutSafeRead :: Test+parseIntsAndNullishAndEmptyStringsWithoutSafeRead =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Just "N/A"+ , Just "N/A"+ , Just "N/A"+ , Just "N/A"+ , Just "N/A"+ , Nothing+ , Just "1"+ , Just "2"+ , Just "3"+ , Just "4"+ , Just "5"+ , Just "6"+ , Just "7"+ , Just "8"+ , Just "9"+ , Just "10"+ , Nothing+ , Just "11"+ , Just "12"+ , Just "13"+ , Just "14"+ , Just "15"+ , Just "16"+ , Just "17"+ , Just "18"+ , Just "19"+ , Just "20"+ , Nothing+ , Just "21"+ , Just "22"+ , Just "23"+ , Just "24"+ , Just "25"+ , Just "26"+ , Just "27"+ , Just "28"+ , Just "29"+ , Just "30"+ , Nothing+ , Just "31"+ , Just "32"+ , Just "33"+ , Just "34"+ , Just "35"+ , Just "36"+ , Just "37"+ , Just "38"+ , Just "39"+ , Just "40"+ , Nothing+ , Just "41"+ , Just "42"+ , Just "43"+ , Just "44"+ , Just "45"+ , Just "46"+ , Just "47"+ , Just "48"+ , Just "49"+ , Just "50"+ , Nothing+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , ""+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , ""+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , ""+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , ""+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , ""+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints with nullish values AND empty strings as OptionalColumn of Texts, when safeRead is off"+ expected+ actual+ )++parseTextsAndEmptyAndNullishStringsWithoutSafeRead :: Test+parseTextsAndEmptyAndNullishStringsWithoutSafeRead =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Nothing+ , Just "To"+ , Just "protect"+ , Just "the"+ , Just "world"+ , Just "from"+ , Just "devastation"+ , Nothing+ , Just "To"+ , Just "unite"+ , Just "all"+ , Just "people"+ , Just "within"+ , Just "our"+ , Just "nation"+ , Nothing+ , Just "To"+ , Just "denounce"+ , Just "the"+ , Just "evils"+ , Just "of"+ , Just "truth"+ , Just "and"+ , Just "love"+ , Nothing+ , Just "To"+ , Just "extend"+ , Just "our"+ , Just "reach"+ , Just "to"+ , Just "the"+ , Just "stars"+ , Just "above"+ , Nothing+ , Just "JESSIE!"+ , Just "JAMES!"+ , Just "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , Nothing+ , Just "Surrender now or prepare to fight!"+ , Nothing+ , Just "Meowth, that's right!"+ , Just "NaN"+ , Just "Nothing"+ , Just "N/A"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , ""+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , ""+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , ""+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , ""+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , ""+ , "Surrender now or prepare to fight!"+ , ""+ , "Meowth, that's right!"+ , "NaN"+ , "Nothing"+ , "N/A"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Texts and Empty Strings as OptionalColumn of Text, with safeRead off"+ expected+ actual+ )++-- 3B. PARSING WITH SAFEREAD ON+parseIntsAndEmptyStringsWithSafeRead :: Test+parseIntsAndEmptyStringsWithSafeRead =+ let afterParse :: [Maybe Int]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1+ , Just 2+ , Just 3+ , Just 4+ , Just 5+ , Just 6+ , Just 7+ , Just 8+ , Just 9+ , Just 10+ , Just 11+ , Just 12+ , Just 13+ , Just 14+ , Just 15+ , Just 16+ , Just 17+ , Just 18+ , Just 19+ , Just 20+ , Just 21+ , Just 22+ , Just 23+ , Just 24+ , Just 25+ , Just 26+ , Just 27+ , Just 28+ , Just 29+ , Just 30+ , Just 31+ , Just 32+ , Just 33+ , Just 34+ , Just 35+ , Just 36+ , Just 37+ , Just 38+ , Just 39+ , Just 40+ , Just 41+ , Just 42+ , Just 43+ , Just 44+ , Just 45+ , Just 46+ , Just 47+ , Just 48+ , Just 49+ , Just 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , ""+ , ""+ , ""+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints and empty strings as OptionalColumn of Ints, when safeRead is on"+ expected+ actual+ )++parseIntsAndDoublesAndEmptyStringsWithSafeRead :: Test+parseIntsAndDoublesAndEmptyStringsWithSafeRead =+ let afterParse :: [Maybe Double]+ afterParse =+ [ Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Nothing+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Nothing+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Nothing+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Nothing+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Nothing+ , Just 3.14+ , Just 2.22+ , Just 8.55+ , Just 23.3+ , Just 12.22222235049451+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , ""+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , ""+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , ""+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , ""+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , ""+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses combination of Ints, Doubles and empty strings as OptionalColumn of Doubles, when safeRead is on"+ expected+ actual+ )++parseDatesAndEmptyStringsWithSafeRead :: Test+parseDatesAndEmptyStringsWithSafeRead =+ let afterParse :: [Maybe Day]+ afterParse =+ [ Just $ fromGregorian 2020 02 12+ , Just $ fromGregorian 2020 02 13+ , Just $ fromGregorian 2020 02 14+ , Nothing+ , Just $ fromGregorian 2020 02 15+ , Just $ fromGregorian 2020 02 16+ , Just $ fromGregorian 2020 02 17+ , Nothing+ , Just $ fromGregorian 2020 02 18+ , Just $ fromGregorian 2020 02 19+ , Just $ fromGregorian 2020 02 20+ , Nothing+ , Just $ fromGregorian 2020 02 21+ , Just $ fromGregorian 2020 02 22+ , Just $ fromGregorian 2020 02 23+ , Nothing+ , Just $ fromGregorian 2020 02 24+ , Just $ fromGregorian 2020 02 25+ , Just $ fromGregorian 2020 02 26+ , Nothing+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , ""+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , ""+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , ""+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , ""+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ , ""+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Dates and Empty Strings as OptionalColumn of Dates, with safeRead on"+ expected+ actual+ )++parseTextsAndEmptyStringsWithSafeRead :: Test+parseTextsAndEmptyStringsWithSafeRead =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Nothing+ , Just "To"+ , Just "protect"+ , Just "the"+ , Just "world"+ , Just "from"+ , Just "devastation"+ , Nothing+ , Just "To"+ , Just "unite"+ , Just "all"+ , Just "people"+ , Just "within"+ , Just "our"+ , Just "nation"+ , Nothing+ , Just "To"+ , Just "denounce"+ , Just "the"+ , Just "evils"+ , Just "of"+ , Just "truth"+ , Just "and"+ , Just "love"+ , Nothing+ , Just "To"+ , Just "extend"+ , Just "our"+ , Just "reach"+ , Just "to"+ , Just "the"+ , Just "stars"+ , Just "above"+ , Nothing+ , Just "JESSIE!"+ , Just "JAMES!"+ , Just "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , Nothing+ , Just "Surrender now or prepare to fight!"+ , Nothing+ , Just "Meowth, that's right!"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , ""+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , ""+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , ""+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , ""+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , ""+ , "Surrender now or prepare to fight!"+ , ""+ , "Meowth, that's right!"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Texts and Empty Strings as OptionalColumn of Text, with safeRead on"+ expected+ actual+ )++parseIntsAndNullishStringsWithSafeRead :: Test+parseIntsAndNullishStringsWithSafeRead =+ let afterParse :: [Maybe Int]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1+ , Just 2+ , Just 3+ , Just 4+ , Just 5+ , Just 6+ , Just 7+ , Just 8+ , Just 9+ , Just 10+ , Just 11+ , Just 12+ , Just 13+ , Just 14+ , Just 15+ , Just 16+ , Just 17+ , Just 18+ , Just 19+ , Just 20+ , Just 21+ , Just 22+ , Just 23+ , Just 24+ , Just 25+ , Just 26+ , Just 27+ , Just 28+ , Just 29+ , Just 30+ , Just 31+ , Just 32+ , Just 33+ , Just 34+ , Just 35+ , Just 36+ , Just 37+ , Just 38+ , Just 39+ , Just 40+ , Just 41+ , Just 42+ , Just 43+ , Just 44+ , Just 45+ , Just 46+ , Just 47+ , Just 48+ , Just 49+ , Just 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints with nullish values as OptionalColumn of Ints, when safeRead is on"+ expected+ actual+ )++parseIntsAndDoublesAndNullishStringsWithSafeRead :: Test+parseIntsAndDoublesAndNullishStringsWithSafeRead =+ let afterParse :: [Maybe Double]+ afterParse =+ [ Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Nothing+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Nothing+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Nothing+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Nothing+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Nothing+ , Just 3.14+ , Just 2.22+ , Just 8.55+ , Just 23.3+ , Just 12.03+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "Nothing"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "N/A"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "NULL"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "null"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "NAN"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.03"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses combination of Ints, Doubles and empty strings as OptionalColumn of Doubles, when safeRead is off"+ expected+ actual+ )++parseIntsAndNullishAndEmptyStringsWithSafeRead :: Test+parseIntsAndNullishAndEmptyStringsWithSafeRead =+ let afterParse :: [Maybe Int]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1+ , Just 2+ , Just 3+ , Just 4+ , Just 5+ , Just 6+ , Just 7+ , Just 8+ , Just 9+ , Just 10+ , Nothing+ , Just 11+ , Just 12+ , Just 13+ , Just 14+ , Just 15+ , Just 16+ , Just 17+ , Just 18+ , Just 19+ , Just 20+ , Nothing+ , Just 21+ , Just 22+ , Just 23+ , Just 24+ , Just 25+ , Just 26+ , Just 27+ , Just 28+ , Just 29+ , Just 30+ , Nothing+ , Just 31+ , Just 32+ , Just 33+ , Just 34+ , Just 35+ , Just 36+ , Just 37+ , Just 38+ , Just 39+ , Just 40+ , Nothing+ , Just 41+ , Just 42+ , Just 43+ , Just 44+ , Just 45+ , Just 46+ , Just 47+ , Just 48+ , Just 49+ , Just 50+ , Nothing+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "Nothing"+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , ""+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , ""+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , ""+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , ""+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , ""+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints with nullish values AND empty strings as OptionalColumn of Ints, when safeRead is on"+ expected+ actual+ )++parseIntsAndDoublesAndNullishAndEmptyStringsWithSafeRead :: Test+parseIntsAndDoublesAndNullishAndEmptyStringsWithSafeRead =+ let afterParse :: [Maybe Double]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1+ , Just 2+ , Just 3+ , Just 4+ , Just 5+ , Just 6+ , Just 7+ , Just 8+ , Just 9+ , Just 10+ , Nothing+ , Just 11+ , Just 12+ , Just 13+ , Just 14+ , Just 15+ , Just 16+ , Just 17+ , Just 18+ , Just 19+ , Just 20+ , Nothing+ , Just 21+ , Just 22+ , Just 23+ , Just 24+ , Just 25+ , Just 26+ , Just 27+ , Just 28+ , Just 29+ , Just 30+ , Nothing+ , Just 3.14+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "N/A"+ , "N/A"+ , "N/A"+ , "N/A"+ , "Nothing"+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , ""+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , ""+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , ""+ , "3.14"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints and Doubles with nullish values AND empty strings as OptionalColumn of Doubles, when safeRead is on"+ expected+ actual+ )++parseTextsAndEmptyAndNullishStringsWithSafeRead :: Test+parseTextsAndEmptyAndNullishStringsWithSafeRead =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Nothing+ , Just "To"+ , Just "protect"+ , Just "the"+ , Just "world"+ , Just "from"+ , Just "devastation"+ , Nothing+ , Just "To"+ , Just "unite"+ , Just "all"+ , Just "people"+ , Just "within"+ , Just "our"+ , Just "nation"+ , Nothing+ , Just "To"+ , Just "denounce"+ , Just "the"+ , Just "evils"+ , Just "of"+ , Just "truth"+ , Just "and"+ , Just "love"+ , Nothing+ , Just "To"+ , Just "extend"+ , Just "our"+ , Just "reach"+ , Just "to"+ , Just "the"+ , Just "stars"+ , Just "above"+ , Nothing+ , Just "JESSIE!"+ , Just "JAMES!"+ , Just "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , Nothing+ , Just "Surrender now or prepare to fight!"+ , Nothing+ , Just "Meowth, that's right!"+ , Nothing+ , Nothing+ , Nothing+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , "To"+ , "protect"+ , "the"+ , "world"+ , "from"+ , "devastation"+ , ""+ , "To"+ , "unite"+ , "all"+ , "people"+ , "within"+ , "our"+ , "nation"+ , ""+ , "To"+ , "denounce"+ , "the"+ , "evils"+ , "of"+ , "truth"+ , "and"+ , "love"+ , ""+ , "To"+ , "extend"+ , "our"+ , "reach"+ , "to"+ , "the"+ , "stars"+ , "above"+ , ""+ , "JESSIE!"+ , "JAMES!"+ , "TEAM ROCKET BLASTS OFF AT THE SPEED OF LIGHT!"+ , ""+ , "Surrender now or prepare to fight!"+ , ""+ , "Meowth, that's right!"+ , "NaN"+ , "Nothing"+ , "N/A"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Texts and Empty Strings as OptionalColumn of Text, with safeRead off"+ expected+ actual+ )++-- 4. PARSING SHOULD NOT DEPEND ON THE NUMBER OF EXAMPLES.+parseIntsWithOneExample :: Test+parseIntsWithOneExample =+ let afterParse :: [Int]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 1 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints without missing values as UnboxedColumn of Ints with only one example"+ expected+ actual+ )++parseIntsWithTwentyFiveExamples :: Test+parseIntsWithTwentyFiveExamples =+ let afterParse :: [Int]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 25 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints without missing values as UnboxedColumn of Ints with some examples"+ expected+ actual+ )++parseIntsWithFortyNineExamples :: Test+parseIntsWithFortyNineExamples =+ let afterParse :: [Int]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 49 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Ints without missing values as UnboxedColumn of Ints with many examples"+ expected+ actual+ )++parseDatesWithOneExample :: Test+parseDatesWithOneExample =+ let afterParse :: [Day]+ afterParse =+ [ fromGregorian 2020 02 12+ , fromGregorian 2020 02 13+ , fromGregorian 2020 02 14+ , fromGregorian 2020 02 15+ , fromGregorian 2020 02 16+ , fromGregorian 2020 02 17+ , fromGregorian 2020 02 18+ , fromGregorian 2020 02 19+ , fromGregorian 2020 02 20+ , fromGregorian 2020 02 21+ , fromGregorian 2020 02 22+ , fromGregorian 2020 02 23+ , fromGregorian 2020 02 24+ , fromGregorian 2020 02 25+ , fromGregorian 2020 02 26+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 1 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Dates without missing values as BoxedColumn of Days with only one example"+ expected+ actual+ )++parseDatesWithFifteenExamples :: Test+parseDatesWithFifteenExamples =+ let afterParse :: [Day]+ afterParse =+ [ fromGregorian 2020 02 12+ , fromGregorian 2020 02 13+ , fromGregorian 2020 02 14+ , fromGregorian 2020 02 15+ , fromGregorian 2020 02 16+ , fromGregorian 2020 02 17+ , fromGregorian 2020 02 18+ , fromGregorian 2020 02 19+ , fromGregorian 2020 02 20+ , fromGregorian 2020 02 21+ , fromGregorian 2020 02 22+ , fromGregorian 2020 02 23+ , fromGregorian 2020 02 24+ , fromGregorian 2020 02 25+ , fromGregorian 2020 02 26+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "2020-02-12"+ , "2020-02-13"+ , "2020-02-14"+ , "2020-02-15"+ , "2020-02-16"+ , "2020-02-17"+ , "2020-02-18"+ , "2020-02-19"+ , "2020-02-20"+ , "2020-02-21"+ , "2020-02-22"+ , "2020-02-23"+ , "2020-02-24"+ , "2020-02-25"+ , "2020-02-26"+ ]+ expected = DI.BoxedColumn $ V.fromList afterParse+ actual = D.parseDefault 15 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses Dates without missing values as BoxedColumn of Days with many examples"+ expected+ actual+ )++parseIntsAndDoublesAsDoublesWithOneExample :: Test+parseIntsAndDoublesAsDoublesWithOneExample =+ let afterParse :: [Double]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ , 1.0+ , 2.0+ , 3.0+ , 4.0+ , 5.0+ , 6.0+ , 7.0+ , 8.0+ , 9.0+ , 10.0+ , 11.0+ , 12.0+ , 13.0+ , 14.0+ , 15.0+ , 16.0+ , 17.0+ , 18.0+ , 19.0+ , 20.0+ , 21.0+ , 22.0+ , 23.0+ , 24.0+ , 25.0+ , 26.0+ , 27.0+ , 28.0+ , 29.0+ , 30.0+ , 31.0+ , 32.0+ , 33.0+ , 34.0+ , 35.0+ , 36.0+ , 37.0+ , 38.0+ , 39.0+ , 40.0+ , 41.0+ , 42.0+ , 43.0+ , 44.0+ , 45.0+ , 46.0+ , 47.0+ , 48.0+ , 49.0+ , 50.0+ , 3.14+ , 2.22+ , 8.55+ , 23.3+ , 12.22222235049450945049504950+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 1 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints and Doubles as UnboxedColumn of Doubles with just one example"+ expected+ actual+ )++parseIntsAndDoublesAsDoublesWithManyExamples :: Test+parseIntsAndDoublesAsDoublesWithManyExamples =+ let afterParse :: [Double]+ afterParse =+ [ 1+ , 2+ , 3+ , 4+ , 5+ , 6+ , 7+ , 8+ , 9+ , 10+ , 11+ , 12+ , 13+ , 14+ , 15+ , 16+ , 17+ , 18+ , 19+ , 20+ , 21+ , 22+ , 23+ , 24+ , 25+ , 26+ , 27+ , 28+ , 29+ , 30+ , 31+ , 32+ , 33+ , 34+ , 35+ , 36+ , 37+ , 38+ , 39+ , 40+ , 41+ , 42+ , 43+ , 44+ , 45+ , 46+ , 47+ , 48+ , 49+ , 50+ , 1.0+ , 2.0+ , 3.0+ , 4.0+ , 5.0+ , 6.0+ , 7.0+ , 8.0+ , 9.0+ , 10.0+ , 11.0+ , 12.0+ , 13.0+ , 14.0+ , 15.0+ , 16.0+ , 17.0+ , 18.0+ , 19.0+ , 20.0+ , 21.0+ , 22.0+ , 23.0+ , 24.0+ , 25.0+ , 26.0+ , 27.0+ , 28.0+ , 29.0+ , 30.0+ , 31.0+ , 32.0+ , 33.0+ , 34.0+ , 35.0+ , 36.0+ , 37.0+ , 38.0+ , 39.0+ , 40.0+ , 41.0+ , 42.0+ , 43.0+ , 44.0+ , 45.0+ , 46.0+ , 47.0+ , 48.0+ , 49.0+ , 50.0+ , 3.14+ , 2.22+ , 8.55+ , 23.3+ , 12.22222235049450945049504950+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.UnboxedColumn $ VU.fromList afterParse+ actual = D.parseDefault 50 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints and Doubles as UnboxedColumn of Doubles with just one example"+ expected+ actual+ )++parseIntsAndDoublesAndEmptyStringsAsDoublesWithOneExampleWithSafeReadOff :: Test+parseIntsAndDoublesAndEmptyStringsAsDoublesWithOneExampleWithSafeReadOff =+ let afterParse :: [Maybe Double]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Just 3.14+ , Just 2.22+ , Just 8.55+ , Just 23.3+ , Just 12.22222235049451+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 1 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints and Doubles as UnboxedColumn of Doubles with just one example"+ expected+ actual+ )++parseIntsAndDoublesAndEmptyStringsAsDoublesWithManyExamplesWithSafeReadOff ::+ Test+parseIntsAndDoublesAndEmptyStringsAsDoublesWithManyExamplesWithSafeReadOff =+ let afterParse :: [Maybe Double]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Just 1.0+ , Just 2.0+ , Just 3.0+ , Just 4.0+ , Just 5.0+ , Just 6.0+ , Just 7.0+ , Just 8.0+ , Just 9.0+ , Just 10.0+ , Just 11.0+ , Just 12.0+ , Just 13.0+ , Just 14.0+ , Just 15.0+ , Just 16.0+ , Just 17.0+ , Just 18.0+ , Just 19.0+ , Just 20.0+ , Just 21.0+ , Just 22.0+ , Just 23.0+ , Just 24.0+ , Just 25.0+ , Just 26.0+ , Just 27.0+ , Just 28.0+ , Just 29.0+ , Just 30.0+ , Just 31.0+ , Just 32.0+ , Just 33.0+ , Just 34.0+ , Just 35.0+ , Just 36.0+ , Just 37.0+ , Just 38.0+ , Just 39.0+ , Just 40.0+ , Just 41.0+ , Just 42.0+ , Just 43.0+ , Just 44.0+ , Just 45.0+ , Just 46.0+ , Just 47.0+ , Just 48.0+ , Just 49.0+ , Just 50.0+ , Just 3.14+ , Just 2.22+ , Just 8.55+ , Just 23.3+ , Just 12.22222235049451+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "21"+ , "22"+ , "23"+ , "24"+ , "25"+ , "26"+ , "27"+ , "28"+ , "29"+ , "30"+ , "31"+ , "32"+ , "33"+ , "34"+ , "35"+ , "36"+ , "37"+ , "38"+ , "39"+ , "40"+ , "41"+ , "42"+ , "43"+ , "44"+ , "45"+ , "46"+ , "47"+ , "48"+ , "49"+ , "50"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "31.0"+ , "32.0"+ , "33.0"+ , "34.0"+ , "35.0"+ , "36.0"+ , "37.0"+ , "38.0"+ , "39.0"+ , "40.0"+ , "41.0"+ , "42.0"+ , "43.0"+ , "44.0"+ , "45.0"+ , "46.0"+ , "47.0"+ , "48.0"+ , "49.0"+ , "50.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 30 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints and Doubles as UnboxedColumn of Doubles with just one example"+ expected+ actual+ )++parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithOneExampleWithSafeReadOff ::+ Test+parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithOneExampleWithSafeReadOff =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "1"+ , Just "2"+ , Just "3"+ , Just "4"+ , Just "5"+ , Just "6"+ , Just "7"+ , Just "8"+ , Just "9"+ , Just "10"+ , Just "11"+ , Just "12"+ , Just "13"+ , Just "14"+ , Just "15"+ , Just "16"+ , Just "17"+ , Just "18"+ , Just "19"+ , Just "20"+ , Just "1.0"+ , Just "2.0"+ , Just "3.0"+ , Just "4.0"+ , Just "5.0"+ , Just "6.0"+ , Just "7.0"+ , Just "8.0"+ , Just "9.0"+ , Just "10.0"+ , Just "11.0"+ , Just "12.0"+ , Just "13.0"+ , Just "14.0"+ , Just "15.0"+ , Just "16.0"+ , Just "17.0"+ , Just "18.0"+ , Just "19.0"+ , Just "20.0"+ , Just "21.0"+ , Just "22.0"+ , Just "23.0"+ , Just "24.0"+ , Just "25.0"+ , Just "26.0"+ , Just "27.0"+ , Just "28.0"+ , Just "29.0"+ , Just "30.0"+ , Just "3.14"+ , Just "2.22"+ , Just "8.55"+ , Just "23.3"+ , Just "12.22222235049451"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 1 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints, Doubles, empty strings, nullish as OptionalColumn of Text with just one example, when safeRead is off"+ expected+ actual+ )++parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithManyExamplesWithSafeReadOff ::+ Test+parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithManyExamplesWithSafeReadOff =+ let afterParse :: [Maybe T.Text]+ afterParse =+ [ Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Nothing+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "NaN"+ , Just "N/A"+ , Just "1"+ , Just "2"+ , Just "3"+ , Just "4"+ , Just "5"+ , Just "6"+ , Just "7"+ , Just "8"+ , Just "9"+ , Just "10"+ , Just "11"+ , Just "12"+ , Just "13"+ , Just "14"+ , Just "15"+ , Just "16"+ , Just "17"+ , Just "18"+ , Just "19"+ , Just "20"+ , Just "1.0"+ , Just "2.0"+ , Just "3.0"+ , Just "4.0"+ , Just "5.0"+ , Just "6.0"+ , Just "7.0"+ , Just "8.0"+ , Just "9.0"+ , Just "10.0"+ , Just "11.0"+ , Just "12.0"+ , Just "13.0"+ , Just "14.0"+ , Just "15.0"+ , Just "16.0"+ , Just "17.0"+ , Just "18.0"+ , Just "19.0"+ , Just "20.0"+ , Just "21.0"+ , Just "22.0"+ , Just "23.0"+ , Just "24.0"+ , Just "25.0"+ , Just "26.0"+ , Just "27.0"+ , Just "28.0"+ , Just "29.0"+ , Just "30.0"+ , Just "3.14"+ , Just "2.22"+ , Just "8.55"+ , Just "23.3"+ , Just "12.22222235049451"+ ]+ beforeParse :: [T.Text]+ beforeParse =+ [ ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , ""+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "NaN"+ , "N/A"+ , "1"+ , "2"+ , "3"+ , "4"+ , "5"+ , "6"+ , "7"+ , "8"+ , "9"+ , "10"+ , "11"+ , "12"+ , "13"+ , "14"+ , "15"+ , "16"+ , "17"+ , "18"+ , "19"+ , "20"+ , "1.0"+ , "2.0"+ , "3.0"+ , "4.0"+ , "5.0"+ , "6.0"+ , "7.0"+ , "8.0"+ , "9.0"+ , "10.0"+ , "11.0"+ , "12.0"+ , "13.0"+ , "14.0"+ , "15.0"+ , "16.0"+ , "17.0"+ , "18.0"+ , "19.0"+ , "20.0"+ , "21.0"+ , "22.0"+ , "23.0"+ , "24.0"+ , "25.0"+ , "26.0"+ , "27.0"+ , "28.0"+ , "29.0"+ , "30.0"+ , "3.14"+ , "2.22"+ , "8.55"+ , "23.3"+ , "12.22222235049451"+ ]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 30 False "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ ( assertEqual+ "Correctly parses a mixture of Ints, Doubles, empty strings, nullish as OptionalColumn of Text with many examples, when safeRead is off"+ expected+ actual+ )++-- 5. EDGE CASES THAT HAVE TO BE INTERPRETED CORRECTLY++parseManyNullishAndOneInt :: Test+parseManyNullishAndOneInt =+ let afterParse :: [Maybe Int]+ afterParse = replicate 100 Nothing ++ [Just 100000]+ beforeParse :: [T.Text]+ beforeParse = replicate 100 "NaN" ++ ["100000"]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ (assertEqual "Correctly parses many Nulls followed by one Int" expected actual)++parseManyNullishAndOneDouble :: Test+parseManyNullishAndOneDouble =+ let afterParse :: [Maybe Double]+ afterParse = replicate 100 Nothing ++ [Just 3.14]+ beforeParse :: [T.Text]+ beforeParse = replicate 100 "NaN" ++ ["3.14"]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ (assertEqual "Correctly parses many Nulls followed by one Int" expected actual)++parseManyNullishAndOneDate :: Test+parseManyNullishAndOneDate =+ let afterParse :: [Maybe Day]+ afterParse = replicate 100 Nothing ++ [Just $ fromGregorian 2024 12 25]+ beforeParse :: [T.Text]+ beforeParse = replicate 100 "NaN" ++ ["2024-12-25"]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ (assertEqual "Correctly parses many Nulls followed by one Int" expected actual)++parseManyNullishAndIncorrectDates :: Test+parseManyNullishAndIncorrectDates =+ let afterParse :: [Maybe T.Text]+ afterParse = replicate 100 Nothing ++ [Just "2024-12-25", Just "2024-12-w6"]+ beforeParse :: [T.Text]+ beforeParse = replicate 100 "NaN" ++ ["2024-12-25", "2024-12-w6"]+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ (assertEqual "Correctly parses many Nulls followed by one Int" expected actual)++parseRepeatedNullish :: Test+parseRepeatedNullish =+ let afterParse :: [Maybe T.Text]+ afterParse = replicate 100 Nothing+ beforeParse :: [T.Text]+ beforeParse = replicate 100 "NaN"+ expected = DI.OptionalColumn $ V.fromList afterParse+ actual = D.parseDefault 10 True "%Y-%m-%d" $ DI.fromVector $ V.fromList beforeParse+ in TestCase+ (assertEqual "Correctly parses many Nulls followed by one Int" expected actual)++parseTests :: [Test]+parseTests =+ [ -- 1. SIMPLE CASES+ TestLabel "parseInts" parseInts+ , TestLabel "parseDoubles" parseDoubles+ , TestLabel "parseDates" parseDates+ , TestLabel "parseTexts" parseTexts+ , -- 2. COMBINATION CASES+ TestLabel "parseIntsAndDoublesAsDoubles" parseIntsAndDoublesAsDoubles+ , TestLabel "parseIntsAndDatesAsTexts" parseIntsAndDatesAsTexts+ , TestLabel "parseTextsAndDoublesAsTexts" parseTextsAndDoublesAsTexts+ , TestLabel "parseDatesAndTextsAsTexts" parseDatesAndTextsAsTexts+ , -- 3A. PARSING WITH SAFEREAD OFF+ TestLabel "parseIntsWithoutSafeRead" parseIntsWithoutSafeRead+ , TestLabel "parseDoublesWithoutSafeRead" parseDoublesWithoutSafeRead+ , TestLabel "parseDatesWithoutSafeRead" parseDatesWithoutSafeRead+ , TestLabel "parseTextsWithoutSafeRead" parseTextsWithoutSafeRead+ , TestLabel+ "parseIntsAndEmptyStringsWithoutSafeRead"+ parseIntsAndEmptyStringsWithoutSafeRead+ , TestLabel+ "parseIntsAndDoublesAndEmptyStringsWithoutSafeRead"+ parseIntsAndDoublesAndEmptyStringsWithoutSafeRead+ , TestLabel+ "parseDatesAndEmptyStringsWithoutSafeRead"+ parseDatesAndEmptyStringsWithoutSafeRead+ , TestLabel+ "parseTextsAndEmptyStringsWithoutSafeRead"+ parseTextsAndEmptyStringsWithoutSafeRead+ , TestLabel+ "parseIntsAndNullishStringsWithoutSafeRead"+ parseIntsAndNullishStringsWithoutSafeRead+ , TestLabel+ "parseIntsAndDoublesAndNullishStringsWithoutSafeRead"+ parseIntsAndDoublesAndNullishStringsWithoutSafeRead+ , TestLabel+ "parseIntsAndNullishAndEmptyStringsWithoutSafeRead"+ parseIntsAndNullishAndEmptyStringsWithoutSafeRead+ , TestLabel+ "parseTextsAndEmptyAndNullishStringsWithoutSafeRead"+ parseTextsAndEmptyAndNullishStringsWithoutSafeRead+ , -- 3B. PARSING WITH SAFEREAD ON+ TestLabel+ "parseIntsAndEmptyStringsWithSafeRead"+ parseIntsAndEmptyStringsWithSafeRead+ , TestLabel+ "parseIntsAndDoublesAndEmptyStringsWithSafeRead"+ parseIntsAndDoublesAndEmptyStringsWithSafeRead+ , TestLabel+ "parseDatesAndEmptyStringsWithSafeRead"+ parseDatesAndEmptyStringsWithSafeRead+ , TestLabel+ "parseTextsAndEmptyStringsWithSafeRead"+ parseTextsAndEmptyStringsWithSafeRead+ , TestLabel+ "parseIntsAndNullishStringsWithSafeRead"+ parseIntsAndNullishStringsWithSafeRead+ , TestLabel+ "parseIntsAndDoublesAndNullishStringsWithSafeRead"+ parseIntsAndDoublesAndNullishStringsWithSafeRead+ , TestLabel+ "parseIntsAndDoublesAndNullishAndEmptyStringsWithSafeRead"+ parseIntsAndDoublesAndNullishAndEmptyStringsWithSafeRead+ , TestLabel+ "parseIntsAndNullishAndEmptyStringsWithSafeRead"+ parseIntsAndNullishAndEmptyStringsWithSafeRead+ , TestLabel+ "parseTextsAndEmptyAndNullishStringsWithSafeRead"+ parseTextsAndEmptyAndNullishStringsWithSafeRead+ , -- 4A. PARSING MUST NOT DEPEND ON THE NUMBER OF EXAMPLES+ TestLabel "parseIntsWithOneExample" parseIntsWithOneExample+ , TestLabel "parseIntsWithTwentyFiveExamples" parseIntsWithTwentyFiveExamples+ , TestLabel "parseIntsWithFortyNineExamples" parseIntsWithFortyNineExamples+ , TestLabel "parseDatesWithOneExample" parseDatesWithOneExample+ , TestLabel "parseDatesWithFifteenExamples" parseDatesWithFifteenExamples+ , TestLabel+ "parseIntsAndDoublesAsDoublesWithOneExample"+ parseIntsAndDoublesAsDoublesWithOneExample+ , TestLabel+ "parseIntsAndDoublesAsDoublesWithManyExamples"+ parseIntsAndDoublesAsDoublesWithManyExamples+ , TestLabel+ "parseIntsAndDoublesAndEmptyStringsAsDoublesWithOneExampleWithSafeReadOff"+ parseIntsAndDoublesAndEmptyStringsAsDoublesWithOneExampleWithSafeReadOff+ , TestLabel+ "parseIntsAndDoublesAndEmptyStringsAsDoublesWithManyExamplesWithSafeReadOff"+ parseIntsAndDoublesAndEmptyStringsAsDoublesWithManyExamplesWithSafeReadOff+ , TestLabel+ "parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithOneExampleWithSafeReadOff"+ parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithOneExampleWithSafeReadOff+ , TestLabel+ "parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithManyExamplesWithSafeReadOff"+ parseIntsAndDoublesAndEmptyStringsAndNullishAsStringssWithManyExamplesWithSafeReadOff+ , -- 5. EDGE CASES THAT HAVE TO BE PARSED CORRECTLY+ TestLabel "parseManyNullishAndOneInt" parseManyNullishAndOneInt+ , TestLabel "parseManyNullishAndOneDouble" parseManyNullishAndOneDouble+ , TestLabel "parseRepeatedNullish" parseRepeatedNullish+ ]++tests :: Test+tests =+ TestList $+ dimensionsTest+ ++ Operations.Aggregations.tests+ ++ Operations.Apply.tests+ ++ Operations.Core.tests+ ++ Operations.Derive.tests+ ++ Operations.Filter.tests+ ++ Operations.GroupBy.tests+ ++ Operations.InsertColumn.tests+ ++ Operations.Join.tests ++ Operations.Merge.tests ++ Operations.ReadCsv.tests ++ Operations.Sort.tests
tests/Operations/Filter.hs view
@@ -5,6 +5,7 @@ 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 Assertions@@ -32,7 +33,7 @@ ( assertExpectException "[Error Case]" (D.columnNotFound "test0" "filter" (D.columnNames testData))- (print $ D.filter @Int "test0" even testData)+ (print $ D.filter (F.col @Int "test0") even testData) ) filterColumnWrongType :: Test@@ -41,7 +42,7 @@ ( assertExpectException "[Error Case]" (D.typeMismatchError (show $ typeRep @Integer) (show $ typeRep @Int))- (print $ D.filter @Integer "test1" even testData)+ (print $ D.filter (F.col @Integer "test1") even testData) ) filterByColumnDoesNotExist :: Test@@ -50,7 +51,7 @@ ( assertExpectException "[Error Case]" (D.columnNotFound "test0" "filter" (D.columnNames testData))- (print $ D.filterBy @Int even "test0" testData)+ (print $ D.filterBy even (F.col @Int "test0") testData) ) filterByColumnWrongType :: Test@@ -59,7 +60,7 @@ ( assertExpectException "[Error Case]" (D.typeMismatchError (show $ typeRep @Integer) (show $ typeRep @Int))- (print $ D.filterBy @Integer even "test1" testData)+ (print $ D.filterBy even (F.col @Integer "test1") testData) ) filterColumnInexistentValues :: Test@@ -68,7 +69,7 @@ ( assertEqual "Non existent filter value returns no rows" (0, 8)- (D.dimensions $ D.filter @Int "test1" (< 0) testData)+ (D.dimensions $ D.filter (F.col @Int "test1") (< 0) testData) ) filterColumnAllValues :: Test@@ -77,7 +78,7 @@ ( assertEqual "Filters all columns" (26, 8)- (D.dimensions $ D.filter @Int "test1" (const True) testData)+ (D.dimensions $ D.filter (F.col @Int "test1") (const True) testData) ) filterJustWAI :: Test
+ tests/Operations/Join.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE OverloadedStrings #-}++module Operations.Join where++import Data.Text (Text)+import qualified DataFrame as D+import DataFrame.Operations.Join+import Test.HUnit++df1 :: D.DataFrame+df1 =+ D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2", "K3", "K4", "K5"])+ , ("A", D.fromList ["A0" :: Text, "A1", "A2", "A3", "A4", "A5"])+ ]++df2 :: D.DataFrame+df2 =+ D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2"])+ , ("B", D.fromList ["B0" :: Text, "B1", "B2"])+ ]++testInnerJoin :: Test+testInnerJoin =+ TestCase+ ( assertEqual+ "Test inner join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2"])+ , ("A", D.fromList ["A0" :: Text, "A1", "A2"])+ , ("B", D.fromList ["B0" :: Text, "B1", "B2"])+ ]+ )+ (D.sortBy D.Ascending ["key"] (innerJoin ["key"] df1 df2))+ )++testLeftJoin :: Test+testLeftJoin =+ TestCase+ ( assertEqual+ "Test left join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2", "K3", "K4", "K5"])+ , ("A", D.fromList ["A0" :: Text, "A1", "A2", "A3", "A4", "A5"])+ , ("B", D.fromList [Just "B0", Just "B1" :: Maybe Text, Just "B2"])+ ]+ )+ (D.sortBy D.Ascending ["key"] (leftJoin ["key"] df2 df1))+ )++testRightJoin :: Test+testRightJoin =+ TestCase+ ( assertEqual+ "Test right join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2"])+ , ("A", D.fromList ["A0" :: Text, "A1", "A2"])+ , ("B", D.fromList ["B0" :: Text, "B1", "B2"])+ ]+ )+ (D.sortBy D.Ascending ["key"] (rightJoin ["key"] df2 df1))+ )++testFullOuterJoin :: Test+testFullOuterJoin =+ TestCase+ ( assertEqual+ "Test full outer join with single key"+ ( D.fromNamedColumns+ [ ("key", D.fromList ["K0" :: Text, "K1", "K2", "K3", "K4", "K5"])+ , ("A", D.fromList ["A0" :: Text, "A1", "A2", "A3", "A4", "A5"])+ , ("B", D.fromList [Just "B0", Just "B1" :: Maybe Text, Just "B2"])+ ]+ )+ (D.sortBy D.Ascending ["key"] (fullOuterJoin ["key"] df2 df1))+ )++tests :: [Test]+tests =+ [ TestLabel "innerJoin" testInnerJoin+ , TestLabel "leftJoin" testLeftJoin+ , TestLabel "rightJoin" testRightJoin+ ]
tests/Operations/Statistics.hs view
@@ -43,7 +43,7 @@ TestCase ( assertBool "Skewness of a data set with the same elements"- (isNaN (D.skewness' (VU.fromList $ replicate 10 42.0)))+ (isNaN (D.skewness' @Double (VU.fromList $ replicate 10 42.0))) ) skewnessOfSymmetricDataSet :: Test@@ -51,7 +51,7 @@ TestCase ( assertEqual "Skewness of a symmetric data set"- (D.skewness' (VU.fromList [-3.0, -2.0, -1.5, 0, 1.5, 2.0, 3.0]))+ (D.skewness' (VU.fromList [-3.0 :: Double, -2.0, -1.5, 0, 1.5, 2.0, 3.0])) 0 ) @@ -60,7 +60,10 @@ TestCase ( assertBool "Skewness of a simple data set"- ( abs (D.skewness' (VU.fromList [25, 28, 26, 30, 40, 50, 40]) - 0.566_731_633_676)+ ( abs+ ( D.skewness' (VU.fromList [25 :: Int, 28, 26, 30, 40, 50, 40])+ - 0.566_731_633_676+ ) < 1e-12 ) )@@ -70,7 +73,7 @@ TestCase ( assertEqual "Skewness of an empty data set"- (D.skewness' (VU.fromList []))+ (D.skewness' @Double (VU.fromList [])) 0 ) @@ -82,7 +85,7 @@ ( D.quantiles' (VU.fromList [0, 1, 2]) 2- (VU.fromList [179.94, 231.94, 839.06, 534.23, 248.94])+ (VU.fromList [179.94 :: Double, 231.94, 839.06, 534.23, 248.94]) ) (VU.fromList [179.94, 248.94, 839.06]) )@@ -95,7 +98,7 @@ ( D.quantiles' (VU.fromList [0, 1, 2]) 2- (VU.fromList [179.94, 231.94, 839.06, 534.23, 248.94, 276.37])+ (VU.fromList [179.94 :: Double, 231.94, 839.06, 534.23, 248.94, 276.37]) ) (VU.fromList [179.94, 262.655, 839.06]) )@@ -108,7 +111,7 @@ ( D.quantiles' (VU.fromList [0, 1, 2, 3, 4]) 4- (VU.fromList [3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20])+ (VU.fromList [3 :: Int, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20]) ) (VU.fromList [3, 7.5, 9, 14, 20]) )@@ -121,7 +124,7 @@ ( D.quantiles' (VU.fromList [0, 1, 2, 3, 4]) 4- (VU.fromList [3, 6, 7, 8, 8, 10, 13, 15, 16, 20])+ (VU.fromList [3 :: Int, 6, 7, 8, 8, 10, 13, 15, 16, 20]) ) (VU.fromList [3, 7.25, 9, 14.5, 20]) )@@ -134,7 +137,7 @@ ( D.quantiles' (VU.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 10- (VU.fromList [4, 7, 3, 1, 11, 6, 2, 9, 8, 10, 5])+ (VU.fromList [4 :: Int, 7, 3, 1, 11, 6, 2, 9, 8, 10, 5]) ) (VU.fromList [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) )@@ -144,7 +147,9 @@ TestCase ( assertEqual "Inter quartile range of an odd length data set"- (D.interQuartileRange' (VU.fromList [3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20]))+ ( D.interQuartileRange'+ (VU.fromList [3 :: Int, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20])+ ) 6.5 ) @@ -153,7 +158,7 @@ TestCase ( assertEqual "Inter quartile range of an even length data set"- (D.interQuartileRange' (VU.fromList [3, 6, 7, 8, 8, 10, 13, 15, 16, 20]))+ (D.interQuartileRange' (VU.fromList [3 :: Int, 6, 7, 8, 8, 10, 13, 15, 16, 20])) 7.25 ) @@ -163,7 +168,7 @@ ( assertExpectException "[Error Case]" (D.wrongQuantileNumberError 1)- (print $ D.quantiles' (VU.fromList [0]) 1 (VU.fromList [1, 2, 3, 4, 5]))+ (print $ D.quantiles' (VU.fromList [0]) 1 (VU.fromList [1 :: Int, 2, 3, 4, 5])) ) wrongQuantileIndex :: Test@@ -172,7 +177,7 @@ ( assertExpectException "[Error Case]" (D.wrongQuantileIndexError (VU.fromList [5]) 4)- (print $ D.quantiles' (VU.fromList [5]) 4 (VU.fromList [1, 2, 3, 4, 5]))+ (print $ D.quantiles' (VU.fromList [5]) 4 (VU.fromList [1 :: Int, 2, 3, 4, 5])) ) tests :: [Test]
tests/Parquet.hs view
@@ -1,8 +1,10 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-} module Parquet where import qualified DataFrame as D+import qualified DataFrame.Functions as F import Data.Int import Data.Text (Text)@@ -64,7 +66,7 @@ TestCase ( assertEqual "allTypesPlainSnappy"- (D.filter "id" (`elem` [6 :: Int32, 7]) allTypes)+ (D.filter (F.col @Int32 "id") (`elem` [6, 7]) allTypes) (unsafePerformIO (D.readParquet "./tests/data/alltypes_plain.snappy.parquet")) ) @@ -73,7 +75,7 @@ TestCase ( assertEqual "allTypesPlainSnappy"- (D.filter "id" (`elem` [0 :: Int32, 1]) allTypes)+ (D.filter (F.col @Int32 "id") (`elem` [0, 1]) allTypes) (unsafePerformIO (D.readParquet "./tests/data/alltypes_dictionary.parquet")) )