sequenceTools-1.4.0.3: src-executables/genoStats.hs
{-# LANGUAGE OverloadedStrings #-}
import SequenceFormats.FreqSum (readFreqSumFile, readFreqSumStdIn, FreqSumHeader(..),
FreqSumEntry(..))
import SequenceFormats.Eigenstrat (readEigenstrat, GenoEntry(..), GenoLine, EigenstratSnpEntry(..), EigenstratIndEntry(..))
import SequenceFormats.Utils (Chrom(..))
import SequenceTools.Utils (versionInfoOpt, versionInfoText)
import Control.Applicative ((<|>))
import Control.Foldl (purely, Fold(..))
import Control.Monad (forM_)
import Control.Monad.IO.Class (MonadIO, liftIO)
import qualified Data.Vector as V
import Lens.Family2 (view)
import qualified Options.Applicative as OP
import Pipes (for, Producer, (>->), yield, Consumer, cat)
import Pipes.Group (groupsBy, folds)
import Pipes.Safe (MonadSafe, runSafeT)
import qualified Pipes.Prelude as P
import System.IO (hPutStrLn, stderr)
import Text.Printf (printf)
data ProgOpt = ProgOpt InputOption
data InputOption = FreqsumInput (Maybe FilePath) | EigenstratInput FilePath FilePath FilePath
data InputEntry = InputEntry Chrom GenoLine deriving (Show)
type StatsReportAllSamples = [StatsReport]
data StatsReport = StatsReport {
srNrSitesMissing :: Int,
srNrSitesHomRef :: Int,
srNrSitesHomAlt :: Int,
srNrSitesHet :: Int
} deriving (Show)
main :: IO ()
main = OP.execParser optionSpec >>= runWithOpts
optionSpec :: OP.ParserInfo ProgOpt
optionSpec = OP.info (pure (.) <*> versionInfoOpt <*> OP.helper <*> argParser) (
OP.progDesc ("A program \
\to evaluate per-chromosome and total statistics \
\of genotyping data, read either as Eigenstrat (by specifying options -g, -s and -i) or FreqSum (default, or by specifying option -f). " ++ versionInfoText))
argParser :: OP.Parser ProgOpt
argParser = ProgOpt <$> (parseFreqsumInput <|> parseEigenstratInput)
parseFreqsumInput :: OP.Parser InputOption
parseFreqsumInput =
process <$> OP.strOption (OP.long "freqsum" <> OP.short 'f' <> OP.help "a freqsum file to read as input. Use - to read from stdin (the default)" <>
OP.value "-" <> OP.showDefault <> OP.metavar "FILEPATH")
where
process p =
if p == "-"
then FreqsumInput Nothing
else FreqsumInput (Just p)
parseEigenstratInput :: OP.Parser InputOption
parseEigenstratInput = EigenstratInput <$> parseGenoFile <*> parseSnpFile <*> parseIndFile
where
parseGenoFile = OP.strOption (OP.long "eigenstratGeno" <> OP.short 'g' <> OP.help "Eigenstrat Geno File" <> OP.metavar "FILEPATH")
parseSnpFile = OP.strOption (OP.long "eigenstratSnp" <> OP.short 's' <> OP.help "Eigenstrat Snp File" <> OP.metavar "FILEPATH")
parseIndFile = OP.strOption (OP.long "eigenstratInd" <> OP.short 'i' <> OP.help "Eigenstrat Ind File" <> OP.metavar "FILEPATH")
runWithOpts :: ProgOpt -> IO ()
runWithOpts (ProgOpt inputOpt) = runSafeT $ do
(names, entryProducer) <- case inputOpt of
FreqsumInput fsFile -> runWithFreqSum fsFile
EigenstratInput genoFile snpFile indFile -> runWithEigenstrat genoFile snpFile indFile
liftIO $ hPutStrLn stderr ("processing samples: " <> show names)
let p = runStats names entryProducer >-> P.tee (reportStats names)
totalReport <- purely P.fold (accumulateAllChromStats names) p
printReports names totalReport
runWithFreqSum :: (MonadSafe m) => Maybe FilePath -> m ([String], Producer InputEntry m ())
runWithFreqSum fsFile = do
(FreqSumHeader names nrHaps, fsProd) <- case fsFile of
Nothing -> readFreqSumStdIn
Just fn -> readFreqSumFile fn
let prod = for fsProd $ \(FreqSumEntry chrom _ _ _ _ _ counts) -> do
let genotypes = V.fromList $ do
(count', nrHap) <- zip counts nrHaps
case count' of
Just 0 -> return HomRef
Just x | x == nrHap -> return HomAlt
| x > 0 && x < nrHap -> return Het
Nothing -> return Missing
_ -> error "should not happen"
yield $ InputEntry chrom genotypes
return (names, prod)
runWithEigenstrat :: (MonadSafe m) =>
FilePath -> FilePath -> FilePath -> m ([String], Producer InputEntry m ())
runWithEigenstrat genoFile snpFile indFile = do
(indEntries, genoStream) <- readEigenstrat genoFile snpFile indFile
let names = [name | EigenstratIndEntry name _ _ <- indEntries]
let prod = genoStream >-> P.map (\(EigenstratSnpEntry chrom _ _ _ _ _, genoLine) -> InputEntry chrom genoLine)
return (names, prod)
runStats :: (MonadIO m) => [String] -> Producer InputEntry m () ->
Producer (Chrom, StatsReportAllSamples) m ()
runStats names entryProducer =
let groupedProd = view (groupsBy (\(InputEntry c1 _) (InputEntry c2 _) -> c1 == c2))
entryProducer
in purely folds (runStatsPerChrom (length names)) groupedProd
runStatsPerChrom :: Int -> Fold InputEntry (Chrom, StatsReportAllSamples)
runStatsPerChrom nrSamples = (,) <$> getChrom <*>
traverse runStatsPerChromPerSample [0..(nrSamples - 1)]
getChrom :: Fold InputEntry Chrom
getChrom = Fold (\_ (InputEntry c _) -> c) (Chrom "") id
runStatsPerChromPerSample :: Int -> Fold InputEntry StatsReport
runStatsPerChromPerSample i = Fold step initial extract
where
step :: StatsReport -> InputEntry -> StatsReport
step accum@(StatsReport miss homr homa het) (InputEntry _ line) = case (line V.! i) of
Missing -> accum {srNrSitesMissing = miss + 1}
HomRef -> accum {srNrSitesHomRef = homr + 1}
HomAlt -> accum {srNrSitesHomAlt = homa + 1}
Het -> accum {srNrSitesHet = het + 1}
initial :: StatsReport
initial = StatsReport 0 0 0 0
extract :: StatsReport -> StatsReport
extract = id
reportStats :: (MonadIO m) => [String] -> Consumer (Chrom, StatsReportAllSamples) m ()
reportStats names = do
liftIO . putStrLn $ "Chrom\tSample\tMissing\tHomRef\tHomAlt\tHet"
for cat $ \(chrom, reports) -> printReports names (chrom, reports)
printReports :: (MonadIO m) => [String] -> (Chrom, StatsReportAllSamples) -> m ()
printReports names (chrom, reports) =
forM_ (zip names reports) $ \(n, StatsReport mis ref alt het) -> do
let total = mis + ref + alt + het
misPerc = round $ (fromIntegral mis / fromIntegral total) * (100.0 :: Double) :: Int
liftIO . putStrLn $ printf "%s\t%s\t%d(%d%%)\t%d\t%d\t%d" (show chrom) n mis misPerc ref alt het
accumulateAllChromStats :: [String] ->
Fold (Chrom, StatsReportAllSamples) (Chrom, StatsReportAllSamples)
accumulateAllChromStats names = Fold step initial extract
where
step :: StatsReportAllSamples -> (Chrom, StatsReportAllSamples) -> StatsReportAllSamples
step sumReports (_, newReports) = do
(StatsReport smiss shomr shoma shet, StatsReport miss homr homa het) <-
zip sumReports newReports
return $ StatsReport (smiss + miss) (shomr + homr) (shoma + homa) (shet + het)
initial :: StatsReportAllSamples
initial = [StatsReport 0 0 0 0 | _ <- names]
extract :: StatsReportAllSamples -> (Chrom, StatsReportAllSamples)
extract r = (Chrom "Total", r)