packages feed

timeseries 0.3.0 → 0.4.0

raw patch · 15 files changed

+199/−70 lines, 15 files

Files

CHANGES.md view
@@ -1,4 +1,10 @@-# Version 0.3+# Version 0.4++  * reimplemented resampling function+  * Implemented Sessions+++# Version 0.3 (2016-12-18)    * slice has different order of its parameters   * valueAt has different order of its parameters
README.md view
@@ -15,6 +15,7 @@   * Calculate statistics     * min, max     * median, variance, standard deviation+  * Finding sessions (periods of activity with small breaks)   ## Installation
benchmark/Benchmarks.hs view
@@ -28,21 +28,21 @@ smallSeries = TS.tsSeries [1..10^4] [1..]  startTime :: UTCTime-startTime = posixSecondsToUTCTime $ fromIntegral 1+startTime = posixSecondsToUTCTime 1   main :: IO () main = defaultMain     [ bgroup "Basic operations"         [ bench "size"  $ nf TS.size bigSeries-        , bench "valueAt"  $ nf (\xs -> TS.valueAt lastIndex xs) bigSeries-        , bench "slice"  $ nf (\xs -> TS.valueAt  (index 1) (TS.slice (index 2) lastIndex xs)) bigSeries+        , bench "valueAt"  $ nf (TS.valueAt lastIndex) bigSeries+        , bench "slice"  $ nf (TS.valueAt (index 1) . TS.slice (index 2) lastIndex) bigSeries         , bench "max"  $ nf maximum bigSeries         , bench "fmap"  $ nf (fmap (+ 2)) bigSeries         ]     , bgroup "Group operation"         [ bench "rolling" $ nf (TS.rolling 20 sum) smallSeries-        , bench "resampling" $ nf (TS.resample startTime (TS.seconds 20) sum) smallSeries+        , bench "resampling" $ nf (TS.resample startTime (TS.seconds 20)) smallSeries         ]     , bgroup "IO"         [ bench "load CSV" $ nfIO (TS.loadCSV TS.NoHeader parseISODateTime "testdata/test-100K.csv") ]
src/Data/TimeSeries.hs view
@@ -1,15 +1,7 @@ -- | TimeSeries library-module Data.TimeSeries-    ( module Data.TimeSeries.Series-    , module Data.TimeSeries.Stats-    , module Data.TimeSeries.Time-    , module Data.TimeSeries.IO.CSVReader-    , module Data.TimeSeries.IO.CSVWriter-    ) where---import Data.TimeSeries.Series-import Data.TimeSeries.Stats-import Data.TimeSeries.Time-import Data.TimeSeries.IO.CSVReader-import Data.TimeSeries.IO.CSVWriter+module Data.TimeSeries (module X) where+  import Data.TimeSeries.Series as X+  import Data.TimeSeries.Stats as X+  import Data.TimeSeries.Time as X+  import Data.TimeSeries.IO.CSVReader as X+  import Data.TimeSeries.IO.CSVWriter as X
src/Data/TimeSeries/IO/CSVWriter.hs view
@@ -4,6 +4,7 @@   import Prelude hiding (writeFile)+import Control.Arrow (first) import Data.Csv import Data.ByteString.Lazy (writeFile) import Data.Text.Time ( formatISODateTime )@@ -16,6 +17,6 @@ -- 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 = map (first formatISODateTime) (toList ts)     let rs' = encode rs     writeFile filePath rs'
src/Data/TimeSeries/Series.hs view
@@ -8,25 +8,30 @@ Definition and basic operation on Series. -} -module Data.TimeSeries.Series-    ( DataPoint+module Data.TimeSeries.Series (+    -- * Series datatype+      DataPoint     , Series     , dpIndex     , dpValue+    -- * Create series     , emptySeries+    , tsSeries+    , series+    -- * Conversion between types+    , toList+    , values+    -- * Selecting data from series     , rolling     , resample-    , series     , size     , slice-    , toList-    , tsSeries     , valueAt-    , values     ) where  import Prelude hiding (max, min)-import Data.Time (UTCTime, NominalDiffTime, diffUTCTime)+import Data.Time ( UTCTime+                 , diffUTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime)  import Data.TimeSeries.Time (TimeResolution, nextTime)@@ -63,7 +68,7 @@  -- | Create series series :: [(UTCTime, a)] -> Series a-series xs = Series $ map (\(x, y) -> DP x y) xs+series xs = Series $ map (uncurry DP) xs   -- | Create time series from timestamps and values@@ -84,7 +89,7 @@  -- | Get series values values :: Series a -> [a]-values ts = map (\(_, y) -> y) (toList ts)+values ts = map snd (toList ts)   -- | Get series size.@@ -125,14 +130,14 @@  -- | Apply function to rolling window to create new Series -- Rolling window is also called Sliding Window.-rolling ::  NominalDiffTime -- ^ Window size+rolling ::  TimeResolution  -- ^ Window size         -> ([a] -> b)       -- ^ Function applied to each window         -> Series a         -- ^ Input Series         -> Series b         -- ^ Converted Series rolling dt f (Series xs) = Series $ map (\(i, vs) -> DP i (f vs)) (windows dt xs)  -- Create rolling windows based on given delta time.-windows ::  NominalDiffTime -> [DataPoint a] -> [(UTCTime, [a])]+windows ::  TimeResolution -> [DataPoint a] -> [(UTCTime, [a])] windows _ [] = [] windows dt xs = g ys : if length xs > length ys then windows dt (tail xs) else []     where@@ -142,25 +147,28 @@         g vs = (dpIndex (last vs), values (Series vs))  -- Check if two DataPoints are closer then given time difference-isInTimeRange :: NominalDiffTime -> DataPoint a -> DataPoint a -> Bool-isInTimeRange dt (DP i _) (DP j _) = diffUTCTime j i < dt+isInTimeRange :: TimeResolution -> DataPoint a -> DataPoint a -> Bool+isInTimeRange dt (DP i _) (DP j _) = j < nextTime dt i   -- | Resample Time series-resample :: UTCTime             -- ^ Starting time+resample :: Fractional a+         => UTCTime             -- ^ Starting time          -> TimeResolution      -- ^ Resampling resolution-         -> ([a] -> b)          -- ^ Function for aggregating data points          -> Series a            -- ^ Input series-         -> Series b            -- ^ Resampled series-resample utc res f (Series xs) = Series (map (\(i, vs) -> DP i (f (g vs))) (resample' utc res xs))-    where-        g :: [DataPoint a] -> [a]-        g  = map dpValue+         -> Series a            -- ^ Resampled series+resample _ _ (Series []) = emptySeries+resample utc res (Series xs) = Series (resample' utc res (head xs) xs)  -- | Resample based on list-resample' :: UTCTime -> TimeResolution -> [DataPoint a] -> [(UTCTime, [DataPoint a])]-resample' _ _ [] = []-resample' utc res xs = (utc, ys) : resample' utc2 res zs+resample' :: Fractional a => UTCTime -> TimeResolution -> DataPoint a -> [DataPoint a] -> [DataPoint a]+resample' _ _ _ [] = []+resample' utc res y (x:xs)+    | utc < dpIndex x   = DP utc mu : resample' utc2 res y (x:xs)+    | utc == dpIndex x  = DP utc (dpValue x) : resample' utc2 res x xs+    | otherwise         = resample' utc res x xs     where         utc2 = nextTime res utc-        (ys, zs) = break (\(DP x _) -> x >= utc2) xs+        mu = (ty/(tx+ty)) * dpValue x + ((tx/(tx+ty)) * dpValue y)+        tx = abs $ realToFrac (diffUTCTime utc (dpIndex x))+        ty = abs $ realToFrac (diffUTCTime utc (dpIndex y))
+ src/Data/TimeSeries/Sessions.hs view
@@ -0,0 +1,49 @@++module Data.TimeSeries.Sessions+    ( Session(..)+    , find+    ) where++import Data.Time (UTCTime)+import Data.TimeSeries.Series (Series, toList)++import Data.TimeSeries.Time(TimeResolution, nextTime)+++-- | Session is a time range of a single event+--   Some examples:+--     * Session with web application+--     * Rain event+data Session = Session { sessionStart :: UTCTime+                       , sessionEnd :: UTCTime }+                       deriving (Eq, Show)+++-- | Find all session in a given time series+find :: TimeResolution -> Series Bool -> [Session]+find dt ts = map (uncurry Session) xs'+    where+        xs = findChanges False (toList ts)+        xs' = join dt (cnv xs)+++-- Helper function for finding all places where value changes from True to False or vice versa+findChanges :: Bool -> [(a, Bool)] -> [a]+findChanges _ [] = []+findChanges True [x] = [fst x]+findChanges v (x:xs) | v == snd x = findChanges v xs+                     | otherwise = fst x : findChanges (snd x) xs++-- Convert list of changes into tuple2+cnv :: [a] -> [(a, a)]+cnv [] = []+cnv [_] = []+cnv (x:y:z) = (x, y) : cnv z++-- Join events close to each other into single session+join :: TimeResolution -> [(UTCTime, UTCTime)] -> [(UTCTime, UTCTime)]+join _ [] = []+join _ [x] = [x]+join dt (x:y:z) = if nextTime dt (snd x) < fst y+                    then x:join dt (y:z)+                    else join dt ((fst x, snd y):z)
src/Data/TimeSeries/Time.hs view
@@ -15,6 +15,9 @@                 , addUTCTime)  +-- | TimeResolution is for holding time periods which can't easy be translated to number of seconds+--   For example day doesn't always have 86400 and month have different number of days.+--   So if we want to define 1 month period we can't use DiffTime for it. data TimeResolution = Years Integer                     | Months Integer                     | Days Integer@@ -23,12 +26,12 @@  -- | Create Time Resolution from days days :: Integer -> TimeResolution-days n = Days n+days = Days   -- | Create Time Resolution from months months :: Integer -> TimeResolution-months n = Months n+months = Months   -- | Create Time Resolution from seconds@@ -38,7 +41,7 @@  -- | Create Time Resolution from years years :: Integer -> TimeResolution-years n = Years n+years = Years   -- | Return Time Series mean
test-src/Data/TimeSeries/IO/CSVSpec.hs view
@@ -8,9 +8,9 @@   spec :: Spec-spec = do+spec = -  describe "Reader" $ do+  describe "Reader" $      it "co2 test data" $ do         xs <- CSV.loadCSV CSV.HasHeader parseISODateTime "testdata/co2.csv"
test-src/Data/TimeSeries/SeriesSpec.hs view
@@ -1,6 +1,7 @@ module Data.TimeSeries.SeriesSpec (spec) where  import Test.Hspec+import Control.Arrow (first) import Data.Time (UTCTime) import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds) import qualified Statistics.Sample as S@@ -17,23 +18,23 @@ spec :: Spec spec = do -  describe "Converting Series" $ do+  describe "Converting Series" $      it "toList" $ do         let xs = TS.toList sampleSeries-        map (\(x, y) -> (utcTimeToPOSIXSeconds x, y)) xs `shouldBe` [(1, 10.0), (2, 1.2), (3, 32.4), (4, 0.6), (5, 11.0)]+        map (first utcTimeToPOSIXSeconds) xs `shouldBe` [(1, 10.0), (2, 1.2), (3, 32.4), (4, 0.6), (5, 11.0)]     describe "Selecting sub Series" $ do      it "return data point value at given index" $ do-        let pos = posixSecondsToUTCTime $ fromIntegral 2+        let pos = posixSecondsToUTCTime 2         TS.valueAt pos sampleSeries `shouldBe` Just 1.2      it "return Nothing value if wrong index" $ do         let idx = [10, 20, 30]         let values = [10.0, 12.0, 32.4]-        let pos = posixSecondsToUTCTime $ fromIntegral 1234+        let pos = posixSecondsToUTCTime 1234         TS.valueAt pos (TS.tsSeries idx values) `shouldBe` Nothing      it "return subset" $ do@@ -42,6 +43,12 @@         let end = utcFromSeconds 3         TS.size (TS.slice start end xs) `shouldBe` 2 +    it "return range" $ do+        let xs = TS.tsSeries [0, 5..] [1.0, 1.2, 32.4, 0.65, 11.0]+        let start = utcFromSeconds 2+        let end = utcFromSeconds 130+        TS.size (TS.slice start end xs) `shouldBe` 4+     it "return empty subset" $ do         let xs = TS.tsSeries [1..5] [10.0, 1.2, 32.4, 0.65, 11.0]         let start = utcFromSeconds 12@@ -60,8 +67,8 @@         let ys = fmap (+ 2) xs         TS.values ys `shouldBe` [12.0, 3.2, 34.4, 2.65, 13.0] -    it "fold over series" $ do-        foldr (\v acc -> v + acc) 0.0 sampleSeries `shouldBe` 55.2+    it "fold over series" $+        foldr (+) 0.0 sampleSeries `shouldBe` 55.2      it "maximum value" $ do         let xs = TS.tsSeries [1..] [10.0, 1.2, 32.4, 0.65, 11.0]@@ -76,16 +83,34 @@      it "rolling window" $ do         let xs = TS.tsSeries [1..] [1.0, 2.0, 3.0, 4.0, 5.0]-        TS.rolling 2 sum xs `shouldBe` TS.tsSeries [2..] [3.0, 5.0, 7.0, 9.0]+        TS.rolling (TS.seconds 2) sum xs `shouldBe` TS.tsSeries [2..] [3.0, 5.0, 7.0, 9.0]      it "smoothing" $ do         let xs = TS.tsSeries [1..] [1.0, 2.0, 3.0, 4.0, 5.0]-        TS.rolling 3 (S.mean . V.fromList) xs `shouldBe` TS.tsSeries [3..] [2.0, 3.0, 4.0]+        TS.rolling (TS.seconds 3) (S.mean . V.fromList) xs `shouldBe` TS.tsSeries [3..] [2.0, 3.0, 4.0] -    it "resampling" $ do++  describe "Resampling" $ do++    it "the same" $ do         let xs = TS.tsSeries [1..] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]-        let startTime = posixSecondsToUTCTime $ fromIntegral 1-        TS.resample startTime (seconds 2) sum xs `shouldBe` TS.tsSeries [1, 3, 5] [3.0, 7.0, 11.0]+        let startTime = posixSecondsToUTCTime 1+        TS.resample startTime (seconds 2) xs `shouldBe` TS.tsSeries [1, 3, 5] [1.0, 3.0, 5.0]++    it "missing values" $ do+        let xs = TS.tsSeries [1, 2, 3, 5] [1.0, 2.0, 3.0, 5.0]+        let startTime = posixSecondsToUTCTime 1+        TS.resample startTime (seconds 1) xs `shouldBe` TS.tsSeries [1, 2, 3, 4, 5] [1.0, 2.0, 3.0, 4.0, 5.0]++    it "missing lots of values" $ do+        let xs = TS.tsSeries [1, 5] [1.0, 5.0]+        let startTime = posixSecondsToUTCTime 1+        TS.resample startTime (seconds 1) xs `shouldBe` TS.tsSeries [1, 2, 3, 4, 5] [1.0, 2.0, 3.0, 4.0, 5.0]++    it "middle values" $ do+        let xs = TS.tsSeries [1, 3, 5] [1.0, 3.0, 5.0]+        let startTime = posixSecondsToUTCTime 0+        TS.resample startTime (seconds 2) xs `shouldBe` TS.tsSeries [0, 2, 4] [1.0, 2.0, 4.0]   
+ test-src/Data/TimeSeries/SessionsSpec.hs view
@@ -0,0 +1,38 @@+module Data.TimeSeries.SessionsSpec (spec) where++import Test.Hspec+import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds)+import Data.Time (NominalDiffTime)+import qualified Data.TimeSeries.Sessions as S++import qualified Data.TimeSeries as TS+++sessionFromSeconds :: NominalDiffTime -> NominalDiffTime -> S.Session+sessionFromSeconds n m = S.Session (posixSecondsToUTCTime n) (posixSecondsToUTCTime m)++spec :: Spec+spec =++  describe "Sessions" $ do++    it "Without breaks" $ do+        let idx = [1..6]+        let values = repeat True+        S.find (TS.seconds 1) (TS.tsSeries idx values) `shouldBe` [sessionFromSeconds 1 6]++    it "Single session" $ do+        let idx = [1..]+        let values = [False, True, True, True, True, True, False, False]+        S.find (TS.seconds 1) (TS.tsSeries idx values) `shouldBe` [sessionFromSeconds 2 7]++    it "2 sessions" $ do+        let idx = [1..]+        let values = [False, True, True, True, False, False, True, False]+        S.find (TS.seconds 1) (TS.tsSeries idx values) `shouldBe` [ sessionFromSeconds 2 5+                                                                  , sessionFromSeconds 7 8]+    it "Single session 2 events" $ do+        let idx = [1..]+        let values = [False, True, True, False, True, True, False, False]+        S.find (TS.seconds 2) (TS.tsSeries idx values) `shouldBe` [sessionFromSeconds 2 7]+
test-src/Data/TimeSeries/TimeSpec.hs view
@@ -8,7 +8,7 @@   spec :: Spec-spec = do+spec =    describe "Time resolution" $ do 
test-src/Spec.hs view
@@ -1,5 +1,6 @@ import Test.Hspec import qualified Data.TimeSeries.SeriesSpec+import qualified Data.TimeSeries.SessionsSpec import qualified Data.TimeSeries.StatsSpec import qualified Data.TimeSeries.TimeSpec import qualified Data.TimeSeries.IO.CSVSpec@@ -8,6 +9,7 @@ main :: IO () main = hspec $ do   describe "Series" Data.TimeSeries.SeriesSpec.spec+  describe "Sessions" Data.TimeSeries.SessionsSpec.spec   describe "Statistcs" Data.TimeSeries.StatsSpec.spec   describe "Time" Data.TimeSeries.TimeSpec.spec   describe "CSV" Data.TimeSeries.IO.CSVSpec.spec
+ testdata/rain.csv view

