packages feed

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 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"))         )