packages feed

sequence-formats 1.6.6.1 → 1.6.7.0

raw patch · 7 files changed

+123/−43 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ SequenceFormats.Plink: PlinkFamEntry :: String -> String -> String -> String -> Sex -> String -> PlinkFamEntry
+ SequenceFormats.Plink: PlinkPopNameAsBoth :: PlinkPopNameMode
+ SequenceFormats.Plink: PlinkPopNameAsFamily :: PlinkPopNameMode
+ SequenceFormats.Plink: PlinkPopNameAsPhenotype :: PlinkPopNameMode
+ SequenceFormats.Plink: [_famFamilyID] :: PlinkFamEntry -> String
+ SequenceFormats.Plink: [_famFatherID] :: PlinkFamEntry -> String
+ SequenceFormats.Plink: [_famIndividualID] :: PlinkFamEntry -> String
+ SequenceFormats.Plink: [_famMotherID] :: PlinkFamEntry -> String
+ SequenceFormats.Plink: [_famPhenotype] :: PlinkFamEntry -> String
+ SequenceFormats.Plink: [_famSexCode] :: PlinkFamEntry -> Sex
+ SequenceFormats.Plink: data PlinkFamEntry
+ SequenceFormats.Plink: data PlinkPopNameMode
+ SequenceFormats.Plink: eigenstratInd2PlinkFam :: PlinkPopNameMode -> EigenstratIndEntry -> PlinkFamEntry
+ SequenceFormats.Plink: instance GHC.Classes.Eq SequenceFormats.Plink.PlinkFamEntry
+ SequenceFormats.Plink: instance GHC.Classes.Eq SequenceFormats.Plink.PlinkPopNameMode
+ SequenceFormats.Plink: instance GHC.Show.Show SequenceFormats.Plink.PlinkFamEntry
+ SequenceFormats.Plink: instance GHC.Show.Show SequenceFormats.Plink.PlinkPopNameMode
+ SequenceFormats.Plink: plinkFam2EigenstratInd :: PlinkPopNameMode -> PlinkFamEntry -> EigenstratIndEntry
- SequenceFormats.Plink: readFamFile :: MonadIO m => FilePath -> m [EigenstratIndEntry]
+ SequenceFormats.Plink: readFamFile :: MonadIO m => FilePath -> m [PlinkFamEntry]
- SequenceFormats.Plink: readPlink :: MonadSafe m => FilePath -> FilePath -> FilePath -> m ([EigenstratIndEntry], Producer (EigenstratSnpEntry, GenoLine) m ())
+ SequenceFormats.Plink: readPlink :: MonadSafe m => FilePath -> FilePath -> FilePath -> m ([PlinkFamEntry], Producer (EigenstratSnpEntry, GenoLine) m ())
- SequenceFormats.Plink: writePlink :: MonadSafe m => FilePath -> FilePath -> FilePath -> [EigenstratIndEntry] -> Consumer (EigenstratSnpEntry, GenoLine) m ()
+ SequenceFormats.Plink: writePlink :: MonadSafe m => FilePath -> FilePath -> FilePath -> [PlinkFamEntry] -> Consumer (EigenstratSnpEntry, GenoLine) m ()

Files

