packages feed

hasklepias 0.12.0 → 0.13.0

raw patch · 17 files changed

+389/−18 lines, 17 filesdep +nonempty-containersdep +semiring-simple

Dependencies added: nonempty-containers, semiring-simple

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Changelog for hasklepias +## 0.13.0++* Adds a rudimentary `Attributes` type and `HasAttributes` typeclass for adding attributes to `Feature`s. This interfaces will likely change in the future, but for now users have the ability to add information like labels to a `Feature`. In fact, `Feature`s which are encoded to JSON are *required* to have attributes.+* Adds rudimentary `Stype` module in order to (ultimately) interface with the [R `stype` package](https://docs.novisci.com/stype/). For now, this module simply creates a few of the types, some of which are *not* an appropriate implementation. For example, the `Nominal` type is simply `newtype Nominal a = Nominal a`. Essentially, it's just a way to label something as nominal at this point.+ ## 0.12.0  * Converts `AttritionStatus` from a List to a NonEmpty container.
exampleApp/Main.hs view
@@ -12,6 +12,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-} module Main(   main ) where@@ -36,6 +37,9 @@    ( Feature "allEvents" (Events Day)    -> Feature "dummy" Status) critTrue = define $ pure Include ++instance HasAttributes "dummy" Bool where+  getAttributes _ = MkAttributes "Text" "Text" "Text"  {-------------------------------------------------------------------------------   Cohort Specifications and evaluation
examples/ExampleCohort1.hs view
@@ -8,10 +8,11 @@  {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} module ExampleCohort1(   exampleCohort1tests ) where@@ -65,17 +66,18 @@ -------------------------------------------------------------------------------}  -- | Defines a feature that returns 'True' ('False' otherwise) if either:---   * at least 1 event during the baseline interval has any of the 'c1' concepts---   * there are at least 2 event that have 'c2' concepts which have at least+--   * at least 1 event during the baseline interval has any of the 'cpts1' concepts+--   * there are at least 2 event that have 'cpts2' concepts which have at least --     7 days between them during the baseline interval twoOutOneIn ::-       [Text]-    -> [Text]+       [Text] -- ^ cpts1+    -> [Text] -- ^ cpts2     -> Definition     ( Feature "calendarIndex" (Index Interval Day)     -> Feature "allEvents" (Events Day)     -> Feature name Bool )-twoOutOneIn cpts1 cpts2 = define+twoOutOneIn cpts1 cpts2 = +  define     (\index events ->         atleastNofX 1 cpts1  (getBaselineConcur index events)          || ( @@ -181,7 +183,6 @@ critFemale :: Definition  (   Feature "allEvents" (Events Day)   -> Feature "isFemale" Status)- critFemale =     define       (\events ->@@ -243,8 +244,6 @@         --  excludeIf ( maybe False (beforeIndex index) mDeadDay) -- different way to write logic       ) -- {-------------------------------------------------------------------------------   Covariate features -------------------------------------------------------------------------------}@@ -260,6 +259,14 @@  diabetes :: BoolFeatDef "diabetes" diabetes = twoOutOneIn ["is_diabetes_outpatient"] ["is_diabetes_inpatient"]++instance HasAttributes  "diabetes" Bool where+  getAttributes _ = MkAttributes +    "Has Diabetes"+    "Has Diabetes within baseline"+    "Has at least 1 event during the baseline interval has any of the 'cpts1' concepts \+    \ OR there are at least 2 event that have 'cpts2' concepts which have at least 7 days \+    \ between them during the baseline interval"  ckd :: BoolFeatDef "ckd" ckd =  twoOutOneIn ["is_ckd_outpatient"] ["is_ckd_inpatient"]
hasklepias.cabal view
@@ -1,6 +1,6 @@ cabal-version:  2.2 name:           hasklepias-version:        0.12.0+version:        0.13.0 description:    Please see the README on GitHub at <https://github.com/novisci/asclepias#readme> homepage:       https://github.com/novisci/asclepias/#readme bug-reports:    https://github.com/novisci/asclepias/issues@@ -31,6 +31,7 @@       EventData.Context.Domain.Demographics       FeatureCompose       FeatureCompose.Aeson+      FeatureCompose.Attributes       FeatureEvents       Hasklepias       Hasklepias.Aeson@@ -40,6 +41,13 @@       Hasklepias.Cohort       Hasklepias.Cohort.Criteria       Hasklepias.Cohort.Index+      Stype+      Stype.Numeric+      Stype.Numeric.Count+      Stype.Numeric.Continuous+      Stype.Numeric.Censored+      Stype.Categorical+      Stype.Categorical.Nominal   other-modules:       Paths_hasklepias   autogen-modules:@@ -59,8 +67,10 @@     , lens == 5.0.1     , lens-aeson == 1.1.1     , mtl == 2.2.2+    , nonempty-containers == 0.3.4.1     , process == 1.6.12.0     , safe >= 0.3+    , semiring-simple == 1.0.0.1     , tasty  == 1.4.1     , tasty-hunit == 0.10.0.3     , text == 1.2.4.1
src/FeatureCompose.hs view
@@ -33,6 +33,7 @@   , missingBecause   , makeFeature   , getFeatureData+  , getFData   , getData   , getDataN   , getNameN@@ -45,6 +46,7 @@   , Eval   , eval +  , HasAttributes(..) ) where  import safe Control.Applicative        ( Applicative(..)@@ -62,15 +64,17 @@ import safe GHC.Generics               ( Generic ) import safe GHC.Show                   ( Show(show) ) import safe GHC.TypeLits               ( KnownSymbol, Symbol, symbolVal )-+import safe FeatureCompose.Attributes {- |  Defines the reasons that a @'FeatureData'@ value may be missing. Can be used to indicate the reason that a @'Feature'@'s data was unable to be derived or does not need to be derived.  -}+{- tag::missingReason[] -} data MissingReason =     InsufficientData -- ^ Insufficient information available to derive data.   | Other Text -- ^ User provided reason for missingness+{- end::missingReason[] -}   deriving (Eq, Show, Generic)  {- | @@ -82,9 +86,11 @@ constructed with @'featureDataL'@ or its synonym @'missingBecause'@.  -}+{- tag::featureData[] -} newtype FeatureData d = MkFeatureData {      getFeatureData :: Either MissingReason d  -- ^ Unwrap FeatureData.   }+{- end::featureData[] -}   deriving (Eq, Show, Generic)  -- | Creates a non-missing 'FeatureData'. Since @'FeatureData'@ is an instance of@@ -165,8 +171,10 @@ Except when using @'pure'@ to lift data into a @Feature@, @Feature@s can only be derived from other @Feature@ via a @'Definition'@. -}+{- tag::feature[] -} newtype (KnownSymbol name) => Feature name d =   MkFeature { getFData :: FeatureData d }+{- end::feature[] -}   deriving (Eq)  -- | A utility for constructing a @'Feature'@ from @'FeatureData'@.@@ -370,3 +378,11 @@       case liftA3 f x y z of           MkFeatureData (Left l)  -> MkFeature $ MkFeatureData (Left l)           MkFeatureData (Right r) -> r++{- | Initializes @Feature@ @Attributes@ to empty strings -}++class HasAttributes n a where+  getAttributes :: Feature n a -> Attributes++-- instance HasAttributes name d where+--   getAttributes x = MkAttributes "" "" ""
src/FeatureCompose/Aeson.hs view
@@ -9,6 +9,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}  module FeatureCompose.Aeson( ) where@@ -19,7 +20,9 @@                                     , MissingReason                                     , FeatureData                                     , getFeatureData-                                    , getData )+                                    , getFData+                                    , HasAttributes(..) )+import FeatureCompose.Attributes import Data.Aeson                   ( object, KeyValue((.=)), ToJSON(toJSON) ) import Data.Proxy                   ( Proxy(Proxy) ) @@ -28,13 +31,19 @@  instance ToJSON MissingReason -instance (ToJSON d)=> ToJSON (FeatureData d) where+instance (ToJSON d) => ToJSON (FeatureData d) where     toJSON  x = case getFeatureData x of       (Left l)  -> toJSON l       (Right r) -> toJSON r -instance (KnownSymbol n, ToJSON d) => ToJSON (Feature n d) where-    toJSON x = object [ --"name"   .= getName x-                         "name"  .= show (symbolVal (Proxy @n))-                      --  , "attrs" .= toJSON (getAttr x)-                       , "data"  .= toJSON (getData x) ]+instance ToJSON Attributes where++-- instance (KnownSymbol n, ToJSON d) => ToJSON (Feature n d) where+--     toJSON x = object [  "name"  .= show (symbolVal (Proxy @n))+--                       --  , "attrs" .= toJSON (getAttributes x)+--                        , "data"  .= toJSON (getFData x) ]++instance (KnownSymbol n, ToJSON d, HasAttributes n d) => ToJSON (Feature n d) where+    toJSON x = object [  "name"  .= show (symbolVal (Proxy @n))+                       , "attrs" .= toJSON (getAttributes x)+                       , "data"  .= toJSON (getFData x) ]
+ src/FeatureCompose/Attributes.hs view
@@ -0,0 +1,39 @@+{-|+Module      : Functions for defining attributes Feature data+Description : Defines attributes instances for Features.+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com+-}+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE Safe #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DeriveGeneric #-}++module FeatureCompose.Attributes(+    Attributes(..)+  -- , HasAttributes(..)+) where++import safe Data.Eq         ( Eq )+import safe Data.Text       ( Text )+import safe GHC.Show        ( Show )+import safe GHC.Generics    ( Generic )++{- |+A data type for holding attritbutes of Features. This type and the @HasAttributes@+are likely to change in future versions.+-}+data Attributes = MkAttributes { +    getShortLabel :: Text+  , getLongLabel :: Text+  , getDerivation :: Text+  } deriving (Eq, Show, Generic)++-- class HasAttributes a where+--   getAttributes :: a -> Attributes++
src/Hasklepias.hs view
@@ -21,6 +21,8 @@     , module EventData.Arbitrary          , module FeatureCompose+    -- ** Adding Attributes to Features+    , module FeatureCompose.Attributes     -- ** Writing Features to JSON     , module FeatureCompose.Aeson @@ -41,6 +43,9 @@     -- ** Creating an executable cohort application     , module Hasklepias.MakeApp +    -- * Statistical Types+    , module Stype+     -- * Rexported Functions and modules     , module Hasklepias.Reexports     , module Hasklepias.ReexportsUnsafe@@ -58,6 +63,7 @@  import FeatureCompose import FeatureCompose.Aeson+import FeatureCompose.Attributes import FeatureEvents import Hasklepias.Reexports import Hasklepias.ReexportsUnsafe@@ -65,3 +71,5 @@ import Hasklepias.Cohort.Criteria import Hasklepias.Aeson import Hasklepias.MakeApp++import Stype
+ src/Stype.hs view
@@ -0,0 +1,19 @@+{-|+Module      : Stype +Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}++{-# OPTIONS_HADDOCK hide #-}+-- {-# LANGUAGE Safe #-}++module Stype (+    module Stype.Numeric+  , module Stype.Categorical+) where ++import Stype.Numeric+import Stype.Categorical
+ src/Stype/Categorical.hs view
@@ -0,0 +1,14 @@+{-|+Module      : Stype categorical+Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}+-- {-# LANGUAGE Safe #-}+module Stype.Categorical (+  module Stype.Categorical.Nominal +) where++import Stype.Categorical.Nominal
+ src/Stype/Categorical/Nominal.hs view
@@ -0,0 +1,17 @@+{-|+Module      : Stype categorical+Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}+-- {-# LANGUAGE Safe #-}+module Stype.Categorical.Nominal (+  Nomimal(..)+) where++-- import qualified Data.Set.NonEmpty as NES++{- | a placeholder for a future nominal type -}+newtype Nomimal a = Nomimal a deriving ( Eq, Show, Ord )
+ src/Stype/Numeric.hs view
@@ -0,0 +1,26 @@+{-|+Module      : Stype numeric types+Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}++{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Safe #-}++module Stype.Numeric (+    module Stype.Numeric.Count+  , module Stype.Numeric.Continuous+  , module Stype.Numeric.Censored+) where++import safe Stype.Numeric.Count+import safe Stype.Numeric.Continuous+import safe Stype.Numeric.Censored++instance Censorable Double where+instance (Ord a) => Censorable (EventTime a) where
+ src/Stype/Numeric/Censored.hs view
@@ -0,0 +1,43 @@+{-|+Module      : Stype censored types and typeclasses+Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}++{-# LANGUAGE Safe #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}++module Stype.Numeric.Censored (+    Censorable(..)+  , MaybeCensored(..)+) where++import safe Data.Text                    (Text)++data MaybeCensored a where+  IntervalCensored :: a -> a -> MaybeCensored a+  RightCensored :: a -> MaybeCensored a+  LeftCensored :: a -> MaybeCensored a+  Uncensored :: a -> MaybeCensored a+  deriving( Eq, Show, Ord )++class (Ord a) => Censorable a where++  parseIntervalCensor :: a -> a -> Either Text (MaybeCensored a)+  parseIntervalCensor x y +    | x < y = Right $ IntervalCensored x y+    | otherwise = Left "y >= x"++  rightCensor ::  a -> a -> MaybeCensored a+  rightCensor c t+    | c < t     = RightCensored c +    | otherwise = Uncensored t++  leftCensor ::  a -> a -> MaybeCensored a+  leftCensor c t+    | c >= t    = LeftCensored c+    | otherwise = Uncensored t
+ src/Stype/Numeric/Continuous.hs view
@@ -0,0 +1,53 @@+{-|+Module      : Stype continuous types+Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}++{-# LANGUAGE Safe #-}+module Stype.Numeric.Continuous (+    Continuous(..)+  , NonnegContinuous(..)+  , EventTime(..)+  , mkEventTime+) where++import safe Control.Applicative           ( Applicative(liftA2) )+++data Continuous a = NegContInf | Cont a | ContInf+  deriving (Eq, Show, Ord)++instance Functor Continuous where+  fmap f (Cont x) = Cont (f x)+  fmap f NegContInf = NegContInf+  fmap f ContInf = ContInf++instance Applicative Continuous where+  pure x = Cont x+  (<*>) (Cont f) (Cont x) =  Cont (f x)+  (<*>) NegContInf (Cont x) =  NegContInf+  (<*>) ContInf (Cont x) =  ContInf+  (<*>) _ NegContInf =  NegContInf+  (<*>) _ ContInf =  ContInf++instance Num a => Num (Continuous a) where+  (+) = liftA2 (+)+  (*) = liftA2 (*) +  abs = fmap abs+  fromInteger x = Cont $ fromInteger x+  signum = fmap signum+  negate = fmap negate++data NonnegContinuous a = NonNeg a | NonNegInf+  deriving (Eq, Show, Ord)++newtype EventTime a = EventTime { getEventTime :: NonnegContinuous a }+  deriving (Eq, Show, Ord)++mkEventTime :: Maybe a -> EventTime a+mkEventTime (Just x) = EventTime $ NonNeg x+mkEventTime Nothing  = EventTime NonNegInf
+ src/Stype/Numeric/Count.hs view
@@ -0,0 +1,49 @@+{-|+Module      : Stype count+Description : Statistical types+Copyright   : (c) NoviSci, Inc 2020+License     : BSD3+Maintainer  : bsaul@novisci.com++-}++{-# LANGUAGE Safe #-}++module Stype.Numeric.Count (+  Count(..)+) where++import safe GHC.Num                      ( Natural )+import safe Data.Semiring                ( Semiring(..) )++newtype Count = Count Natural+  deriving (Eq, Show, Ord)++instance Num Count where+  (+) (Count x) (Count y) = Count (x + y)+  (*) (Count x) (Count y) = Count (x * y) +  fromInteger x = Count $ fromInteger x+  abs = id -- useless+  signum x = Count 1 -- useless+  negate = id -- useless++instance Semigroup Natural where+  (<>) x y = x + y++instance Monoid Natural where+  mempty = 0++instance Semiring Natural where+  one = 1+  (<.>) = (*)++instance Semigroup Count where+  (<>) (Count x) (Count y) = Count (x <> y)++instance Monoid Count where+  mempty = Count 0++instance Semiring Count where+  one = Count 1+  (<.>) = (*)+
test/FeatureCompose/AesonSpec.hs view
@@ -1,5 +1,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} module FeatureCompose.AesonSpec (spec) where  import IntervalAlgebra@@ -8,6 +11,7 @@ import FeatureEvents import FeatureCompose import FeatureCompose.Aeson+import FeatureCompose.Attributes import Data.Aeson (encode) import Data.Maybe import Data.Time as DT@@ -27,10 +31,23 @@         Nothing -> featureDataL (Other "No Enrollment")         Just x  -> pure (getInterval x) +dummy ::  Feature "dummy" Bool+dummy = pure True +instance HasAttributes "dummy" Bool where+    getAttributes x = MkAttributes "some Label" "longer label..." "a description"++ spec :: Spec spec = do     it "an Int event is parsed correctly" $        encode (index ex1)  `shouldBe` "{\"end\":10,\"begin\":0}"++    it "dummy encodes correctly" $+        encode dummy `shouldBe` +        "{\"data\":true,\"name\":\"\\\"dummy\\\"\",\+        \\"attrs\":{\"getDerivation\":\"a description\",\+        \\"getLongLabel\":\"longer label...\",\+        \\"getShortLabel\":\"some Label\"}}"  
test/FeatureComposeSpec.hs view
@@ -66,6 +66,32 @@ f3F = define f3  +featInts :: [Int] -> Feature "someInts" [Int]+featInts = pure++feat1 :: Definition (Feature "someInts" [Int] -> Feature "hasMoreThan3" Bool)+feat1 = defineA+  (\ints -> if null ints then makeFeature (missingBecause $ Other "no data")+           else makeFeature $ featureDataR (length ints > 3))++feat2 :: Definition (+      Feature "hasMoreThan3" Bool+  -> Feature "someInts" [Int]+  -> Feature "sum" Int)+feat2 = define (\b ints -> if b then sum ints else 0)++ex0 = featInts []+ex0a = eval feat1 ex0 -- MkFeature (MkFeatureData (Left (Other "no data")))+ex0b = eval feat2 (ex0a, ex0) -- MkFeature (MkFeatureData (Left (Other "no data")))++ex1 = featInts [3, 8]+ex1a = eval feat1 ex1 -- MkFeature (MkFeatureData (Right False))+ex1b = eval feat2 (ex1a, ex1) -- MkFeature (MkFeatureData (Right 0))++ex2 = featInts [1..4]+ex2a = eval feat1 ex2 -- MkFeature (MkFeatureData (Right True))+ex2b = eval feat2 (ex2a, ex2) -- MkFeature (MkFeatureData (Right 10))+ spec :: Spec spec = do @@ -131,3 +157,12 @@         eval f3F (pure True, eval f2F (pure False)) `shouldBe` makeFeature (missingBecause (Other "test"))       it "eval of f3F  returns correct value" $         eval f3F (pure True, eval f2F (pure True)) `shouldBe` pure "this"++  describe "checking ex0-2" $+    do+      it "ex0b" $+        ex0b `shouldBe` makeFeature (missingBecause (Other "no data"))+      it "ex1b" $+        ex1b `shouldBe` makeFeature (featureDataR 0)+      it "ex2b" $+        ex2b `shouldBe` makeFeature (featureDataR 10)