too-many-cells-2.1.0.1: src/TooManyCells/Matrix/Types.hs
{- TooManyCells.Matrix.Types
Gregory W. Schwartz
Collects the types used in the program concerning the matrix.
-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}
module TooManyCells.Matrix.Types where
-- Remote
import BirchBeer.Types
import Control.DeepSeq (NFData (..), force)
import Control.Monad (join)
import Control.Parallel (par)
import Control.Parallel.Strategies (withStrategy, rdeepseq, Strategy (..))
import Data.Either (isRight)
import Data.Maybe (fromMaybe)
import Data.Monoid (Monoid (..), mempty)
import Data.Colour.Palette.BrewerSet (Kolor)
import Data.Colour.SRGB (Colour (..), RGB (..), toSRGB)
import Data.Function (on)
import Data.List (sortBy)
import Data.Map.Strict (Map)
import Data.Text (Text)
import Data.Vector (Vector (..))
import GHC.Generics (Generic)
import Language.R as R
import Language.R.QQ (r)
import Math.Clustering.Hierarchical.Spectral.Sparse (ShowB)
import Math.Clustering.Hierarchical.Spectral.Types (ClusteringTree, ClusteringVertex)
import Math.Modularity.Types (Q (..))
import Safe (headMay)
import TextShow (TextShow (..))
import qualified Control.DeepSeq as DS
import qualified Control.Lens as L
import qualified Data.Aeson as A
import qualified Data.Attoparsec.Text as Atto
import qualified Data.ByteString as B
import qualified Data.Clustering.Hierarchical as HC
import qualified Data.Graph.Inductive as G
import qualified Data.HashMap.Strict as HMap
import qualified Data.HashSet as HSet
import qualified Data.IntMap as IMap
import qualified Data.IntervalMap.Interval as IntervalMap
import qualified Data.IntervalMap.Strict as IntervalMap
import qualified Data.Map.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import qualified Data.Sparse.Common as S
import qualified Data.Sparse.SpMatrix as S
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import qualified Numeric.LinearAlgebra as H
import qualified TextShow as TS
-- Local
-- Basic
newtype Cell = Cell
{ unCell :: Text
} deriving (Eq,Ord,Read,Show,Generic) deriving anyclass (A.ToJSON, A.FromJSON)
newtype Cols = Cols { unCols :: [Double] }
newtype FeatureColumn = FeatureColumn { unFeatureColumn :: Int }
newtype CustomLabel = CustomLabel { unCustomLabel :: Text}
deriving (Eq,Ord,Read,Show)
newtype BinWidth = BinWidth { unBinWidth :: Int}
newtype NoBinarizeFlag = NoBinarizeFlag { unNoBinarizeFlag :: Bool }
newtype BinarizeFlag = BinarizeFlag { unBinarizeFlag :: Bool }
newtype TransposeFlag = TransposeFlag { unTransposeFlag :: Bool }
newtype DropDimensionFlag = DropDimensionFlag { unDropDimensionFlag :: Bool }
newtype BinIdx = BinIdx { unBinIdx :: Int} deriving (Eq, Ord, Read, Show, Generic) deriving newtype (Num)
newtype RangeIdx = RangeIdx { unRangeIdx :: Int} deriving (Eq, Ord, Read, Show, Generic) deriving newtype (Num)
newtype CellWhitelist = CellWhitelist
{ unCellWhitelist :: HSet.HashSet Text
} deriving (Eq,Ord,Show)
newtype ExcludeFragments = ExcludeFragments { unExcludeFragments :: T.Text }
newtype BlacklistRegions = BlacklistRegions { unBlacklistRegions :: T.Text }
newtype PCADim = PCADim
{ unPCADim :: Int
} deriving (Eq,Ord,Read,Show)
newtype LSADim = LSADim
{ unLSADim :: Int
} deriving (Eq,Ord,Read,Show)
newtype SVDDim = SVDDim
{ unSVDDim :: Int
} deriving (Eq,Ord,Read,Show)
newtype ShiftPositiveFlag = ShiftPositiveFlag
{ unShiftPositiveFlag :: Bool
} deriving (Read,Show)
newtype FilterThresholds = FilterThresholds
{ unFilterThresholds :: (Double, Double)
} deriving (Read,Show)
newtype MatrixTranspose = MatrixTranspose
{ unMatrixTranspose :: Bool
} deriving (Read,Show)
newtype ChrRegion = ChrRegion
{ unChrRegion :: (T.Text, IntervalMap.Interval Int)
} deriving (Read,Show,Eq,Ord)
newtype ChrRegionBin = ChrRegionBin
{ unChrRegionBin :: ChrRegion
} deriving (Read,Show,Eq,Ord)
newtype ChrRegionMat = ChrRegionMat
{ unChrRegionMat
:: Map.Map T.Text (IntervalMap.IntervalMap Int (S.SpVector Double))
} deriving (Show)
newtype CustomRegions = CustomRegions
{ unCustomRegions :: [ChrRegion]
} deriving (Read,Show)
newtype X = X
{ unX :: Double
} deriving (Eq,Ord,Read,Show,Generic) deriving newtype (Num) deriving anyclass (A.ToJSON,A.FromJSON)
newtype Y = Y
{ unY :: Double
} deriving (Eq,Ord,Read,Show,Generic) deriving newtype (Num) deriving anyclass (A.ToJSON,A.FromJSON)
newtype ProjectionMap = ProjectionMap
{ unProjectionMap :: Map.Map Cell (X, Y)
}
newtype RMat s = RMat { unRMat :: R.SomeSEXP s }
newtype RMatObsRow s = RMatObsRow { unRMatObsRow :: R.SomeSEXP s }
newtype RMatFeatRow s = RMatFeatRow { unRMatFeatRow :: R.SomeSEXP s }
newtype RMatScaled s = RMatScaled { unRMatScaled :: R.SomeSEXP s }
newtype Row = Row
{ unRow :: Int
} deriving (Eq,Ord,Read,Show,Generic) deriving anyclass (A.ToJSON,A.FromJSON)
newtype Rows = Rows { unRows :: [Double] }
newtype Vals = Vals { unVals :: [Double] }
newtype LabelMat = LabelMat { unLabelMat :: (SingleCells, Maybe LabelMap) }
newtype AggSingleCells = AggSingleCells { unAggSingleCells :: SingleCells }
deriving (Show)
newtype AggReferenceMat = AggReferenceMat { unAggReferenceMat :: AggSingleCells }
deriving (Show)
instance Semigroup LabelMat where
(<>) (LabelMat (!m1, !l1)) (LabelMat (!m2, !l2)) =
LabelMat (mappend m1 m2, mappend l1 l2)
instance Monoid LabelMat where
mempty = LabelMat (mempty, mempty)
newtype MatObsRow = MatObsRow
{ unMatObsRow :: S.SpMatrix Double
} deriving (Show)
instance Semigroup MatObsRow where
(<>) (MatObsRow x) (MatObsRow y) = MatObsRow $ S.vertStackSM x y
instance Monoid MatObsRow where
mempty = MatObsRow $ S.zeroSM 0 0
-- Advanced
-- | Parse a chromosome region feature into an interval map.
parseChrRegion :: T.Text -> Either String ChrRegion
parseChrRegion x = either (\a -> Left $ a <> " (did you follow the format chrN:LOWERBOUND-UPPERBOUND ?): " <> T.unpack x) Right
. Atto.parseOnly parserChrRegion
. T.filter (/= ',')
$ x
parserChrRegion :: Atto.Parser ChrRegion
parserChrRegion = do
Atto.string "chr"
chr <- Atto.takeWhile1 (/= ':')
Atto.char ':'
start <- Atto.decimal
Atto.char '-'
end <- Atto.decimal
return . ChrRegion $ (chr, IntervalMap.ClosedInterval start end)
-- | Check if a matrix is a ChrRegion matrix based on the first feature.
isChrRegionMat :: (MatrixLike m) => m -> Bool
isChrRegionMat =
maybe False (isRight . parseChrRegion) . flip (V.!?) 0 . getColNames
instance TextShow ChrRegion where
showb (ChrRegion (chr, i)) = "chr"
<> TS.fromText chr
<> ":"
<> showb (IntervalMap.lowerBound i)
<> "-"
<> showb (IntervalMap.upperBound i)
instance Monoid ChrRegionMat where
mempty = ChrRegionMat Map.empty
instance Semigroup ChrRegionMat where
(<>) (ChrRegionMat x) (ChrRegionMat y) =
ChrRegionMat
. Map.unionWith
( \x y
-> IntervalMap.flattenWith (S.^+^)
. IntervalMap.unionWith (S.^+^) x
$ y
)
x
$ y
data SingleCells = SingleCells { _matrix :: MatObsRow
, _rowNames :: Vector Cell
, _colNames :: Vector Feature
}
deriving (Show)
L.makeLenses ''SingleCells
instance Semigroup SingleCells where
(<>) x y
| (null . L.view colNames $ x)
|| (null . L.view colNames $ y)
|| (L.view colNames x == L.view colNames y) = -- If either is null or they share features, then do a normal append
SingleCells { _matrix = mappend (L.view matrix x) (L.view matrix y)
, _rowNames = (V.++) (L.view rowNames x) (L.view rowNames y)
, _colNames = _colNames x
}
| isChrRegionMat x =
either (\a -> error $ "Invalid chromosome region notation for feature, must be in format `chrN:START-END`: " <> a) id
$ mergeChrFeaturesSingleCells x y
| otherwise = mergeFeaturesSingleCells x y
-- | Join two SingleCells with different features.
mergeFeaturesSingleCells :: SingleCells -> SingleCells -> SingleCells
mergeFeaturesSingleCells sc1 sc2 =
SingleCells { _matrix = MatObsRow
. force
$ (newMat id sc1)
S.^+^ (newMat (+ sc1NRows) sc2)
, _rowNames = (V.++) (L.view rowNames sc1) (L.view rowNames sc2)
, _colNames = V.fromList newCols
}
where
newMat rowF sc = S.modifyKeysSM
rowF
(lookupCol (fmap unFeature $ L.view colNames sc))
. S.SM (newNRows, HMap.size newColMap)
. S.immSM
$ getMatrix sc
newNRows = (V.length . L.view rowNames $ sc1)
+ (V.length . L.view rowNames $ sc2)
sc1NRows = S.nrows . getMatrix $ sc1
lookupCol :: V.Vector T.Text -> Int -> Int
lookupCol olds x = fromMaybe (error $ "mergeFeaturesSingleCells: No new index when joining cols: " <> show x)
. (>>=) (olds V.!? x)
$ flip HMap.lookup newColMap
newColMap :: HMap.HashMap T.Text Int
newColMap = HMap.fromList . flip zip [0..] . fmap unFeature $ newCols
newCols = Set.toAscList . Set.union (colToSet sc1) $ colToSet sc2
colToSet = Set.fromList . V.toList . L.view colNames
-- | Join two SingleCells with different chromosome region features. Merge the
-- features such that overlapping regions become one region.
mergeChrFeaturesSingleCells :: SingleCells -> SingleCells -> Either String SingleCells
mergeChrFeaturesSingleCells sc1 sc2 = do
let getScMap sc = ChrRegionMat
. fmap (IntervalMap.flattenWith (S.^+^))
-- . Map.unionsWith (\x y -> force x `par` force y `par` IntervalMap.unionWith (S.^+^) x y)
. Map.unionsWith (IntervalMap.unionWith (S.^+^))
. zipWith (\r (ChrRegion (c, i))
-> Map.singleton c $ IntervalMap.singleton i r
)
(S.toRowsL . S.transpose $ getMatrix sc)
<$> mapM parseChrRegion (V.toList $ getColNames sc)
sc1NRows = (V.length . L.view rowNames $ sc1)
newNRows = sc1NRows + (V.length . L.view rowNames $ sc2)
updateRows rowF mat = MatObsRow
. S.modifyKeysSM
rowF
id
. S.SM (newNRows, S.ncols $ unMatObsRow mat)
. S.immSM
. unMatObsRow
$ mat
newSc1 = L.over matrix (updateRows id) sc1
newSc2 = L.over matrix (updateRows (+ sc1NRows)) sc2
sc1Map <- getScMap newSc1
sc2Map <- getScMap newSc2
let newMap = sc1Map <> sc2Map
newMat = S.fromColsL
$ Map.elems (unChrRegionMat newMap) >>= IntervalMap.elems
newCols =
Map.toAscList (unChrRegionMat newMap)
>>= ( \(!k, !v) -> zipWith
(\ x y
-> Feature
. showt
. ChrRegion
$ (x, y)
)
(repeat k)
$ IntervalMap.keys v
)
return $
SingleCells { _matrix = MatObsRow . force $ newMat
, _rowNames = (V.++)
(L.view rowNames sc1)
(L.view rowNames sc2)
, _colNames = V.fromList newCols
}
instance Monoid SingleCells where
mempty = SingleCells { _matrix = mempty
, _rowNames = V.empty
, _colNames = V.empty
}
instance MatrixLike SingleCells where
getMatrix = unMatObsRow . _matrix
getRowNames = fmap unCell . _rowNames
getColNames = fmap unFeature . _colNames
data CellInfo = CellInfo
{ _barcode :: Cell
, _cellRow :: Row
} deriving (Eq,Ord,Read,Show,Generic) deriving anyclass (A.ToJSON,A.FromJSON)
L.makeLenses ''CellInfo
-- | A range with a label, most ranges can vary with each other.
data Range = Range { rangeIdx :: Maybe RangeIdx
, _rangeLabel :: Text
, _rangeLowerBound :: Int
, _rangeUpperBound :: Int
}
deriving (Eq, Ord, Read, Show)
L.makeFields ''Range
instance TextShow Range where
showb (Range _ l lb ub) = mconcat [TS.fromText l, ":", showb lb, "-", showb ub]
-- | A bin with a label, bins are standard across all observations.
data Bin = Bin { binIdx :: Maybe BinIdx, _binLabel :: Text, _binLowerBound :: Int, _binUpperBound :: Int }
deriving (Eq, Ord, Read, Show)
L.makeFields ''Bin
instance TextShow Bin where
showb (Bin _ l lb ub)= mconcat [TS.fromText l, ":", showb lb, "-", showb ub]
-- | Map of ranges to their bins.
newtype RangeBinMap = RangeBinMap
{ unRangeBinMap :: IMap.IntMap BinIdx
}
data NormType = TfIdfNorm
| UQNorm
| MedNorm
| TotalMedNorm
| TotalNorm
| LogCPMNorm Double
| QuantileNorm
| NoneNorm
deriving (Read, Show, Eq)
-- | Map of bins to their idxs in unsorted and un-uniqued list.
newtype BinIdxMap = BinIdxMap
{ unBinIdxMap :: Map.Map Bin BinIdx
}
deriving stock instance Generic Feature
instance NFData Feature where rnf x = seq x ()
instance (NFData a) => NFData (S.SpMatrix a) where
rnf all@(S.SM (!x, !y) !m) = seq all ()
instance (NFData a) => NFData (S.SpVector a) where
rnf all@(S.SV !x !m) = seq all ()