timeseries 0.1.0 → 0.2.0
raw patch · 15 files changed
+197/−111 lines, 15 filesdep +text-timedep −bytestring-time
Dependencies added: text-time
Dependencies removed: bytestring-time
Files
- CHANGES.md +10/−0
- README.md +6/−7
- benchmark/Benchmarks.hs +12/−2
- src/Data/TimeSeries.hs +6/−2
- src/Data/TimeSeries/CSVReader.hs +0/−37
- src/Data/TimeSeries/IO/CSVReader.hs +43/−0
- src/Data/TimeSeries/IO/CSVWriter.hs +21/−0
- src/Data/TimeSeries/Series.hs +38/−34
- src/Data/TimeSeries/Stats.hs +14/−0
- test-src/Data/TimeSeries/CSVSpec.hs +0/−17
- test-src/Data/TimeSeries/IO/CSVSpec.hs +17/−0
- test-src/Data/TimeSeries/SeriesSpec.hs +16/−4
- test-src/Spec.hs +4/−2
- testdata/test-100K.csv too large to diff
- timeseries.cabal +10/−6
CHANGES.md view
@@ -1,3 +1,13 @@+# Version 0.2 (2016-12-15)++ * Renamed function to load CSV file from **load** to **loadCSV**+ * Added toList function to the series+ * Implemented CSV Writer+ * Time Series is instance of Functor+ * Time Series is instance of Foldable+ * Implemented mean+ + # Version 0.1 (2016-12-13) * Implemented functions
README.md view
@@ -1,12 +1,11 @@-[](https://travis-ci.org/klangner/time-series-lib)+[](https://travis-ci.org/klangner/timeseries) [](https://hackage.haskell.org/package/timeseries) # Welcome to Haskell Time Series library Implemented functionality: * Base operations on time series (get value, slice, max etc.)- * Reading from CSV file-+ * Reading and writing from CSV file ## Installation@@ -24,13 +23,13 @@ and other improvements. Please report bugs via the-[github issue tracker](http://github.com/klangner/time-series-lib/issues).+[github issue tracker](http://github.com/klangner/timeseries/issues). -Master [git repository](http://github.com/klangner/time-series-lib):+Master [git repository](http://github.com/klangner/timeseries): -* `git clone https://github.com/klangner/time-series-lib.git`+* `git clone https://github.com/klangner/timeseries.git` # Redistributing -time-series-lib source code is distributed under the BSD3 License.+timeseries source code is distributed under the BSD3 License.
benchmark/Benchmarks.hs view
@@ -1,10 +1,16 @@ import Criterion.Main+import Control.DeepSeq import Data.Time (UTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import Data.Text.Time (parseISODateTime) import qualified Data.TimeSeries as TS +instance NFData a => NFData (TS.Series a) where+ rnf xs = rnf (TS.toList xs)++ seriesSize :: Integer seriesSize = 10^6 @@ -14,15 +20,19 @@ lastIndex :: UTCTime lastIndex = index (seriesSize - 1) -bigSeries :: TS.Series+bigSeries :: TS.Series Double bigSeries = TS.tsSeries [1..seriesSize] [1..] main :: IO () main = defaultMain- [ bgroup "Big series"+ [ bgroup "Basic operations" [ bench "size" $ nf TS.size bigSeries , bench "valueAt" $ nf (\xs -> TS.valueAt xs lastIndex) bigSeries , bench "slice" $ nf (\xs -> TS.valueAt (TS.slice xs (index 2) lastIndex) (index 1)) bigSeries+ , bench "max" $ nf maximum bigSeries+ , bench "fmap" $ nf (fmap (+ 2)) bigSeries ]+ , bgroup "IO"+ [ bench "load CSV" $ nfIO (TS.loadCSV TS.NoHeader parseISODateTime "../testdata/test-100K.csv") ] ]
src/Data/TimeSeries.hs view
@@ -1,9 +1,13 @@ -- | TimeSeries library module Data.TimeSeries ( module Data.TimeSeries.Series- , module Data.TimeSeries.CSVReader+ , module Data.TimeSeries.Stats+ , module Data.TimeSeries.IO.CSVReader+ , module Data.TimeSeries.IO.CSVWriter ) where import Data.TimeSeries.Series-import Data.TimeSeries.CSVReader+import Data.TimeSeries.Stats+import Data.TimeSeries.IO.CSVReader+import Data.TimeSeries.IO.CSVWriter
− src/Data/TimeSeries/CSVReader.hs
@@ -1,37 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE OverloadedStrings #-}--module Data.TimeSeries.CSVReader- ( load- )where---import qualified Data.ByteString.Lazy as BS-import qualified Data.Text as T-import Data.Csv-import qualified Data.Vector as V-import Data.Time (UTCTime)--import Data.TimeSeries.Series ( Series- , emptySeries- , series- )----- | Load data from CSV file and create Time Series from it--- As a first argument provide function to convert date from ByteString to UTCTime-load :: (T.Text -> UTCTime) -> FilePath -> IO Series-load ft filePath = do- csvData <- BS.readFile filePath- case decode HasHeader csvData of- Left err -> do- _ <- putStrLn err- return emptySeries-- Right vs -> do- let vs' = V.map (parseLine ft) vs- return $ series (V.toList vs')---parseLine :: (T.Text -> UTCTime) -> (T.Text, Double) -> (UTCTime, Double)-parseLine ft (date, value) = (ft date, value)
+ src/Data/TimeSeries/IO/CSVReader.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-}++module Data.TimeSeries.IO.CSVReader+ ( HasHeader(..)+ , loadCSV+ )where+++import qualified Data.ByteString.Lazy as BS+import qualified Data.Text as T+import qualified Data.Csv as CSV+import qualified Data.Vector as V+import Data.Time (UTCTime)++import Data.TimeSeries.Series ( Series+ , emptySeries+ , series+ )++data HasHeader = HasHeader | NoHeader++-- | Load data from CSV file and create Time Series from it+-- As a first argument provide function to convert date from ByteString to UTCTime+loadCSV :: HasHeader -> (T.Text -> UTCTime) -> FilePath -> IO (Series Double)+loadCSV hasHeader ft filePath = do+ csvData <- BS.readFile filePath+ case CSV.decode (toCSVHeader hasHeader) csvData of+ Left err -> do+ _ <- putStrLn err+ return emptySeries++ Right vs -> do+ let vs' = V.map (parseLine ft) vs+ return $ series (V.toList vs')+++toCSVHeader :: HasHeader -> CSV.HasHeader+toCSVHeader HasHeader = CSV.HasHeader+toCSVHeader _ = CSV.NoHeader++parseLine :: (T.Text -> UTCTime) -> (T.Text, Double) -> (UTCTime, Double)+parseLine ft (date, value) = (ft date, value)
+ src/Data/TimeSeries/IO/CSVWriter.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.TimeSeries.IO.CSVWriter( saveCSV ) where+++import Prelude hiding (writeFile)+import Data.Csv+import Data.ByteString.Lazy (writeFile)+import Data.Text.Time ( formatISODateTime )++import Data.TimeSeries.Series ( Series+ , toList )+++-- | Load data from CSV file and create Time Series from it+-- As a first argument provide function to convert date from ByteString to UTCTime+saveCSV :: Series Double -> FilePath -> IO ()+saveCSV ts filePath = do+ let rs = map (\(x, y) -> (formatISODateTime x, y)) (toList ts)+ let rs' = encode rs+ writeFile filePath rs'
src/Data/TimeSeries/Series.hs view
@@ -11,15 +11,14 @@ module Data.TimeSeries.Series ( DataPoint , Series- , Value , emptySeries- , max- , min , series , size , slice+ , toList , tsSeries , valueAt+ , values ) where import Prelude hiding (max, min)@@ -27,21 +26,36 @@ import Data.Time.Clock.POSIX (posixSecondsToUTCTime) -type Value = Double-data DataPoint = DP !UTCTime !Value+-- | Data points is single index value od time series+data DataPoint a = DP !UTCTime a deriving (Show, Eq) -data Series = Series [DataPoint]+instance Functor DataPoint where+ fmap f (DP x y) = DP x (f y)++instance Foldable DataPoint where+ foldMap f (DP _ y) = f y++-- | Data structure for holding Series.+-- Implementation should be hidden so it can be changed in the future+data Series a = Series [DataPoint a] deriving (Show, Eq) +instance Functor Series where+ fmap f (Series xs) = Series (map (fmap f) xs) +instance Foldable Series where+ foldMap f (Series xs) = foldMap (foldMap f) xs+ length = size++ -- | Create empty series-emptySeries :: Series+emptySeries :: Series a emptySeries = Series [] --- | Create empty series-series :: [(UTCTime, Double)] -> Series+-- | Create series+series :: [(UTCTime, a)] -> Series a series xs = Series $ map (\(x, y) -> DP x y) xs @@ -49,17 +63,27 @@ -- -- >seriesFromSeconds [1, 2, 3] [41.3, 52.22, 3.0] == Series [DP 1970-01-01 00:00:01 UTC 2.3,DP 1970-01-01 00:00:02 UTC 4.5] ---tsSeries :: [Integer] -> [Value] -> Series+tsSeries :: [Integer] -> [a] -> Series a tsSeries ts vs = Series (zipWith DP idx vs) where idx = map (posixSecondsToUTCTime . fromIntegral) ts +-- | Convert Time Series to list+toList :: Series a -> [(UTCTime, a)]+toList (Series xs) = map (\(DP x y) -> (x, y)) xs+++-- | Get only series values+values :: Series a -> [a]+values ts = map (\(_, y) -> y) (toList ts)++ -- | Get series size. -- Complexity O(n) -- -- >size (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) == 3 ---size :: Series -> Int+size :: Series a -> Int size (Series xs) = length xs @@ -69,10 +93,10 @@ -- >valueAt (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) 2 == Just 52.22 -- >valueAt (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) 5 == Nothing ---valueAt :: Series -> UTCTime -> Maybe Value+valueAt :: Series a -> UTCTime -> Maybe a valueAt (Series xs) ts = safeHead [y | DP x y <- xs, x == ts] where safeHead [] = Nothing- safeHead (a:_) = Just a+ safeHead (i:_) = Just i -- | Return series subset@@ -81,27 +105,7 @@ -- >slice (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) 2 3 == Series [DP 2 52.22, DP 3 3.0] -- >slice (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) 5 23 == Series [] ---slice :: Series -> UTCTime -> UTCTime -> Series+slice :: Series a -> UTCTime -> UTCTime -> Series a slice (Series xs) start end = Series [DP x y | DP x y <- xs, x >= start && x <= end] --- | Return maximum value in the series--- Complexity O(n)------ >max (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) == 52.22--- >max (Series []) == 0----max :: Series -> Value-max (Series xs) = foldl (\d (DP _ y) -> max' d y) 0.0 xs- where max' a b = if a > b then a else b---- | Return maximum value in the series--- Complexity O(n)------ >min (Series [DP 1 41.3, DP 2 52.22, DP 3 3.0]) == 3.0--- >min (Series []) == 0----min :: Series -> Value-min (Series xs) = foldl (\d (DP _ y) -> min' d y) maxValue xs- where min' a b = if a < b then a else b- maxValue = fromIntegral (maxBound :: Int)
+ src/Data/TimeSeries/Stats.hs view
@@ -0,0 +1,14 @@++module Data.TimeSeries.Stats+ ( mean+ ) where++import Prelude hiding (max, min)+import Data.TimeSeries.Series (Series)+++-- | Return maximum value in the series+mean :: Fractional a => Series a -> a+mean xs = sum xs / fromIntegral (length xs)+-- mean xs = (fromIntegral (length xs))+
− test-src/Data/TimeSeries/CSVSpec.hs
@@ -1,17 +0,0 @@-module Data.TimeSeries.CSVSpec (spec) where--import Test.Hspec-import Data.ByteString.Time (parseISODateTime)--import qualified Data.TimeSeries as TS-import qualified Data.TimeSeries.CSVReader as CSV---spec :: Spec-spec = do-- describe "Reader" $ do-- it "co2 test data" $ do- xs <- CSV.load parseISODateTime "testdata/co2.csv"- TS.size xs `shouldBe` 192
+ test-src/Data/TimeSeries/IO/CSVSpec.hs view
@@ -0,0 +1,17 @@+module Data.TimeSeries.IO.CSVSpec (spec) where++import Test.Hspec+import Data.Text.Time (parseISODateTime)++import qualified Data.TimeSeries as TS+import qualified Data.TimeSeries.IO.CSVReader as CSV+++spec :: Spec+spec = do++ describe "Reader" $ do++ it "co2 test data" $ do+ xs <- CSV.loadCSV CSV.HasHeader parseISODateTime "testdata/co2.csv"+ TS.size xs `shouldBe` 192
test-src/Data/TimeSeries/SeriesSpec.hs view
@@ -1,7 +1,7 @@ module Data.TimeSeries.SeriesSpec (spec) where import Test.Hspec-import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds) import qualified Data.TimeSeries as TS @@ -16,6 +16,12 @@ let values = [1.0, 2.0, 3.0] TS.size (TS.tsSeries idx values) `shouldBe` 3 + it "toList" $ do+ let idx = [1, 2, 3]+ let values = [1.0, 2.0, 3.0]+ let xs = TS.toList (TS.tsSeries idx values)+ map (\(x, y) -> (utcTimeToPOSIXSeconds x, y)) xs `shouldBe` [(1, 1.0), (2, 2.0), (3, 3.0)]+ it "return data point value at given index" $ do let idx = [10, 20, 30] let values = [10.0, 12.0, 32.4]@@ -40,10 +46,16 @@ let end = posixSecondsToUTCTime $ fromIntegral 34 TS.slice xs start end `shouldBe` TS.emptySeries + it "map over series" $ do+ let xs = TS.tsSeries [1..] [10.0, 1.2, 32.4, 0.65, 11.0]+ let ys = fmap (+ 2) xs+ (TS.values ys)`shouldBe` [12.0, 3.2, 34.4, 2.65, 13.0]+ it "maximum value" $ do- let xs = TS.tsSeries [1..5] [10.0, 1.2, 32.4, 0.65, 11.0]- TS.max xs `shouldBe` 32.4+ let xs = TS.tsSeries [1..] [10.0, 1.2, 32.4, 0.65, 11.0]+ maximum xs `shouldBe` 32.4 it "minimum value" $ do let xs = TS.tsSeries [1..5] [10.0, 1.2, 32.4, 0.65, 11.0]- TS.min xs `shouldBe` 0.65+ minimum xs `shouldBe` 0.65+
test-src/Spec.hs view
@@ -1,9 +1,11 @@ import Test.Hspec import qualified Data.TimeSeries.SeriesSpec-import qualified Data.TimeSeries.CSVSpec+import qualified Data.TimeSeries.StatsSpec+import qualified Data.TimeSeries.IO.CSVSpec main :: IO () main = hspec $ do describe "Series" Data.TimeSeries.SeriesSpec.spec- describe "CSV" Data.TimeSeries.CSVSpec.spec+ describe "Statistcs" Data.TimeSeries.StatsSpec.spec+ describe "CSV" Data.TimeSeries.IO.CSVSpec.spec
+ testdata/test-100K.csv view
file too large to diff
timeseries.cabal view
@@ -1,5 +1,5 @@ name: timeseries-version: 0.1.0+version: 0.2.0 synopsis: Library for Time Series processing License: BSD3 License-file: LICENSE@@ -31,16 +31,18 @@ build-depends: base >= 4.7 && < 5, bytestring < 0.11,- bytestring-time < 1, cassava >= 0.4 && < 0.5, text < 2,+ text-time < 1, time >= 1.5 && < 2, vector < 0.12 exposed-modules: Data.TimeSeries other-modules: Data.TimeSeries.Series,- Data.TimeSeries.CSVReader+ Data.TimeSeries.Stats,+ Data.TimeSeries.IO.CSVReader+ Data.TimeSeries.IO.CSVWriter test-suite unit-tests type: exitcode-stdio-1.0@@ -52,17 +54,19 @@ hspec >=2 && <3, QuickCheck >=2.6 && <3, bytestring < 0.11,- bytestring-time < 1, cassava >= 0.4 && < 0.5, text < 2,+ text-time < 1, time >= 1.5 && < 2, vector < 0.12 other-modules: Data.TimeSeries, Data.TimeSeries.Series,+ Data.TimeSeries.Stats, Data.TimeSeries.SeriesSpec- Data.TimeSeries.CSVReader- Data.TimeSeries.CSVSpec+ Data.TimeSeries.IO.CSVReader+ Data.TimeSeries.IO.CSVWriter+ Data.TimeSeries.IO.CSVSpec hs-source-dirs: src, test-src