module Data.TimeSeries.Series (
DataPoint
, Series
, dpIndex
, dpValue
, emptySeries
, tsSeries
, series
, toList
, values
, rolling
, resample
, size
, slice
, valueAt
) where
import Prelude hiding (max, min)
import Data.Time ( UTCTime
, diffUTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.TimeSeries.Time (TimeResolution, nextTime)
data DataPoint a = DP { dpIndex :: !UTCTime
, dpValue :: a
}
deriving (Show, Eq)
instance Functor DataPoint where
fmap f (DP x y) = DP x (f y)
instance Foldable DataPoint where
foldMap f (DP _ y) = f y
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
emptySeries :: Series a
emptySeries = Series []
series :: [(UTCTime, a)] -> Series a
series xs = Series $ map (uncurry DP) xs
tsSeries :: [Integer]
-> [a]
-> Series a
tsSeries ts vs = Series (zipWith DP idx vs)
where idx = map (posixSecondsToUTCTime . fromIntegral) ts
toList :: Series a -> [(UTCTime, a)]
toList (Series xs) = map (\(DP x y) -> (x, y)) xs
values :: Series a -> [a]
values ts = map snd (toList ts)
size :: Series a -> Int
size (Series xs) = length xs
valueAt :: UTCTime
-> Series a
-> Maybe a
valueAt ts (Series xs) = safeHead [y | DP x y <- xs, x == ts]
where safeHead [] = Nothing
safeHead (i:_) = Just i
slice :: UTCTime
-> UTCTime
-> Series a
-> Series a
slice start end (Series xs) = Series [DP x y | DP x y <- xs, x >= start && x <= end]
rolling :: TimeResolution
-> ([a] -> b)
-> Series a
-> Series b
rolling dt f (Series xs) = Series $ map (\(i, vs) -> DP i (f vs)) (windows dt xs)
windows :: TimeResolution -> [DataPoint a] -> [(UTCTime, [a])]
windows _ [] = []
windows dt xs = g ys : if length xs > length ys then windows dt (tail xs) else []
where
ys = takeWhile (isInTimeRange dt (head xs)) xs
g vs = (dpIndex (last vs), values (Series vs))
isInTimeRange :: TimeResolution -> DataPoint a -> DataPoint a -> Bool
isInTimeRange dt (DP i _) (DP j _) = j < nextTime dt i
resample :: Fractional a
=> UTCTime
-> TimeResolution
-> Series a
-> Series a
resample _ _ (Series []) = emptySeries
resample utc res (Series xs) = Series (resample' utc res (head xs) xs)
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
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))