Changelog.md view
@@ -1,7 +1,15 @@+V 1.6.7.0:+* Added an option to Plink output, which allows users to store the population mame in either the first or last column, or both+* Fixed a bug in the pileup-parser which failed at reference-skip symbols (">" and "<").+ V 1.6.6.0: Added a minimal Bed parser and a bed-filter+ V 1.6.5.0: Long chromosome names are now parsed correctly+ V 1.6.4: Fixed a bug in Fasta parsing which would skip the first line if the header was minimal.+ V 1.6.3: Pileup reference-base is read as upper case, even if it's not in the file.+ V 1.6.2: Made compatible with GHC 9 (thanks to github user sjakobi)  V 1.6.0: Plink Output now supported.
sequence-formats.cabal view
@@ -1,6 +1,6 @@ cabal-version:      >=1.10 name:               sequence-formats-version:            1.6.6.1+version:            1.6.7.0 license:            GPL-3 license-file:       LICENSE maintainer:         stephan.schiffels@mac.com
src/SequenceFormats/Pileup.hs view
@@ -68,9 +68,9 @@         | x == ',' = (refA, ReverseStrand) : go xs         | x `elem` ("ACTGN" :: String) = (x, ForwardStrand) : go xs         | x `elem` ("actgn" :: String) = (toUpper x, ReverseStrand) : go xs-        | x `elem` ("$*" :: String) = go xs-        | x == '^' = go (drop 1 xs)-        | (x == '+' || x == '-') =+        | x `elem` ("$*#<>" :: String) = go xs+        | x == '^' = go (drop 1 xs) -- skip the next character, which is the mapping quality +        | x == '+' || x == '-' =  -- insertions or deletions, followed by a decimal number             let [(num, rest)] = reads xs in go (drop num rest)         | otherwise = error $ "cannot parse read base string: " ++ (x:xs)     go [] = []
src/SequenceFormats/Plink.hs view
@@ -1,6 +1,16 @@ {-# LANGUAGE BinaryLiterals    #-} {-# LANGUAGE OverloadedStrings #-}-module SequenceFormats.Plink (readBimStdIn, readBimFile, writeBim, readFamFile, readPlinkBedFile, readPlink, writePlink) where+module SequenceFormats.Plink (readBimStdIn,+                              readBimFile,+                              writeBim,+                              readFamFile,+                              readPlinkBedFile,+                              readPlink,+                              writePlink,+                              PlinkFamEntry(..),+                              plinkFam2EigenstratInd,+                              eigenstratInd2PlinkFam,+                              PlinkPopNameMode(..)) where  import           SequenceFormats.Eigenstrat       (EigenstratIndEntry (..),                                                    EigenstratSnpEntry (..),@@ -19,6 +29,7 @@ import           Data.Bits                        (shiftL, shiftR, (.&.), (.|.)) import qualified Data.ByteString                  as BB import qualified Data.ByteString.Char8            as B+import           Data.List                        (intercalate) import           Data.Vector                      (fromList, toList) import           Data.Word                        (Word8) import           Pipes                            (Consumer, Producer, (>->))@@ -30,7 +41,18 @@ import           System.IO                        (Handle, IOMode (..),                                                    hPutStrLn, withFile) +-- see https://www.cog-genomics.org/plink/2.0/formats#fam+data PlinkFamEntry = PlinkFamEntry {+    _famFamilyID     :: String,+    _famIndividualID :: String,+    _famFatherID     :: String,+    _famMotherID     :: String,+    _famSexCode      :: Sex,+    _famPhenotype    :: String+} deriving (Eq, Show) +data PlinkPopNameMode = PlinkPopNameAsFamily | PlinkPopNameAsPhenotype | PlinkPopNameAsBoth deriving (Eq, Show)+ bimParser :: A.Parser EigenstratSnpEntry bimParser = do     chrom      <- word@@ -51,23 +73,39 @@     convertNum '4' = 'T'     convertNum x   = x -famParser :: A.Parser EigenstratIndEntry+famParser :: A.Parser PlinkFamEntry famParser = do     A.skipMany A.space-    pop <- word-    ind <- A.skipMany1 A.space >> word-    _   <- A.skipMany1 A.space >> A.decimal :: A.Parser Int-    _   <- A.skipMany1 A.space >> A.decimal :: A.Parser Int-    sex <- A.skipMany1 A.space >> parseSex-    _   <- A.skipMany1 A.space >> word+    famID    <- B.unpack <$> word+    indID    <- B.unpack <$> (A.skipMany1 A.space >> word)+    fatherID <- B.unpack <$> (A.skipMany1 A.space >> word)+    motherID <- B.unpack <$> (A.skipMany1 A.space >> word)+    sex      <- A.skipMany1 A.space >> parseSex+    phen     <- B.unpack <$> (A.skipMany1 A.space >> word)     void A.endOfLine-    return $ EigenstratIndEntry (B.unpack ind) sex (B.unpack pop)+    return $ PlinkFamEntry famID indID fatherID motherID sex phen   where     parseSex = parseMale <|> parseFemale <|> parseUnknown     parseMale = A.char '1' >> return Male     parseFemale = A.char '2' >> return Female     parseUnknown = A.anyChar >> return Unknown +plinkFam2EigenstratInd :: PlinkPopNameMode -> PlinkFamEntry -> EigenstratIndEntry+plinkFam2EigenstratInd plinkPopNameMode (PlinkFamEntry famId indId _ _ sex phen) =+    let popName = case plinkPopNameMode of+            PlinkPopNameAsFamily    -> famId+            PlinkPopNameAsPhenotype -> phen+            -- If the two differ but you want both, then merge them somehow.+            PlinkPopNameAsBoth -> if famId == phen then famId else famId ++ ":" ++ phen+    in  EigenstratIndEntry indId sex popName++eigenstratInd2PlinkFam :: PlinkPopNameMode -> EigenstratIndEntry -> PlinkFamEntry+eigenstratInd2PlinkFam plinkPopNameMode (EigenstratIndEntry indId sex popName)=+    case plinkPopNameMode of+        PlinkPopNameAsFamily    -> PlinkFamEntry popName indId "0" "0" sex "0"+        PlinkPopNameAsPhenotype -> PlinkFamEntry "DummyFamily" indId "0" "0" sex popName+        PlinkPopNameAsBoth      -> PlinkFamEntry popName indId "0" "0" sex popName+ bedHeaderParser :: AB.Parser () bedHeaderParser = do     void $ AB.word8 0b01101100 -- magic number I for BED files@@ -110,7 +148,7 @@ readBimFile = consumeProducer bimParser . readFileProd  -- |Function to read a Plink fam file. Returns the Eigenstrat Individual Entries as list.-readFamFile :: (MonadIO m) => FilePath -> m [EigenstratIndEntry]+readFamFile :: (MonadIO m) => FilePath -> m [PlinkFamEntry] readFamFile fn =     liftIO . withFile fn ReadMode $ \handle ->         P.toListM $ consumeProducer famParser (PB.fromHandle handle)@@ -119,7 +157,7 @@ readPlink :: (MonadSafe m) => FilePath -- ^The Bed file                -> FilePath -- ^The Bim File                -> FilePath -- ^The Fam file-               -> m ([EigenstratIndEntry], Producer (EigenstratSnpEntry, GenoLine) m ()) -- The return pair of individual entries and a joint Snp/Geno Producer.+               -> m ([PlinkFamEntry], Producer (EigenstratSnpEntry, GenoLine) m ()) -- The return pair of individual entries and a joint Snp/Geno Producer. readPlink bedFile bimFile famFile = do     indEntries <- readFamFile famFile     let nrInds = length indEntries@@ -139,11 +177,11 @@     in  toTextPipe >-> snpOutTextConsumer  -- |Function to write a Plink Fam file.-writeFam :: (MonadIO m) => FilePath -> [EigenstratIndEntry] -> m ()+writeFam :: (MonadIO m) => FilePath -> [PlinkFamEntry] -> m () writeFam f indEntries =     liftIO . withFile f WriteMode $ \h ->-        forM_ indEntries $ \(EigenstratIndEntry name sex popName) ->-            hPutStrLn h $ popName <> "\t" <> name <> "\t0\t0\t" <> sexToStr sex <> "\t0"+        forM_ indEntries $ \(PlinkFamEntry famId indId fatherId motherId sex phen) ->+            hPutStrLn h . intercalate "\t" $ [famId, indId, fatherId, motherId, sexToStr sex, phen]   where     sexToStr sex = case sex of         Male    -> "1"@@ -163,12 +201,12 @@     genoLineToBytes genoLine = go (toList genoLine)       where         go :: [GenoEntry] -> [Word8]-        go [] = [] -- empty list for recursion stop+        go []                         = [] -- empty list for recursion stop         go (g1 : g2 : g3 : g4 : rest) = constructByte [g1, g2, g3, g4] : go rest -- at least 5 entries -> more than 1 byte-        go genoEntries = [constructByte genoEntries] -- four or less entries -> 1 byte+        go genoEntries                = [constructByte genoEntries] -- four or less entries -> 1 byte         constructByte :: [GenoEntry] -> Word8-        constructByte [] = error "constructByte - should never happen"-        constructByte [g] = genoEntryToByte g+        constructByte []     = error "constructByte - should never happen"+        constructByte [g]    = genoEntryToByte g         constructByte (g:gs) = shiftL (constructByte gs) 2 .|. genoEntryToByte g  genoEntryToByte :: GenoEntry -> Word8@@ -181,7 +219,7 @@ writePlink :: (MonadSafe m) => FilePath -- ^The Bed file                 -> FilePath -- ^The Bim File                 -> FilePath -- ^The Fam file-                -> [EigenstratIndEntry] -- ^The list of individual entries+                -> [PlinkFamEntry] -- ^The list of individual entries                 -> Consumer (EigenstratSnpEntry, GenoLine) m () -- ^A consumer to read joint Snp/Genotype entries. writePlink bedFile bimFile famFile indEntries = do     liftIO $ writeFam famFile indEntries
test/SequenceFormats/PlinkSpec.hs view
@@ -6,7 +6,9 @@                                              GenoEntry (..), GenoLine, Sex (..)) import           SequenceFormats.Plink      (readBimFile, readFamFile,                                              readPlink, readPlinkBedFile,-                                             writePlink)+                                             writePlink, PlinkFamEntry(..),+                                             plinkFam2EigenstratInd, eigenstratInd2PlinkFam,+                                             PlinkPopNameMode(..)) import           SequenceFormats.Utils      (Chrom (..))  import           Control.Foldl              (list, purely)@@ -24,6 +26,8 @@     testReadBedFile     testReadPlink     testWritePlink+    testFam2Ind+    testInd2Fam  mockDatEigenstratSnp :: [EigenstratSnpEntry] mockDatEigenstratSnp = [@@ -35,13 +39,13 @@     EigenstratSnpEntry (Chrom "11") 500000 0.005000 "rs5555" 'T' 'A',     EigenstratSnpEntry (Chrom "11") 600000 0.006000 "rs6666" 'G' 'T'] -mockDatEigenstratInd :: [EigenstratIndEntry]-mockDatEigenstratInd = [-    EigenstratIndEntry "SAMPLE0" Female "1",-    EigenstratIndEntry "SAMPLE1" Male "2",-    EigenstratIndEntry "SAMPLE2" Female "3",-    EigenstratIndEntry "SAMPLE3" Male "4",-    EigenstratIndEntry "SAMPLE4" Female "5"]+mockDatPlinkFam :: [PlinkFamEntry]+mockDatPlinkFam = [+    PlinkFamEntry "Family1" "SAMPLE0" "0" "0" Female "Pop1",+    PlinkFamEntry "Family2" "SAMPLE1" "0" "0" Male "Pop2",+    PlinkFamEntry "Family3" "SAMPLE2" "0" "0" Female "Pop3",+    PlinkFamEntry "Family4" "SAMPLE3" "0" "0" Male "Pop4",+    PlinkFamEntry "Family5" "SAMPLE4" "0" "0" Female "Pop5"]  mockDatPlinkBed :: [GenoLine] mockDatPlinkBed = [@@ -62,7 +66,7 @@ testReadFamFile :: Spec testReadFamFile = describe "readFamFile" $     it "should read a FAM file correctly" $ do-        readFamFile "testDat/example.fam" `shouldReturn` mockDatEigenstratInd+        readFamFile "testDat/example.fam" `shouldReturn` mockDatPlinkFam  testReadBedFile :: Spec testReadBedFile = describe "readBedFile" $@@ -77,10 +81,10 @@ testReadPlink = describe "readPlink" $ do     it "should read the correct Plink files" $ do         let bimFile = "testDat/example.plink.bim"-            famFile = "testDat/example.plink.fam"+            famFile = "testDat/example.fam"             bedFile = "testDat/example.plink.bed"         (indEntries, esProd) <- runSafeT $ readPlink bedFile bimFile famFile-        indEntries `shouldBe` mockDatEigenstratInd+        indEntries `shouldBe` mockDatPlinkFam         snpGenoEntries <- runSafeT $ purely P.fold list esProd         (map fst snpGenoEntries) `shouldBe` mockDatEigenstratSnp         (map snd snpGenoEntries) `shouldBe` mockDatPlinkBed@@ -95,9 +99,39 @@             testDatGenoProd = each mockDatPlinkBed             testDatJointProd = P.zip testDatSnpProd testDatGenoProd         liftIO . runSafeT . runEffect $-            testDatJointProd >-> writePlink tmpGeno tmpSnp tmpInd mockDatEigenstratInd+            testDatJointProd >-> writePlink tmpGeno tmpSnp tmpInd mockDatPlinkFam         (indEntries, esProd) <- liftIO . runSafeT $ readPlink tmpGeno tmpSnp tmpInd-        indEntries `shouldBe` mockDatEigenstratInd+        indEntries `shouldBe` mockDatPlinkFam         snpGenoEntries <- liftIO . runSafeT $ purely P.fold list esProd         (map fst snpGenoEntries) `shouldBe` mockDatEigenstratSnp         (map snd snpGenoEntries) `shouldBe` mockDatPlinkBed++testFam2Ind :: Spec+testFam2Ind = describe "plinkFam2EigenstratInd" $ do+    it "should correctly convert with family-option" $ do+        let fam = PlinkFamEntry "Family1" "SAMPLE0" "0" "0" Female "Pop1"+        plinkFam2EigenstratInd PlinkPopNameAsFamily fam `shouldBe` EigenstratIndEntry "SAMPLE0" Female "Family1"+    it "should correctly convert with phenotype-option" $ do+        let fam = PlinkFamEntry "Family1" "SAMPLE0" "0" "0" Female "Pop1"+        plinkFam2EigenstratInd PlinkPopNameAsPhenotype fam `shouldBe` EigenstratIndEntry "SAMPLE0" Female "Pop1"+    it "should correctly convert with both-option when they differ" $ do+        let fam = PlinkFamEntry "Family1" "SAMPLE0" "0" "0" Female "Pop1"+        plinkFam2EigenstratInd PlinkPopNameAsBoth fam `shouldBe` EigenstratIndEntry "SAMPLE0" Female "Family1:Pop1"+    it "should correctly convert with both-option when they are the same" $ do+        let fam = PlinkFamEntry "Pop1" "SAMPLE0" "0" "0" Female "Pop1"+        plinkFam2EigenstratInd PlinkPopNameAsBoth fam `shouldBe` EigenstratIndEntry "SAMPLE0" Female "Pop1"+    +testInd2Fam :: Spec+testInd2Fam = describe "eigenstratInd2PlinkFam" $ do+    it "should correctly convert with family-option" $ do+        let es = EigenstratIndEntry "SAMPLE0" Female "Pop1"+            fam = PlinkFamEntry "Pop1" "SAMPLE0" "0" "0" Female "0"+        eigenstratInd2PlinkFam PlinkPopNameAsFamily es `shouldBe` fam+    it "should correctly convert with phenotype-option" $ do+        let es = EigenstratIndEntry "SAMPLE0" Female "Pop1"+            fam = PlinkFamEntry "DummyFamily" "SAMPLE0" "0" "0" Female "Pop1"+        eigenstratInd2PlinkFam PlinkPopNameAsPhenotype es `shouldBe` fam+    it "should correctly convert with both-option" $ do+        let es = EigenstratIndEntry "SAMPLE0" Female "Pop1"+            fam = PlinkFamEntry "Pop1" "SAMPLE0" "0" "0" Female "Pop1"+        eigenstratInd2PlinkFam PlinkPopNameAsBoth es `shouldBe` fam
testDat/example.fam view
@@ -1,5 +1,5 @@-     1      SAMPLE0 0 0 2 0-     2      SAMPLE1 0 0 1 0-     3      SAMPLE2 0 0 2 0-     4      SAMPLE3 0 0 1 0-     5      SAMPLE4 0 0 2 0+     Family1      SAMPLE0 0 0 2 Pop1+     Family2      SAMPLE1 0 0 1 Pop2+     Family3      SAMPLE2 0 0 2 Pop3+     Family4      SAMPLE3 0 0 1 Pop4+     Family5      SAMPLE4 0 0 2 Pop5
testDat/example.pileup view
@@ -1,3 +1,3 @@ 1 1000 A 4 ..,C*. IIII 3 ..ac* III 8 ,,.,ccAA-3acccA IIIIIIII-1 2000 c 4 ..C+4IIIIa IIII 3 ACt..* III 8 .a.a.C.C* IIIIIIII-2 1000 G 4 ,,.,,..-3AccC*. IIII 3 .,,* III 8 .,,.,,* IIIIIIII+1 2000 c 4 ..C+4IIIIa< IIII 3 ACt..* III 8 .a.a.C.C* IIIIIIII+2 1000 G 4 ,,.,,.#.-3Acc>C*. IIII 3 .,,* III 8 .,,.,,* IIIIIIII