file too large to diff

timeseries.cabal view
@@ -1,9 +1,10 @@ name:           timeseries-version:        0.3.0+version:        0.4.0 synopsis:       Library for Time Series processing License:        BSD3 License-file:   LICENSE Copyright:      (c) 2016 Krzysztof Langner+stability:      Unstable interface, incomplete features. Extra-Source-Files:                 README.md,                 CHANGES.md,@@ -39,10 +40,11 @@                     vector < 0.12   exposed-modules:                     Data.TimeSeries+                    Data.TimeSeries.Sessions   other-modules:-                    Data.TimeSeries.Series,-                    Data.TimeSeries.Stats,-                    Data.TimeSeries.Time,+                    Data.TimeSeries.Series+                    Data.TimeSeries.Stats+                    Data.TimeSeries.Time                     Data.TimeSeries.IO.CSVReader                     Data.TimeSeries.IO.CSVWriter @@ -64,12 +66,14 @@                     time >= 1.5 && < 2,                     vector < 0.12   other-modules:-                    Data.TimeSeries,-                    Data.TimeSeries.Series,-                    Data.TimeSeries.Stats,-                    Data.TimeSeries.Time,+                    Data.TimeSeries+                    Data.TimeSeries.Series+                    Data.TimeSeries.Sessions+                    Data.TimeSeries.Stats+                    Data.TimeSeries.Time                     Data.TimeSeries.SeriesSpec-                    Data.TimeSeries.TimeSpec,+                    Data.TimeSeries.SessionsSpec+                    Data.TimeSeries.TimeSpec                     Data.TimeSeries.IO.CSVReader                     Data.TimeSeries.IO.CSVWriter                     Data.TimeSeries.IO.CSVSpec