----------------------------------------------------------------------------- -- | -- Module : Data.Series -- Copyright : (c) Laurent P. René de Cotret -- License : MIT -- Maintainer : laurent.decotret@outlook.com -- Portability : portable -- -- This module contains data structures and functions to work with 'Series' capable of holding any Haskell value. -- For better performance, at the cost of less flexibility, see the "Data.Series.Unboxed". -- -- = Introduction to series -- -- A 'Series' of type @Series k a@ is a labeled array of values of type @a@, -- indexed by keys of type @k@. -- -- Like `Data.Map.Strict.Map` from the @containers@ package, 'Series' support efficient: -- -- * random access by key ( \(O(\log n)\) ); -- * slice by key ( \(O(\log n)\) ). -- -- Like `Data.Vector.Vector`, they support efficient: -- -- * random access by index ( \(O(1)\) ); -- * slice by index ( \(O(1)\) ); -- * numerical operations. -- -- This module re-exports most of the content of "Data.Series.Generic", with type signatures -- specialized to the boxed container type `Data.Vector.Vector`. -- -- For better performance (at the cost of more constraints), especially when it comes to numerical calculations, prefer to -- use "Data.Series.Unboxed", which contains an implementation of series specialized to the unboxed container type `Data.Vector.Unboxed.Vector`. module Data.Series ( Series, index, values, -- * Building/converting 'Series' singleton, fromIndex, -- ** Lists fromList, toList, -- ** Vectors fromVector, toVector, -- ** Handling duplicates Occurrence, fromListDuplicates, fromVectorDuplicates, -- ** Strict Maps fromStrictMap, toStrictMap, -- ** Lazy Maps fromLazyMap, toLazyMap, -- ** Ad-hoc conversion with other data structures IsSeries(..), -- ** Conversion between 'Series' types G.convert, -- * Mapping and filtering map, mapWithKey, mapIndex, concatMap, take, takeWhile, drop, dropWhile, filter, filterWithKey, -- ** Mapping with effects mapWithKeyM, mapWithKeyM_, forWithKeyM, forWithKeyM_, traverseWithKey, -- * Combining series zipWith, zipWithMatched, zipWithKey, zipWith3, zipWithMatched3, zipWithKey3, ZipStrategy, skipStrategy, mapStrategy, constStrategy, zipWithStrategy, zipWithStrategy3, zipWithMonoid, esum, eproduct, unzip, unzip3, -- * Index manipulation require, catMaybes, dropIndex, -- * Accessors -- ** Bulk access select, selectWhere, Range, to, from, upto, Selection, -- ** Single-element access at, iat, -- * Replacing values replace, (|->), (<-|), -- * Scans forwardFill, -- * Grouping and windowing operations groupBy, Grouping, aggregateWith, foldWith, windowing, expanding, -- * Folds fold, foldM, foldWithKey, foldMWithKey, foldMapWithKey, -- ** Specialized folds G.mean, G.variance, G.std, length, null, all, any, and, or, sum, product, maximum, maximumOn, minimum, minimumOn, argmin, argmax, -- * Scans postscanl, prescanl, -- * Displaying 'Series' display, displayWith, noLongerThan, DisplayOptions(..), G.defaultDisplayOptions ) where import Control.Foldl ( Fold, FoldM ) import qualified Data.Map.Lazy as ML import qualified Data.Map.Strict as MS import Data.Series.Index ( Index ) import Data.Series.Generic ( IsSeries(..), Range, Selection, ZipStrategy, Occurrence, DisplayOptions(..) , to, from, upto, skipStrategy, mapStrategy, constStrategy, noLongerThan ) import qualified Data.Series.Generic as G import Data.Vector ( Vector ) import Prelude hiding ( map, concatMap, zipWith, zipWith3, filter, take, takeWhile, drop, dropWhile, last, unzip, unzip3 , length, null, all, any, and, or, sum, product, maximum, minimum, ) -- $setup -- >>> import qualified Data.Series as Series -- >>> import qualified Data.Series.Index as Index infixl 1 `select` infix 6 |->, <-| -- | A series is a labeled array of values of type @a@, -- indexed by keys of type @k@. -- -- Like @Data.Map@ and @Data.HashMap@, they support efficient: -- -- * random access by key ( \(O(\log n)\) ); -- * slice by key ( \(O(\log n)\) ). -- -- Like @Data.Vector.Vector@, they support efficient: -- -- * random access by index ( \(O(1)\) ); -- * slice by index ( \(O(1)\) ); -- * numerical operations. type Series = G.Series Vector index :: Series k a -> Index k {-# INLINABLE index #-} index = G.index values :: Series k a -> Vector a {-# INLINABLE values #-} values = G.values -- | Create a 'Series' with a single element. singleton :: k -> a -> Series k a {-# INLINABLE singleton #-} singleton = G.singleton -- | \(O(n)\) Generate a 'Series' by mapping every element of its index. -- -- >>> fromIndex (const (0::Int)) $ Index.fromList ['a','b','c','d'] -- index | values -- ----- | ------ -- 'a' | 0 -- 'b' | 0 -- 'c' | 0 -- 'd' | 0 fromIndex :: (k -> a) -> Index k -> Series k a {-# INLINABLE fromIndex #-} fromIndex = G.fromIndex -- | Construct a series from a list of key-value pairs. There is no -- condition on the order of pairs. -- -- >>> let xs = fromList [('b', 0::Int), ('a', 5), ('d', 1) ] -- >>> xs -- index | values -- ----- | ------ -- 'a' | 5 -- 'b' | 0 -- 'd' | 1 -- -- If you need to handle duplicate keys, take a look at `fromListDuplicates`. fromList :: Ord k => [(k, a)] -> Series k a {-# INLINABLE fromList #-} fromList = G.fromList -- | Construct a series from a list of key-value pairs. -- Contrary to `fromList`, values at duplicate keys are preserved. To keep each -- key unique, an `Occurrence` number counts up. -- -- >>> let xs = fromListDuplicates [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ] -- >>> xs -- index | values -- ----- | ------ -- ('a',0) | 5 -- ('b',0) | 0 -- ('d',0) | 1 -- ('d',1) | -4 -- ('d',2) | 7 fromListDuplicates :: Ord k => [(k, a)] -> Series (k, Occurrence) a {-# INLINABLE fromListDuplicates #-} fromListDuplicates = G.fromListDuplicates -- | Construct a list from key-value pairs. The elements are in order sorted by key: -- -- >>> let xs = Series.fromList [ ('b', 0::Int), ('a', 5), ('d', 1) ] -- >>> xs -- index | values -- ----- | ------ -- 'a' | 5 -- 'b' | 0 -- 'd' | 1 -- >>> toList xs -- [('a',5),('b',0),('d',1)] toList :: Series k a -> [(k, a)] {-# INLINABLE toList #-} toList = G.toList -- | Construct a 'Vector' of key-value pairs. The elements are in order sorted by key. toVector :: Series k a -> Vector (k, a) {-# INLINABLE toVector #-} toVector = G.toVector -- | Construct a 'Series' from a 'Vector' of key-value pairs. There is no -- condition on the order of pairs. Duplicate keys are silently dropped. If you -- need to handle duplicate keys, see 'fromVectorDuplicates'. -- -- Note that due to differences in sorting, -- @'Series.fromList'@ and @'Series.fromVector' . 'Vector.fromList'@ -- may not be equivalent if the input list contains duplicate keys. fromVector :: Ord k => Vector (k, a) -> Series k a {-# INLINABLE fromVector #-} fromVector = G.fromVector -- | Construct a series from a 'Vector' of key-value pairs. -- Contrary to 'fromVector', values at duplicate keys are preserved. To keep each -- key unique, an 'Occurrence' number counts up. -- -- >>> import qualified Data.Vector as Vector -- >>> let xs = fromVectorDuplicates $ Vector.fromList [('b', 0::Int), ('a', 5), ('d', 1), ('d', -4), ('d', 7) ] -- >>> xs -- index | values -- ----- | ------ -- ('a',0) | 5 -- ('b',0) | 0 -- ('d',0) | 1 -- ('d',1) | -4 -- ('d',2) | 7 fromVectorDuplicates :: Ord k => Vector (k, a) -> Series (k, Occurrence) a {-# INLINABLE fromVectorDuplicates #-} fromVectorDuplicates = G.fromVectorDuplicates -- | Convert a series into a lazy @Map@. toLazyMap :: Series k a -> ML.Map k a {-# INLINABLE toLazyMap #-} toLazyMap = G.toLazyMap -- | Construct a series from a lazy @Map@. fromLazyMap :: ML.Map k a -> Series k a {-# INLINABLE fromLazyMap #-} fromLazyMap = G.fromLazyMap -- | Convert a series into a strict @Map@. toStrictMap :: Series k a -> MS.Map k a {-# INLINABLE toStrictMap #-} toStrictMap = G.toStrictMap -- | Construct a series from a strict @Map@. fromStrictMap :: MS.Map k a -> Series k a {-# INLINABLE fromStrictMap #-} fromStrictMap = G.fromStrictMap -- | \(O(n)\) Map every element of a 'Series'. map :: (a -> b) -> Series k a -> Series k b {-# INLINABLE map #-} map = G.map -- | \(O(n)\) Map every element of a 'Series', possibly using the key as well. mapWithKey :: (k -> a -> b) -> Series k a -> Series k b {-# INLINABLE mapWithKey #-} mapWithKey = G.mapWithKey -- | \(O(n \log n)\). -- Map each key in the index to another value. Note that the resulting series -- may have less elements, because each key must be unique. -- -- In case new keys are conflicting, the first element is kept. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> import qualified Data.List -- >>> xs `mapIndex` (Data.List.take 1) -- index | values -- ----- | ------ -- "L" | 4 -- "P" | 1 mapIndex :: (Ord k, Ord g) => Series k a -> (k -> g) -> Series g a {-# INLINABLE mapIndex #-} mapIndex = G.mapIndex -- | Map a function over all the elements of a 'Series' and concatenate the result into a single 'Series'. concatMap :: Ord k => (a -> Series k b) -> Series k a -> Series k b {-# INLINABLE concatMap #-} concatMap = G.concatMap -- | \(O(n)\) Apply the monadic action to every element of a series and its -- index, yielding a series of results. mapWithKeyM :: (Monad m, Ord k) => (k -> a -> m b) -> Series k a -> m (Series k b) {-# INLINABLE mapWithKeyM #-} mapWithKeyM = G.mapWithKeyM -- | \(O(n)\) Apply the monadic action to every element of a series and its -- index, discarding the results. mapWithKeyM_ :: Monad m => (k -> a -> m b) -> Series k a -> m () {-# INLINABLE mapWithKeyM_ #-} mapWithKeyM_ = G.mapWithKeyM_ -- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, -- yielding a series of results. forWithKeyM :: (Monad m, Ord k) => Series k a -> (k -> a -> m b) -> m (Series k b) {-# INLINABLE forWithKeyM #-} forWithKeyM = G.forWithKeyM -- | \(O(n)\) Apply the monadic action to all elements of the series and their associated keys, -- discarding the results. forWithKeyM_ :: Monad m => Series k a -> (k -> a -> m b) -> m () {-# INLINABLE forWithKeyM_ #-} forWithKeyM_ = G.forWithKeyM_ -- | \(O(n)\) Traverse a 'Series' with an Applicative action, taking into account both keys and values. traverseWithKey :: (Applicative t, Ord k) => (k -> a -> t b) -> Series k a -> t (Series k b) {-# INLINABLE traverseWithKey #-} traverseWithKey = G.traverseWithKey -- | \(O(\log n)\) @'take' n xs@ returns at most @n@ elements of the 'Series' @xs@. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- "Vienna" | 5 -- >>> take 2 xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 take :: Int -> Series k a -> Series k a {-# INLINABLE take #-} take = G.take -- | \(O(n)\) Returns the longest prefix (possibly empty) of the input 'Series' that satisfy a predicate. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- "Vienna" | 5 -- >>> takeWhile (>1) xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 takeWhile :: (a -> Bool) -> Series k a -> Series k a takeWhile = G.takeWhile -- | \(O(\log n)\) @'drop' n xs@ drops at most @n@ elements from the 'Series' @xs@. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- "Vienna" | 5 -- >>> drop 2 xs -- index | values -- ----- | ------ -- "Paris" | 1 -- "Vienna" | 5 drop :: Int -> Series k a -> Series k a {-# INLINABLE drop #-} drop = G.drop -- | \(O(n)\) Returns the complement of `takeWhile`. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4), ("Vienna", 5)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- "Vienna" | 5 -- >>> dropWhile (>1) xs -- index | values -- ----- | ------ -- "Paris" | 1 -- "Vienna" | 5 dropWhile :: (a -> Bool) -> Series k a -> Series k a dropWhile = G.dropWhile -- | Apply a function elementwise to two series, matching elements -- based on their keys. For keys present only in the left or right series, -- the value 'Nothing' is returned. -- -- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ] -- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ] -- >>> zipWith (+) xs ys -- index | values -- ----- | ------ -- "alpha" | Just 10 -- "beta" | Just 12 -- "delta" | Nothing -- "gamma" | Nothing -- -- To only combine elements where keys are in both series, see 'zipWithMatched'. zipWith :: (Ord k) => (a -> b -> c) -> Series k a -> Series k b -> Series k (Maybe c) zipWith = G.zipWith {-# INLINABLE zipWith #-} -- | Apply a function elementwise to three series, matching elements -- based on their keys. For keys present only in the left or right series, -- the value 'Nothing' is returned. -- -- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ] -- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ] -- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ] -- >>> zipWith3 (\x y z -> x + y + z) xs ys zs -- index | values -- ----- | ------ -- "alpha" | Just 30 -- "beta" | Nothing -- "delta" | Nothing -- "epsilon" | Nothing -- "gamma" | Nothing -- -- To only combine elements where keys are in all series, see 'zipWithMatched3' zipWith3 :: (Ord k) => (a -> b -> c -> d) -> Series k a -> Series k b -> Series k c -> Series k (Maybe d) {-# INLINABLE zipWith3 #-} zipWith3 = G.zipWith3 -- | Apply a function elementwise to two series, matching elements -- based on their keys. Keys present only in the left or right series are dropped. -- -- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ] -- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ] -- >>> zipWithMatched (+) xs ys -- index | values -- ----- | ------ -- "alpha" | 10 -- "beta" | 12 -- -- To combine elements where keys are in either series, see 'zipWith'. zipWithMatched :: Ord k => (a -> b -> c) -> Series k a -> Series k b -> Series k c {-# INLINABLE zipWithMatched #-} zipWithMatched = G.zipWithMatched -- | Apply a function elementwise to three series, matching elements -- based on their keys. Keys not present in all three series are dropped. -- -- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ] -- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ] -- >>> let zs = Series.fromList [ ("alpha", 20::Int), ("delta", 13), ("epsilon", 6) ] -- >>> zipWithMatched3 (\x y z -> x + y + z) xs ys zs -- index | values -- ----- | ------ -- "alpha" | 30 zipWithMatched3 :: (Ord k) => (a -> b -> c -> d) -> Series k a -> Series k b -> Series k c -> Series k d {-# INLINABLE zipWithMatched3 #-} zipWithMatched3 = G.zipWithMatched3 -- | Apply a function elementwise to two series, matching elements -- based on their keys. Keys present only in the left or right series are dropped. -- -- To combine elements where keys are in either series, see 'zipWith' zipWithKey :: (Ord k) => (k -> a -> b -> c) -> Series k a -> Series k b -> Series k c {-# INLINABLE zipWithKey #-} zipWithKey = G.zipWithKey -- | Apply a function elementwise to three series, matching elements -- based on their keys. Keys present only in the left or right series are dropped. -- -- To combine elements where keys are in any series, see 'zipWith3' zipWithKey3 :: (Ord k) => (k -> a -> b -> c -> d) -> Series k a -> Series k b -> Series k c -> Series k d {-# INLINABLE zipWithKey3 #-} zipWithKey3 = G.zipWithKey3 -- | Zip two 'Series' with a combining function, applying a `ZipStrategy` when one key is present in one of the 'Series' but not both. -- -- In the example below, we want to set the value to @-100@ (via @`constStrategy` (-100)@) for keys which are only present -- in the left 'Series', and drop keys (via `skipStrategy`) which are only present in the `right 'Series' -- -- >>> let xs = Series.fromList [ ("alpha", 0::Int), ("beta", 1), ("gamma", 2) ] -- >>> let ys = Series.fromList [ ("alpha", 10::Int), ("beta", 11), ("delta", 13) ] -- >>> zipWithStrategy (+) (constStrategy (-100)) skipStrategy xs ys -- index | values -- ----- | ------ -- "alpha" | 10 -- "beta" | 12 -- "gamma" | -100 -- -- Note that if you want to drop keys missing in either 'Series', it is faster to use @`zipWithMatched` f@ -- than using @`zipWithStrategy` f skipStrategy skipStrategy@. zipWithStrategy :: (Ord k) => (a -> b -> c) -- ^ Function to combine values when present in both series -> ZipStrategy k a c -- ^ Strategy for when the key is in the left series but not the right -> ZipStrategy k b c -- ^ Strategy for when the key is in the right series but not the left -> Series k a -> Series k b -> Series k c {-# INLINABLE zipWithStrategy #-} zipWithStrategy = G.zipWithStrategy -- | Zip three 'Series' with a combining function, applying a 'ZipStrategy' when one key is -- present in one of the 'Series' but not all of the others. -- -- Note that if you want to drop keys missing in either 'Series', it is faster to use @'zipWithMatched3' f@ -- than using @'zipWithStrategy3' f skipStrategy skipStrategy skipStrategy@. zipWithStrategy3 :: (Ord k) => (a -> b -> c -> d) -- ^ Function to combine values when present in all series -> ZipStrategy k a d -- ^ Strategy for when the key is in the left series but not in all the others -> ZipStrategy k b d -- ^ Strategy for when the key is in the center series but not in all the others -> ZipStrategy k c d -- ^ Strategy for when the key is in the right series but not in all the others -> Series k a -> Series k b -> Series k c -> Series k d {-# INLINABLE zipWithStrategy3 #-} zipWithStrategy3 = G.zipWithStrategy3 -- | Zip two 'Series' with a combining function. The value for keys which are missing from -- either 'Series' is replaced with the appropriate `mempty` value. -- -- >>> import Data.Monoid ( Sum(..) ) -- >>> let xs = Series.fromList [ ("2023-01-01", Sum (1::Int)), ("2023-01-02", Sum 2) ] -- >>> let ys = Series.fromList [ ("2023-01-01", Sum (5::Int)), ("2023-01-03", Sum 7) ] -- >>> Series.zipWith (<>) xs ys -- index | values -- ----- | ------ -- "2023-01-01" | Just (Sum {getSum = 6}) -- "2023-01-02" | Nothing -- "2023-01-03" | Nothing -- >>> zipWithMonoid (<>) xs ys -- index | values -- ----- | ------ -- "2023-01-01" | Sum {getSum = 6} -- "2023-01-02" | Sum {getSum = 2} -- "2023-01-03" | Sum {getSum = 7} zipWithMonoid :: ( Monoid a, Monoid b, Ord k) => (a -> b -> c) -> Series k a -> Series k b -> Series k c zipWithMonoid = G.zipWithMonoid {-# INLINABLE zipWithMonoid #-} -- | Elementwise sum of two 'Series'. Elements missing in one or the other 'Series' is considered 0. -- -- >>> let xs = Series.fromList [ ("2023-01-01", (1::Int)), ("2023-01-02", 2) ] -- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ] -- >>> xs `esum` ys -- index | values -- ----- | ------ -- "2023-01-01" | 6 -- "2023-01-02" | 2 -- "2023-01-03" | 7 esum :: (Ord k, Num a) => Series k a -> Series k a -> Series k a esum = G.esum {-# INLINABLE esum #-} -- | Elementwise product of two 'Series'. Elements missing in one or the other 'Series' is considered 1. -- -- >>> let xs = Series.fromList [ ("2023-01-01", (2::Int)), ("2023-01-02", 3) ] -- >>> let ys = Series.fromList [ ("2023-01-01", (5::Int)), ("2023-01-03", 7) ] -- >>> xs `eproduct` ys -- index | values -- ----- | ------ -- "2023-01-01" | 10 -- "2023-01-02" | 3 -- "2023-01-03" | 7 eproduct :: (Ord k, Num a) => Series k a -> Series k a -> Series k a eproduct = G.eproduct {-# INLINABLE eproduct #-} -- | \(O(n)\) Unzip a 'Series' of 2-tuples. unzip :: Series k (a, b) -> ( Series k a , Series k b ) unzip = G.unzip {-# INLINABLE unzip #-} -- | \(O(n)\) Unzip a 'Series' of 3-tuples. unzip3 :: Series k (a, b, c) -> ( Series k a , Series k b , Series k c ) unzip3 = G.unzip3 {-# INLINABLE unzip3 #-} -- | Require a series to have a specific `Index`. -- Contrary to @select@, all keys in the `Index` will be present in the resulting series. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> xs `require` Index.fromList ["Paris", "Lisbon", "Taipei"] -- index | values -- ----- | ------ -- "Lisbon" | Just 4 -- "Paris" | Just 1 -- "Taipei" | Nothing require :: Ord k => Series k a -> Index k -> Series k (Maybe a) {-# INLINABLE require #-} require = G.require -- | \(O(n)\) Drop the index of a series by replacing it with an `Int`-based index. Values will -- be indexed from 0. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> dropIndex xs -- index | values -- ----- | ------ -- 0 | 4 -- 1 | 2 -- 2 | 1 dropIndex :: Series k a -> Series Int a {-# INLINABLE dropIndex #-} dropIndex = G.dropIndex -- | Filter elements. Only elements for which the predicate is @True@ are kept. -- Notice that the filtering is done on the values, not on the keys. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> filter (>2) xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- -- See also 'filterWithKey'. filter :: Ord k => (a -> Bool) -> Series k a -> Series k a {-# INLINABLE filter #-} filter = G.filter -- | Filter elements, taking into account the corresponding key. Only elements for which -- the predicate is @True@ are kept. filterWithKey :: Ord k => (k -> a -> Bool) -> Series k a -> Series k a {-# INLINABLE filterWithKey #-} filterWithKey = G.filterWithKey -- | Drop elements which are not available (NA). -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> let ys = xs `require` Index.fromList ["Paris", "London", "Lisbon", "Toronto"] -- >>> ys -- index | values -- ----- | ------ -- "Lisbon" | Just 4 -- "London" | Just 2 -- "Paris" | Just 1 -- "Toronto" | Nothing -- >>> catMaybes ys -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 catMaybes :: Ord k => Series k (Maybe a) -> Series k a {-# INLINABLE catMaybes #-} catMaybes = G.catMaybes -- | Select a subseries. There are a few ways to do this. -- -- The first way to do this is to select a sub-series based on random keys. For example, -- selecting a subseries from an `Index`: -- -- >>> let xs = Series.fromList [('a', 10::Int), ('b', 20), ('c', 30), ('d', 40)] -- >>> xs `select` Index.fromList ['a', 'd'] -- index | values -- ----- | ------ -- 'a' | 10 -- 'd' | 40 -- -- The second way to select a sub-series is to select all keys in a range: -- -- >>> xs `select` 'b' `to` 'c' -- index | values -- ----- | ------ -- 'b' | 20 -- 'c' | 30 -- -- Note that with `select`, you'll always get a sub-series; if you ask for a key which is not -- in the series, it'll be ignored: -- -- >>> xs `select` Index.fromList ['a', 'd', 'e'] -- index | values -- ----- | ------ -- 'a' | 10 -- 'd' | 40 -- -- See `require` if you want to ensure that all keys are present. select :: (Selection s, Ord k) => Series k a -> s k -> Series k a select = G.select -- | Select a sub-series from a series matching a condition. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> xs `selectWhere` (fmap (>1) xs) -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 selectWhere :: Ord k => Series k a -> Series k Bool -> Series k a {-# INLINABLE selectWhere #-} selectWhere = G.selectWhere -- | \(O(\log n)\). Extract a single value from a series, by key. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs `at` "Paris" -- Just 1 -- >>> xs `at` "Sydney" -- Nothing at :: Ord k => Series k a -> k -> Maybe a {-# INLINABLE at #-} at = G.at -- | \(O(1)\). Extract a single value from a series, by index. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> xs `iat` 0 -- Just 4 -- >>> xs `iat` 3 -- Nothing iat :: Series k a -> Int -> Maybe a {-# INLINABLE iat #-} iat = G.iat -- | Replace values in the right series from values in the left series at matching keys. -- Keys not in the right series are unaffected. -- -- See `(|->)` and `(<-|)`, which might be more readable. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> let ys = Series.singleton "Paris" (99::Int) -- >>> ys `replace` xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 99 replace :: Ord k => Series k a -> Series k a -> Series k a {-# INLINABLE replace #-} replace = G.replace -- | Replace values in the right series from values in the left series at matching keys. -- Keys not in the right series are unaffected. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> let ys = Series.singleton "Paris" (99::Int) -- >>> ys |-> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 99 (|->) :: (Ord k) => Series k a -> Series k a -> Series k a {-# INLINABLE (|->) #-} (|->) = (G.|->) -- | Replace values in the left series from values in the right series at matching keys. -- Keys not in the left series are unaffected. -- -- >>> let xs = Series.fromList [("Paris", 1 :: Int), ("London", 2), ("Lisbon", 4)] -- >>> xs -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 1 -- >>> let ys = Series.singleton "Paris" (99::Int) -- >>> xs <-| ys -- index | values -- ----- | ------ -- "Lisbon" | 4 -- "London" | 2 -- "Paris" | 99 (<-|) :: (Ord k) => Series k a -> Series k a -> Series k a {-# INLINABLE (<-|) #-} (<-|) = (G.<-|) -- | \(O(n)\) Replace all instances of 'Nothing' with the last previous -- value which was not 'Nothing'. -- -- >>> let xs = Series.fromList (zip [0..] [Just 1, Just 2,Nothing, Just 3]) :: Series Int (Maybe Int) -- >>> xs -- index | values -- ----- | ------ -- 0 | Just 1 -- 1 | Just 2 -- 2 | Nothing -- 3 | Just 3 -- >>> forwardFill 0 xs -- index | values -- ----- | ------ -- 0 | 1 -- 1 | 2 -- 2 | 2 -- 3 | 3 -- -- If the first entry of the series is missing, the first input to 'forwardFill' will be used: -- -- >>> let ys = Series.fromList (zip [0..] [Nothing, Just 2,Nothing, Just 3]) :: Series Int (Maybe Int) -- >>> ys -- index | values -- ----- | ------ -- 0 | Nothing -- 1 | Just 2 -- 2 | Nothing -- 3 | Just 3 -- >>> forwardFill 0 ys -- index | values -- ----- | ------ -- 0 | 0 -- 1 | 2 -- 2 | 2 -- 3 | 3 forwardFill :: a -- ^ Until the first non-'Nothing' is found, 'Nothing' will be filled with this value. -> Series v (Maybe a) -> Series v a {-# INLINABLE forwardFill #-} forwardFill = G.forwardFill -- | \(O(n)\) Execute a 'Fold' over a 'Series'. -- -- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Double -- >>> xs -- index | values -- ----- | ------ -- 0 | 1.0 -- 1 | 2.0 -- 2 | 3.0 -- 3 | 4.0 -- >>> import Control.Foldl (variance) -- >>> fold variance xs -- 1.25 -- -- See also 'foldM' for monadic folds, and 'foldWithKey' to take keys into -- account while folding. fold :: Fold a b -> Series k a -> b fold = G.fold {-# INLINABLE fold #-} -- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series'. -- -- See also 'fold' for pure folds, and 'foldMWithKey' to take keys into -- account while folding. foldM :: (Monad m) => FoldM m a b -> Series k a -> m b foldM = G.foldM {-# INLINABLE foldM #-} -- | \(O(n)\) Execute a 'Fold' over a 'Series', taking keys into account. foldWithKey :: Fold (k, a) b -> Series k a -> b foldWithKey = G.foldWithKey {-# INLINABLE foldWithKey #-} -- | \(O(n)\) Execute a monadic 'FoldM' over a 'Series', where the 'FoldM' takes keys into account. foldMWithKey :: (Monad m) => FoldM m (k, a) b -> Series k a -> m b foldMWithKey = G.foldMWithKey {-# INLINABLE foldMWithKey #-} -- | \(O(n)\) Map each element and associated key of the structure to a monoid and combine -- the results. foldMapWithKey :: Monoid m => (k -> a -> m) -> Series k a -> m {-# INLINABLE foldMapWithKey #-} foldMapWithKey = G.foldMapWithKey -- | Group values in a 'Series' by some grouping function (@k -> g@). -- The provided grouping function is guaranteed to operate on a non-empty 'Series'. -- -- This function is expected to be used in conjunction with 'aggregateWith': -- -- >>> import Data.Maybe ( fromMaybe ) -- >>> type Date = (Int, String) -- >>> month :: (Date -> String) = snd -- >>> :{ -- let xs = Series.fromList [ ((2020, "January") :: Date, 0 :: Int) -- , ((2021, "January"), -5) -- , ((2020, "June") , 20) -- , ((2021, "June") , 25) -- ] -- in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum) -- :} -- index | values -- ----- | ------ -- "January" | -5 -- "June" | 20 groupBy :: Series k a -- ^ Grouping function ->(k -> g) -- ^ Input series -> Grouping k g a -- ^ Grouped series {-# INLINABLE groupBy #-} groupBy = G.groupBy -- | Representation of a 'Series' being grouped. type Grouping k g a = G.Grouping k g Vector a -- | Aggregate groups resulting from a call to 'groupBy': -- -- >>> import Data.Maybe ( fromMaybe ) -- >>> type Date = (Int, String) -- >>> month :: (Date -> String) = snd -- >>> :{ -- let xs = Series.fromList [ ((2020, "January") :: Date, 0 :: Int) -- , ((2021, "January"), -5) -- , ((2020, "June") , 20) -- , ((2021, "June") , 25) -- ] -- in xs `groupBy` month `aggregateWith` (fromMaybe 0 . minimum) -- :} -- index | values -- ----- | ------ -- "January" | -5 -- "June" | 20 -- -- If you want to aggregate groups using a binary function, see 'foldWith' which -- may be much faster. aggregateWith :: (Ord g) => Grouping k g a -> (Series k a -> b) -> Series g b {-# INLINABLE aggregateWith #-} aggregateWith = G.aggregateWith -- | Aggregate each group in a 'Grouping' using a binary function. -- While this is not as expressive as 'aggregateWith', users looking for maximum -- performance should use 'foldWith' as much as possible. foldWith :: Ord g => Grouping k g a -> (a -> a -> a) -> Series g a {-# INLINABLE foldWith #-} foldWith = G.foldWith -- | Expanding window aggregation. -- -- >>> import qualified Data.Series as Series -- >>> :{ -- let (xs :: Series.Series Int Int) -- = Series.fromList [ (1, 0) -- , (2, 1) -- , (3, 2) -- , (4, 3) -- , (5, 4) -- , (6, 5) -- ] -- in (xs `expanding` sum) :: Series.Series Int Int -- :} -- index | values -- ----- | ------ -- 1 | 0 -- 2 | 1 -- 3 | 3 -- 4 | 6 -- 5 | 10 -- 6 | 15 expanding :: Series k a -- ^ Series vector -> (Series k a -> b) -- ^ Aggregation function -> Series k b -- ^ Resulting vector {-# INLINABLE expanding #-} expanding = G.expanding -- | General-purpose window aggregation. -- -- >>> import qualified Data.Series as Series -- >>> :{ -- let (xs :: Series.Series Int Int) -- = Series.fromList [ (1, 0) -- , (2, 1) -- , (3, 2) -- , (4, 3) -- , (5, 4) -- , (6, 5) -- ] -- in windowing (\k -> k `to` (k+2)) sum xs -- :} -- index | values -- ----- | ------ -- 1 | 3 -- 2 | 6 -- 3 | 9 -- 4 | 12 -- 5 | 9 -- 6 | 5 windowing :: Ord k => (k -> Range k) -> (Series k a -> b) -> Series k a -> Series k b {-# INLINABLE windowing #-} windowing = G.windowing -- | \(O(1)\) Test whether a 'Series' is empty. null :: Series k a -> Bool {-# INLINABLE null #-} null = G.null -- |\(O(1)\) Extract the length of a 'Series'. length :: Series k a -> Int {-# INLINABLE length #-} length = G.length -- | \(O(n)\) Check if all elements satisfy the predicate. all :: (a -> Bool) -> Series k a -> Bool {-# INLINABLE all #-} all = G.all -- | \(O(n)\) Check if any element satisfies the predicate. any :: (a -> Bool) -> Series k a -> Bool {-# INLINABLE any #-} any = G.any -- | \(O(n)\) Check if all elements are 'True'. and :: Series k Bool -> Bool {-# INLINABLE and #-} and = G.and -- | \(O(n)\) Check if any element is 'True'. or :: Series k Bool -> Bool {-# INLINABLE or #-} or = G.or -- | \(O(n)\) Compute the sum of the elements. sum :: (Num a) => Series k a -> a {-# INLINABLE sum #-} sum = G.sum -- | \(O(n)\) Compute the product of the elements. product :: (Num a) => Series k a -> a {-# INLINABLE product #-} product = G.product -- | \(O(n)\) Yield the maximum element of the series. In case of a tie, the first occurrence wins. -- If the 'Series' is empty, @Nothing@ is returned. -- -- See also 'argmax'. maximum :: (Ord a) => Series k a -> Maybe a {-# INLINABLE maximum #-} maximum = G.maximum -- | \(O(n)\) @'maximumOn' f xs@ teturns the maximum element of the series @xs@, as determined by the function @f@. -- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned. maximumOn :: (Ord b) => (a -> b) -> Series k a -> Maybe a {-# INLINABLE maximumOn #-} maximumOn = G.maximumOn -- | \(O(n)\) Yield the minimum element of the series. In case of a tie, the first occurrence wins. -- If the 'Series' is empty, @Nothing@ is returned. -- -- See also 'argmin'. minimum :: (Ord a) => Series k a -> Maybe a {-# INLINABLE minimum #-} minimum = G.minimum -- | \(O(n)\) @'minimumOn' f xs@ teturns the minimum element of the series @xs@, as determined by the function @f@. -- In case of a tie, the first occurrence wins. If the 'Series' is empty, @Nothing@ is returned. minimumOn :: (Ord b) => (a -> b) -> Series k a -> Maybe a {-# INLINABLE minimumOn #-} minimumOn = G.minimumOn -- | \(O(n)\) Find the index of the maximum element in the input series. -- If the input series is empty, 'Nothing' is returned. -- -- The index of the first occurrence of the maximum element is returned. -- -- >>> :{ -- let (xs :: Series Int Int) -- = Series.fromList [ (1, 0) -- , (2, 1) -- , (3, 2) -- , (4, 7) -- , (5, 4) -- , (6, 5) -- ] -- in argmax xs -- :} -- Just 4 argmax :: Ord a => Series k a -> Maybe k argmax = G.argmax {-# INLINABLE argmax #-} -- | \(O(n)\) Find the index of the minimum element in the input series. -- If the input series is empty, 'Nothing' is returned. -- -- The index of the first occurrence of the minimum element is returned. -- >>> :{ -- let (xs :: Series Int Int) -- = Series.fromList [ (1, 1) -- , (2, 1) -- , (3, 2) -- , (4, 0) -- , (5, 4) -- , (6, 5) -- ] -- in argmin xs -- :} -- Just 4 argmin :: Ord a => Series k a -> Maybe k argmin = G.argmin {-# INLINABLE argmin #-} -- | \(O(n)\) Left-to-right postscan. -- -- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int -- >>> xs -- index | values -- ----- | ------ -- 0 | 1 -- 1 | 2 -- 2 | 3 -- 3 | 4 -- >>> postscanl (+) 0 xs -- index | values -- ----- | ------ -- 0 | 1 -- 1 | 3 -- 2 | 6 -- 3 | 10 postscanl :: (a -> b -> a) -> a -> Series k b -> Series k a {-# INLINABLE postscanl #-} postscanl = G.postscanl -- | \(O(n)\) Left-to-right prescan. -- -- >>> let xs = Series.fromList (zip [0..] [1,2,3,4]) :: Series Int Int -- >>> xs -- index | values -- ----- | ------ -- 0 | 1 -- 1 | 2 -- 2 | 3 -- 3 | 4 -- >>> prescanl (+) 0 xs -- index | values -- ----- | ------ -- 0 | 0 -- 1 | 1 -- 2 | 3 -- 3 | 6 prescanl :: (a -> b -> a) -> a -> Series k b -> Series k a {-# INLINABLE prescanl #-} prescanl = G.prescanl -- | Display a 'Series' using default 'DisplayOptions'. -- -- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int -- >>> putStrLn $ display xs -- index | values -- ----- | ------ -- 0 | 1 -- 1 | 2 -- 2 | 3 -- ... | ... -- 4 | 5 -- 5 | 6 -- 6 | 7 display :: (Show k, Show a) => Series k a -> String display = G.display -- | Display a 'Series' using customizable 'DisplayOptions'. -- -- >>> let xs = Series.fromList (zip [0..] [1,2,3,4,5,6,7]) :: Series Int Int -- >>> import Data.List (replicate) -- >>> :{ -- let opts = DisplayOptions { maximumNumberOfRows = 4 -- , indexHeader = "keys" -- , valuesHeader = "vals" -- , keyDisplayFunction = (\i -> replicate i 'x') `noLongerThan` 5 -- , valueDisplayFunction = (\i -> replicate i 'o') -- } -- in putStrLn $ displayWith opts xs -- :} -- keys | vals -- ----- | ------ -- | o -- x | oo -- ... | ... -- xxxxx | oooooo -- xxx... | ooooooo displayWith :: DisplayOptions k a -> Series k a -> String displayWith = G.displayWith