hasklepias-0.17.0: src/Cohort/AssessmentIntervals.hs
{-|
Module : Cohort Index
Description : Defines the Index and related types and functions
Copyright : (c) NoviSci, Inc 2020
License : BSD3
Maintainer : bsaul@novisci.com
-}
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
-- {-# LANGUAGE Safe #-}
module Cohort.AssessmentIntervals(
{- |
The assessment intervals provided are:
* 'Baseline': an interval which either 'IntervalAlgebra.meets' or
'IntervalAlgebra.precedes' index. Covariates are typically assessed during
baseline intervals. A cohort's specification may include multiple baseline
intervals, as different features may require different baseline intervals.
For example, one feature may use a baseline interval of 365 days prior to
index, while another uses a baseline interval of 90 days before index up
to 30 days before index.
* `Followup`: an interval which is 'IntervalAlgebra.startedBy',
'IntervalAlgebra.metBy', or 'IntervalAlgebra.after' an 'Index'. Outcomes
are typically assessed during followup intervals. Similar to 'Baseline',
a cohort's specification may include multiple followup intervals,
as different features may require different followup intervals.
In future versions, one subject may have multiple values for an 'Index'
corresponding to unique 'Cohort.Core.ObsUnit'. That is, there is a 1-to-1 map between
index values and observational units, but there may be a 1-to-many map from
subjects to indices.
While users are protected from forming invalid assessment intervals, they still
need to carefully consider how to filter events based on the assessment interval.
Consider the following data:
@
_ <- Index (15, 16)
---------- <- Baseline (5, 15)
--- <- A (1, 4)
--- <- B (2, 5)
--- <- C (4, 7)
--- <- D (5, 8)
--- <- E (8, 11)
--- <- F (12, 15)
--- <- G (14, 17)
___ <- H (17, 20)
|----|----|----|----|
0 10 20
@
We have index, baseline, and 8 events (A-H). If Baseline is our assessment interval,
then the events concuring (i.e. not disjoint) with Baseline are C-G. While C-F
probably make sense to use in deriving some covariate, what about G? The event G
begins during baseline but ends after index. If you want, for example, to know
how many events started during baseline, then you’d want to include G in your
filter (using 'IntervalAlgebra.concur'). But if you wanted to know the durations
of events enclosed by baseline, then you wouldn’t want to filter using concur
and instead perhaps use 'IntervalAlgebra.enclosedBy'.
-}
BaselineInterval
, Baseline(..)
, FollowupInterval
, Followup(..)
, AssessmentInterval
, makeBaselineFromIndex
, makeBaselineBeforeIndex
, makeFollowupFromIndex
, makeFollowupMeetingIndex
, makeFollowupAfterIndex
) where
import GHC.Generics ( Generic )
import GHC.Num ( Num((+)) )
import GHC.Show ( Show )
import Data.Eq ( Eq )
import Data.Functor ( Functor(fmap) )
import Data.Function ( ($) )
import Data.Ord ( Ord, (<=) )
import IntervalAlgebra ( Interval
, Intervallic(..)
, IntervalSizeable(..)
, enderval
, begin
, end
, beginerval
, duration )
import Cohort.Index ( Index )
{-| A type to contain baseline intervals. See the 'Baseline' typeclass for methods
to create values of this type.
-}
newtype BaselineInterval a = MkBaselineInterval (Interval a)
deriving (Eq, Show, Generic)
instance Functor BaselineInterval where
fmap f (MkBaselineInterval x) = MkBaselineInterval (fmap f x)
instance (Ord a) => Intervallic BaselineInterval a where
getInterval (MkBaselineInterval x) = getInterval x
setInterval (MkBaselineInterval x) y = MkBaselineInterval (setInterval x y)
{-|
Provides functions for creating a 'BaselineInterval' from an 'Index'. The
'baseline' function should satify:
[Meets]
@'IntervalAlgebra.relate' ('baseline' d i) i = 'IntervalAlgebra.Meets'@
The 'baselineBefore' function should satisfy:
[Before]
@'IntervalAlgebra.relate' ('baselineBefore' s d i) i = 'IntervalAlgebra.Before'@
>>> import Cohort.Index
>>> import IntervalAlgebra
>>> x = makeIndex (beginerval 1 10)
>>> b =baseline 10 x
>>> b
>>> relate b x
MkBaselineInterval (0, 10)
Meets
>>> import Cohort.Index
>>> import IntervalAlgebra
>>> x = makeIndex (beginerval 1 10)
>>> b = baselineBefore 2 4 x
>>> b
>>> relate b x
MkBaselineInterval (4, 8)
Before
-}
class Intervallic i a => Baseline i a where
-- | Creates a 'BaselineInterval' of the given duration that 'IntervalAlgebra.Meets'
-- the 'Index' interval.
baseline ::
( IntervalSizeable a b) =>
b -- ^ duration of baseline
-> Index i a -- ^ the 'Index' event
-> BaselineInterval a
baseline dur index = MkBaselineInterval (enderval dur (begin index))
-- | Creates a 'BaselineInterval' of the given duration that 'IntervalAlgebra.precedes'
-- the 'Index' interval.
baselineBefore ::
( IntervalSizeable a b) =>
b -- ^ duration to shift back
-> b -- ^ duration of baseline
-> Index i a -- ^ the 'Index' event
-> BaselineInterval a
baselineBefore shiftBy dur index =
MkBaselineInterval $ enderval dur (begin (enderval shiftBy (begin index)))
instance (Ord a) => Baseline Interval a
{-| A type to contain followup intervals. See the 'Followup' typeclass for methods
to create values of this type.
-}
newtype FollowupInterval a = MkFollowupInterval (Interval a)
deriving (Eq, Show, Generic)
instance Functor FollowupInterval where
fmap f (MkFollowupInterval x) = MkFollowupInterval (fmap f x)
instance (Ord a) => Intervallic FollowupInterval a where
getInterval (MkFollowupInterval x) = getInterval x
setInterval (MkFollowupInterval x) y = MkFollowupInterval (setInterval x y)
{-|
Provides functions for creating a 'FollowupInterval' from an 'Index'. The
'followup' function should satify:
[StartedBy]
@'IntervalAlgebra.relate' ('followup' d i) i = 'IntervalAlgebra.StartedBy'@
The 'followupMetBy' function should satisfy:
[MetBy]
@'IntervalAlgebra.relate' ('followupMetBy' d i) i = 'IntervalAlgebra.MetBy'@
The 'followupAfter' function should satisfy:
[After]
@'IntervalAlgebra.relate' ('followupAfter' s d i) i = 'IntervalAlgebra.After'@
>>> import Cohort.Index
>>> import IntervalAlgebra
>>> x = makeIndex (beginerval 1 10)
>>> f = followup 10 x
>>> f
>>> relate f x
MkFollowupInterval (10, 20)
StartedBy
Note the consequence of providing a duration less than or equal to the duration
of the index: a 'IntervalAlgebra.moment' is added to the duration, so that the
end of the 'FollowupInterval' is greater than the end of the 'Index'.
>>> import Cohort.Index
>>> import IntervalAlgebra
>>> x = makeIndex (beginerval 1 10)
>>> f = followup 1 x
>>> f
>>> relate f x
MkFollowupInterval (10, 12)
StartedBy
>>> import Cohort.Index
>>> import IntervalAlgebra
>>> x = makeIndex (beginerval 1 10)
>>> f = followupMetBy 9 x
>>> f
>>> relate f x
MkFollowupInterval (11, 20)
MetBy
>>> import Cohort.Index
>>> import IntervalAlgebra
>>> x = makeIndex (beginerval 1 10)
>>> f = followupAfter 1 9 x
>>> f
>>> relate f x
MkFollowupInterval (12, 21)
After
-}
class Intervallic i a => Followup i a where
followup ::
( IntervalSizeable a b
, Intervallic i a) =>
b -- ^ duration of followup
-> Index i a -- ^ the 'Index' event
-> FollowupInterval a
followup dur index = MkFollowupInterval (beginerval d2 (begin index))
where d2 = if dur <= duration index
then duration index + moment' index
else dur
followupMetBy ::
( IntervalSizeable a b
, Intervallic i a) =>
b -- ^ duration of followup
-> Index i a -- ^ the 'Index' event
-> FollowupInterval a
followupMetBy dur index = MkFollowupInterval (beginerval dur (end index))
followupAfter ::
( IntervalSizeable a b
, Intervallic i a) =>
b -- ^ duration add between the end of index and begin of followup
-> b -- ^ duration of followup
-> Index i a -- ^ the 'Index' event
-> FollowupInterval a
followupAfter shiftBy dur index =
MkFollowupInterval $ beginerval dur (end (beginerval shiftBy (end index)))
instance (Ord a) => Followup Interval a
-- | A data type that contains variants of intervals during which assessment
-- may occur.
data AssessmentInterval a =
Bl (BaselineInterval a) -- ^ holds a 'BaselineInterval'
| Fl (FollowupInterval a) -- ^ holds a 'FollowupInterval'
deriving (Eq, Show, Generic)
instance (Ord a) => Intervallic AssessmentInterval a where
getInterval (Bl x) = getInterval x
getInterval (Fl x) = getInterval x
setInterval (Bl x) y = Bl (setInterval x y)
setInterval (Fl x) y = Fl (setInterval x y)
instance Functor AssessmentInterval where
fmap f (Bl x) = Bl (fmap f x)
fmap f (Fl x) = Fl (fmap f x)
-- | Creates an 'AssessmentInterval' using the 'baseline' function.
--
-- >>> import Cohort.Index
-- >>> x = makeIndex $ beginerval 1 10
-- >>> makeBaselineFromIndex 10 x
-- Bl (MkBaselineInterval (0, 10))
--
makeBaselineFromIndex ::
(Baseline i a, IntervalSizeable a b) =>
b
-> Index i a
-> AssessmentInterval a
makeBaselineFromIndex dur index = Bl ( baseline dur index )
-- | Creates an 'AssessmentInterval' using the 'baselineBefore' function.
--
-- >>> import Cohort.Index
-- >>> x = makeIndex $ beginerval 1 10
-- >>> makeBaselineBeforeIndex 2 10 x
-- Bl (MkBaselineInterval (-2, 8))
--
makeBaselineBeforeIndex ::
(Baseline i a, IntervalSizeable a b) =>
b
-> b
-> Index i a
-> AssessmentInterval a
makeBaselineBeforeIndex shiftBy dur index =
Bl ( baselineBefore shiftBy dur index )
-- | Creates an 'AssessmentInterval' using the 'followup' function.
--
-- >>> import Cohort.Index
-- >>> x = makeIndex $ beginerval 1 10
-- >>> makeFollowupFromIndex 10 x
-- Fl (MkFollowupInterval (10, 20))
--
makeFollowupFromIndex ::
(Followup i a, IntervalSizeable a b) =>
b
-> Index i a
-> AssessmentInterval a
makeFollowupFromIndex dur index = Fl ( followup dur index )
-- | Creates an 'AssessmentInterval' using the 'followupMetBy' function.
--
-- >>> import Cohort.Index
-- >>> x = makeIndex $ beginerval 1 10
-- >>> makeFollowupMeetingIndex 10 x
-- Fl (MkFollowupInterval (11, 21))
--
makeFollowupMeetingIndex ::
(Followup i a, IntervalSizeable a b) =>
b
-> Index i a
-> AssessmentInterval a
makeFollowupMeetingIndex dur index = Fl ( followupMetBy dur index )
-- | Creates an 'AssessmentInterval' using the 'followupAfter' function.
--
-- >>> import Cohort.Index
-- >>> x = makeIndex $ beginerval 1 10
-- >>> makeFollowupAfterIndex 10 10 x
-- Fl (MkFollowupInterval (21, 31))
--
makeFollowupAfterIndex ::
(Followup i a, IntervalSizeable a b) =>
b
-> b
-> Index i a
-> AssessmentInterval a
makeFollowupAfterIndex shiftBy dur index = Fl ( followupAfter shiftBy dur index )