44{-# LANGUAGE GADTs #-}
55{-# LANGUAGE InstanceSigs #-}
66{-# LANGUAGE OverloadedStrings #-}
7+ {-# LANGUAGE PatternSynonyms #-}
78{-# LANGUAGE ScopedTypeVariables #-}
89{-# LANGUAGE TypeApplications #-}
910
@@ -18,13 +19,18 @@ import Control.DeepSeq (NFData (..), rnf)
1819import Control.Exception (throw )
1920import Data.Function (on )
2021import Data.List (sortBy , transpose , (\\) )
21- import Data.Type.Equality (TestEquality (testEquality ), type (:~: ) (Refl ))
22+ import Data.Maybe (fromMaybe )
23+ import Data.Type.Equality (
24+ TestEquality (testEquality ),
25+ type (:~: ) (Refl ),
26+ type (:~~: ) (HRefl ),
27+ )
2228import DataFrame.Display.Terminal.PrettyPrint
2329import DataFrame.Errors
2430import DataFrame.Internal.Column
2531import DataFrame.Internal.Expression
2632import Text.Printf
27- import Type.Reflection (Typeable , typeRep )
33+ import Type.Reflection (Typeable , eqTypeRep , typeRep , pattern App )
2834import Prelude hiding (null )
2935
3036data DataFrame = DataFrame
@@ -196,3 +202,40 @@ Note that a dataframe with columns but no rows is not considered null.
196202-}
197203null :: DataFrame -> Bool
198204null df = V. null (columns df)
205+
206+ -- | Convert a DataFrame to a CSV (comma-separated) text.
207+ toCsv :: DataFrame -> T. Text
208+ toCsv = toSeparated ' ,'
209+
210+ -- | Convert a DataFrame to a text representation with a custom separator.
211+ toSeparated :: Char -> DataFrame -> T. Text
212+ toSeparated sep df
213+ | null df = T. empty
214+ | otherwise =
215+ let (rows, _) = dataframeDimensions df
216+ headers = map fst (sortBy (compare `on` snd ) (M. toList (columnIndices df)))
217+ sepText = T. singleton sep
218+ headerLine = T. intercalate sepText headers
219+ dataLines = map (T. intercalate sepText . getRowAsText df) [0 .. rows - 1 ]
220+ in T. unlines (headerLine : dataLines)
221+
222+ getRowAsText :: DataFrame -> Int -> [T. Text ]
223+ getRowAsText df i = map (`showElement` i) (V. toList (columns df))
224+
225+ showElement :: Column -> Int -> T. Text
226+ showElement (BoxedColumn _ (c :: V. Vector a )) i = case c V. !? i of
227+ Nothing -> error $ " Column index out of bounds at row " ++ show i
228+ Just e
229+ | Just Refl <- testEquality (typeRep @ a ) (typeRep @ T. Text ) -> e
230+ | App t1 t2 <- typeRep @ a
231+ , Just HRefl <- eqTypeRep t1 (typeRep @ Maybe ) ->
232+ case testEquality t2 (typeRep @ T. Text ) of
233+ Just Refl -> fromMaybe " null" e
234+ Nothing -> stripJust (T. pack (show e))
235+ | otherwise -> T. pack (show e)
236+ showElement (UnboxedColumn _ c) i = case c VU. !? i of
237+ Nothing -> error $ " Column index out of bounds at row " ++ show i
238+ Just e -> T. pack (show e)
239+
240+ stripJust :: T. Text -> T. Text
241+ stripJust = fromMaybe " null" . T. stripPrefix " Just "
0 commit comments