packages feed

slynx-0.2.1: src/SLynx/Examine/Examine.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

{- |

Module      :  Analyze.Analyze
Description :  Parse sequence file formats and analyze them
Copyright   :  (c) Dominik Schrempf 2018
License     :  GPL-3.0-or-later

Maintainer  :  dominik.schrempf@gmail.com
Stability   :  unstable
Portability :  portable

Creation date: Fri Oct  5 08:41:05 2018.

-}

module SLynx.Examine.Examine
  ( examineCmd
  )
where

import           Control.Monad.Logger
import           Control.Monad.Trans.Reader     ( ask )
import qualified Data.ByteString.Lazy.Char8    as L
import qualified Data.Set                      as S
import           Text.Printf

import           SLynx.Examine.Options
import           SLynx.Tools

import qualified ELynx.Data.Alphabet.Alphabet  as A
import qualified ELynx.Data.Alphabet.Character as C
import qualified ELynx.Data.Sequence.Alignment as M
import qualified ELynx.Data.Sequence.Sequence  as Seq
import           ELynx.Tools

pRow :: String -> String -> L.ByteString
pRow name val = alignLeft 50 n <> alignRight 10 v
 where
  n = L.pack name
  v = L.pack val

examineAlignment :: Bool -> M.Alignment -> L.ByteString
examineAlignment perSiteFlag a =
  L.unlines
      [ L.pack
        "Sequences have equal length (multi sequence alignment, or single sequence)."
      , pRow "Total number of columns in alignment:" $ show (M.length a)
      , pRow "Number of columns without gaps:" $ show (M.length aNoGaps)
      , pRow "Number of columns with standard characters only:"
        $ show (M.length aOnlyStd)
      , L.empty
      , pRow "Total number of characters:" $ show nTot
      , pRow "Standard (i.e., not extended IUPAC) characters:"
        $ show (nTot - nIUPAC - nGaps - nUnknowns)
      , pRow "Extended IUPAC characters:" $ show nIUPAC
      , pRow "Gaps:" $ show nGaps
      , pRow "Unknowns:" $ show nUnknowns
      , L.empty
      , pRow "Percentage of standard characters:"
        $ printf "%2.2f" (100.0 - percentIUPAC - percentGaps - percentUnknowns)
      , pRow "Percentage of extended IUPAC characters:"
        $ printf "%2.2f" percentIUPAC
      , pRow "Percentage of gaps:" $ printf "%2.2f" percentGaps
      , pRow "Percentage of unknowns:" $ printf "%2.2f" percentUnknowns
      , L.empty
      , L.pack "Distribution of characters:"
              -- Holy crap.
      , L.pack
      $ concatMap ((: "     ") . C.toChar)
      $ S.toList
      $ A.std
      $ A.alphabetSpec
      $ M.alphabet a
      , L.pack $ unwords $ map (printf "%.3f") charFreqs
      , L.empty
      , L.pack "Mean effective number of states (measured using entropy):"
      , pRow "Across whole alignment:" $ printf "%.3f" kEffMean
      , pRow "Across columns without gaps:" $ printf "%.3f" kEffMeanNoGaps
      , pRow "Across columns without extended IUPAC characters:"
        $ printf "%.3f" kEffMeanOnlyStd
      , L.empty
      , L.pack "Mean effective number of states (measured using homoplasy):"
      , pRow "Across whole alignment:" $ printf "%.3f" kEffMeanHomo
      , pRow "Across columns without gaps:" $ printf "%.3f" kEffMeanNoGapsHomo
      , pRow "Across columns without extended IUPAC characters:"
        $ printf "%.3f" kEffMeanOnlyStdHomo
      ]
    <> perSiteBS
 where
  nTot             = M.length a * M.nSequences a
  nIUPAC           = M.countIUPACChars a
  nGaps            = M.countGaps a
  nUnknowns        = M.countUnknowns a
  percentIUPAC     = 100 * fromIntegral nIUPAC / fromIntegral nTot :: Double
  percentGaps      = 100 * fromIntegral nGaps / fromIntegral nTot :: Double
  percentUnknowns  = 100 * fromIntegral nUnknowns / fromIntegral nTot :: Double
  aNoGaps          = M.filterColsNoGaps a
  aOnlyStd         = M.filterColsOnlyStd aNoGaps
  charFreqsPerSite = M.toFrequencyData a
  charFreqs        = M.distribution charFreqsPerSite
  kEffs            = M.kEffEntropy charFreqsPerSite
  kEffsNoGaps      = M.kEffEntropy . M.toFrequencyData $ aNoGaps
  kEffsOnlyStd     = M.kEffEntropy . M.toFrequencyData $ aOnlyStd
  kEffMean         = sum kEffs / fromIntegral (length kEffs)
  kEffMeanNoGaps   = sum kEffsNoGaps / fromIntegral (length kEffsNoGaps)
  kEffMeanOnlyStd  = sum kEffsOnlyStd / fromIntegral (length kEffsOnlyStd)
  kEffsHomo        = M.kEffHomoplasy charFreqsPerSite
  kEffsNoGapsHomo  = M.kEffHomoplasy . M.toFrequencyData $ aNoGaps
  kEffsOnlyStdHomo = M.kEffHomoplasy . M.toFrequencyData $ aOnlyStd
  kEffMeanHomo     = sum kEffsHomo / fromIntegral (length kEffsHomo)
  kEffMeanNoGapsHomo =
    sum kEffsNoGapsHomo / fromIntegral (length kEffsNoGapsHomo)
  kEffMeanOnlyStdHomo =
    sum kEffsOnlyStdHomo / fromIntegral (length kEffsOnlyStdHomo)
  perSiteBS = if perSiteFlag
    then L.unlines
      [ L.pack "Effective number of used states per site:"
      , L.pack . show $ kEffs
      ]
    else L.empty

examine :: Bool -> [Seq.Sequence] -> L.ByteString
examine perSiteFlag ss =
  Seq.summarizeSequences ss <> case M.fromSequences ss of
    Left  _ -> L.empty
    Right a -> L.pack "\n" <> examineAlignment perSiteFlag a

-- | Examine sequences.
examineCmd :: ELynx ExamineArguments ()
examineCmd = do
  (ExamineArguments al inFile perSiteFlag) <- local <$> ask
  $(logInfo) "Command: Examine sequences."
  ss <- readSeqs al inFile
  let result = examine perSiteFlag ss
  out "result of examination" result ".out"