Skip to content

Commit 00ab323

Browse files
authored
Parsimon fixes (#2649)
* Parsimony Filtering: Revert to internal filtering for disambiguation * Added parsimony reporting * tests * Better check on filtering psms by q value * revert post search analysis task * Require explicit PSM input for parsimony * Require explicit filtered input for protein scoring * Align protein scoring filter tie behavior * Strengthen scoring tests and remove triage notes
1 parent 79e2bf5 commit 00ab323

12 files changed

Lines changed: 504 additions & 96 deletions

File tree

MetaMorpheus/EngineLayer/ProteinParsimony/ProteinParsimonyEngine.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Transcriptomics.Digestion;
1313
using Transcriptomics;
1414
using EngineLayer.SpectrumMatch;
15+
using System.Threading;
1516

1617
namespace EngineLayer
1718
{
@@ -30,24 +31,27 @@ public class ProteinParsimonyEngine : MetaMorpheusEngine
3031
/// </summary>
3132
private readonly bool _treatModPeptidesAsDifferentPeptides;
3233

33-
public ProteinParsimonyEngine(FilteredPsms filteredPsmsForParsimony, bool modPeptidesAreDifferent, CommonParameters commonParameters, List<(string fileName, CommonParameters fileSpecificParameters)> fileSpecificParameters, List<string> nestedIds) : base(commonParameters, fileSpecificParameters, nestedIds)
34+
public ProteinParsimonyEngine(List<SpectralMatch> unFilteredPsmsForParsimony, bool modPeptidesAreDifferent, CommonParameters commonParameters, List<(string fileName, CommonParameters fileSpecificParameters)> fileSpecificParameters, List<string> nestedIds) : base(commonParameters, fileSpecificParameters, nestedIds)
3435
{
3536
_treatModPeptidesAsDifferentPeptides = modPeptidesAreDifferent;
37+
_fdrFilteredPsms = new List<SpectralMatch>();
3638

37-
if (!filteredPsmsForParsimony.FilteredPsmsList.Any())
38-
{
39-
_fdrFilteredPsms = new List<SpectralMatch>();
40-
}
39+
var filtered = FilteredPsms.Filter(unFilteredPsmsForParsimony,
40+
commonParams: CommonParameters,
41+
includeDecoys: true,
42+
includeContaminants: true,
43+
includeAmbiguous: false,
44+
includeHighQValuePsms: false);
4145

4246
// parsimony will only use non-ambiguous, high-confidence PSMs
4347
// KEEP contaminants for use in parsimony!
4448
if (modPeptidesAreDifferent)
4549
{
46-
_fdrFilteredPsms = filteredPsmsForParsimony.FilteredPsmsList.Where(p => p.FullSequence != null).ToList();
50+
_fdrFilteredPsms = filtered.FilteredPsmsList.Where(p => p.FullSequence != null).ToList();
4751
}
4852
else
4953
{
50-
_fdrFilteredPsms = filteredPsmsForParsimony.FilteredPsmsList.Where(p => p.BaseSequence != null).ToList();
54+
_fdrFilteredPsms = filtered.FilteredPsmsList.Where(p => p.BaseSequence != null).ToList();
5155
}
5256

5357
// peptides to use in parsimony = peptides observed in high-confidence PSMs (including decoys)
@@ -62,14 +66,16 @@ public ProteinParsimonyEngine(FilteredPsms filteredPsmsForParsimony, bool modPep
6266

6367
// we're storing all PSMs (not just FDR-filtered ones) here because we will remove some protein associations
6468
// from low-confidence PSMs if they can be explained by a parsimonious protein
65-
_allPsms = filteredPsmsForParsimony.FilteredPsmsList;
69+
_allPsms = unFilteredPsmsForParsimony;
6670
}
6771

6872
protected override MetaMorpheusEngineResults RunSpecific()
6973
{
7074
ProteinParsimonyResults myAnalysisResults = new ProteinParsimonyResults(this);
7175

72-
myAnalysisResults.ProteinGroups = RunProteinParsimonyEngine();
76+
myAnalysisResults.ProteinGroups = RunProteinParsimonyEngine(out int hypothesesAdded, out int hypothesesRemoved);
77+
myAnalysisResults.HypothesesRemoved = hypothesesRemoved;
78+
myAnalysisResults.HypothesesAdded = hypothesesAdded;
7379

7480
return myAnalysisResults;
7581
}
@@ -79,8 +85,11 @@ protected override MetaMorpheusEngineResults RunSpecific()
7985
/// Parsimony algorithm based on: https://www.ncbi.nlm.nih.gov/pubmed/14632076 Anal Chem. 2003 Sep 1;75(17):4646-58.
8086
/// TODO: Note describing that peptide objects with the same sequence are associated with different proteins
8187
/// </summary>
82-
private List<ProteinGroup> RunProteinParsimonyEngine()
88+
private List<ProteinGroup> RunProteinParsimonyEngine(out int hypothesesAdded, out int hypothesesRemoved)
8389
{
90+
hypothesesAdded = 0;
91+
hypothesesRemoved = 0;
92+
8493
// parsimonious list of proteins built by this protein parsimony engine
8594
HashSet<IBioPolymer> parsimoniousProteinList = new();
8695

@@ -118,6 +127,7 @@ private List<ProteinGroup> RunProteinParsimonyEngine()
118127
var sequenceWithPsmsList = sequenceWithPsms.ToList();
119128

120129
// create new peptide-protein associations as needed
130+
int added = 0;
121131
Parallel.ForEach(Partitioner.Create(0, sequenceWithPsmsList.Count),
122132
new ParallelOptions { MaxDegreeOfParallelism = CommonParameters.MaxThreadsToUsePerFile },
123133
(range, loopState) =>
@@ -219,14 +229,16 @@ private List<ProteinGroup> RunProteinParsimonyEngine()
219229
hypothesis.PeptideCumulativeTargetNotch = tentativeMatch.PeptideCumulativeTargetNotch;
220230
hypothesis.PeptideCumulativeDecoyNotch = tentativeMatch.PeptideCumulativeDecoyNotch;
221231

222-
psm.AddProteinMatch(hypothesis);
232+
if (psm.AddProteinMatch(hypothesis))
233+
Interlocked.Increment(ref added);
223234
}
224235
}
225236
}
226237
}
227238
}
228239
}
229240
);
241+
hypothesesAdded += added;
230242
}
231243
}
232244

@@ -440,7 +452,7 @@ private List<ProteinGroup> RunProteinParsimonyEngine()
440452
// no protein associations are removed)
441453
if (psm.BestMatchingBioPolymersWithSetMods.Any(p => parsimoniousProteinList.Contains(p.SpecificBioPolymer.Parent)))
442454
{
443-
psm.TrimProteinMatches(parsimoniousProteinList);
455+
hypothesesRemoved += psm.TrimProteinMatches(parsimoniousProteinList);
444456
}
445457
}
446458

MetaMorpheus/EngineLayer/ProteinParsimony/ProteinParsimonyResults.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace EngineLayer
55
{
66
public class ProteinParsimonyResults : MetaMorpheusEngineResults
77
{
8+
public int HypothesesAdded { get; set; }
9+
public int HypothesesRemoved { get; set; }
810
public ProteinParsimonyResults(ProteinParsimonyEngine proteinAnalysisEngine) : base(proteinAnalysisEngine)
911
{
1012
}
@@ -15,7 +17,9 @@ public override string ToString()
1517
{
1618
var sb = new StringBuilder();
1719
sb.AppendLine(base.ToString());
20+
sb.AppendLine($"Hypotheses Added to Spectral Matches: {HypothesesAdded}");
21+
sb.AppendLine($"Hypotheses Removed from Spectral Matches: {HypothesesRemoved}");
1822
return sb.ToString();
1923
}
2024
}
21-
}
25+
}

MetaMorpheus/EngineLayer/ProteinScoringAndFdr/ProteinScoringAndFdrEngine.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using EngineLayer.SpectrumMatch;
2-
using Proteomics.ProteolyticDigestion;
32
using System.Collections.Generic;
43
using System.Linq;
54
using Omics;
@@ -8,7 +7,7 @@ namespace EngineLayer
87
{
98
public class ProteinScoringAndFdrEngine : MetaMorpheusEngine
109
{
11-
private readonly IEnumerable<SpectralMatch> _FilteredPsms;
10+
private readonly IList<SpectralMatch> _FilteredPsms;
1211
private readonly bool NoOneHitWonders;
1312
private readonly bool TreatModPeptidesAsDifferentPeptides;
1413
private readonly bool MergeIndistinguishableProteinGroups;
@@ -17,17 +16,26 @@ public class ProteinScoringAndFdrEngine : MetaMorpheusEngine
1716
private readonly FilterType _filterType;
1817
private readonly double _filterThreshold;
1918

20-
public ProteinScoringAndFdrEngine(List<ProteinGroup> proteinGroups, FilteredPsms filteredPsms, bool noOneHitWonders, bool treatModPeptidesAsDifferentPeptides, bool mergeIndistinguishableProteinGroups,
19+
public ProteinScoringAndFdrEngine(List<ProteinGroup> proteinGroups, IList<SpectralMatch> filteredPsms, bool noOneHitWonders, bool treatModPeptidesAsDifferentPeptides, bool mergeIndistinguishableProteinGroups,
2120
CommonParameters commonParameters, List<(string fileName, CommonParameters fileSpecificParameters)> fileSpecificParameters, List<string> nestedIds) : base(commonParameters, fileSpecificParameters, nestedIds)
2221
{
23-
_FilteredPsms = filteredPsms.FilteredPsmsList;
22+
_FilteredPsms = filteredPsms;
2423
ProteinGroups = proteinGroups;
2524
NoOneHitWonders = noOneHitWonders;
2625
TreatModPeptidesAsDifferentPeptides = treatModPeptidesAsDifferentPeptides;
2726
MergeIndistinguishableProteinGroups = mergeIndistinguishableProteinGroups;
2827
_decoyIdentifiers = proteinGroups.SelectMany(p => p.Proteins.Where(b => b.IsDecoy).Select(b => b.Accession.Split('_')[0])).ToHashSet();
29-
_filterType = filteredPsms.FilterType;
30-
_filterThreshold = filteredPsms.FilterThreshold;
28+
29+
if (CommonParameters.PepQValueThreshold < CommonParameters.QValueThreshold)
30+
{
31+
_filterType = FilterType.PepQValue;
32+
_filterThreshold = CommonParameters.PepQValueThreshold;
33+
}
34+
else
35+
{
36+
_filterType = FilterType.QValue;
37+
_filterThreshold = CommonParameters.QValueThreshold;
38+
}
3139
}
3240

3341
protected override MetaMorpheusEngineResults RunSpecific()
@@ -51,10 +59,12 @@ private void ScoreProteinGroups(List<ProteinGroup> proteinGroups, IEnumerable<Sp
5159
{
5260
// add each protein groups PSMs
5361
var peptideToPsmMatching = new Dictionary<IBioPolymerWithSetMods, HashSet<SpectralMatch>>();
54-
foreach (var psm in psmList)
62+
foreach (var psm in psmList.FilterByQValue(
63+
includeHighQValuePsms: false,
64+
qValueThreshold: _filterThreshold,
65+
filterAtPeptideLevel: false,
66+
filterType: _filterType))
5567
{
56-
// Use filter-type-aware threshold check
57-
5868
if ((TreatModPeptidesAsDifferentPeptides && psm.FullSequence != null) || (!TreatModPeptidesAsDifferentPeptides && psm.BaseSequence != null))
5969
{
6070
foreach (var pepWithSetMods in psm.BestMatchingBioPolymersWithSetMods.Select(p => p.SpecificBioPolymer))
@@ -253,4 +263,4 @@ private void AssignQValuesToProteins(List<ProteinGroup> sortedProteinGroups)
253263
}
254264
}
255265
}
256-
}
266+
}

MetaMorpheus/EngineLayer/SpectralMatch.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,34 +348,38 @@ public static Dictionary<string, string> DataDictionary(SpectralMatch psm, IRead
348348
/// <summary>
349349
/// This method is used by protein parsimony to remove PeptideWithSetModifications objects that have non-parsimonious protein associations
350350
/// </summary>
351-
public void TrimProteinMatches(HashSet<IBioPolymer> parsimoniousProteins)
351+
public int TrimProteinMatches(HashSet<IBioPolymer> parsimoniousProteins)
352352
{
353+
int removed = 0;
353354
if (IsDecoy)
354355
{
355356
if (_BestMatchingBioPolymersWithSetMods.Any(p => parsimoniousProteins.Contains(p.SpecificBioPolymer.Parent) && p.SpecificBioPolymer.Parent.IsDecoy))
356357
{
357-
_BestMatchingBioPolymersWithSetMods.RemoveAll(p => !parsimoniousProteins.Contains(p.SpecificBioPolymer.Parent));
358+
removed += _BestMatchingBioPolymersWithSetMods.RemoveAll(p => !parsimoniousProteins.Contains(p.SpecificBioPolymer.Parent));
358359
}
359360
// else do nothing
360361
}
361362
else
362363
{
363-
_BestMatchingBioPolymersWithSetMods.RemoveAll(p => !parsimoniousProteins.Contains(p.SpecificBioPolymer.Parent));
364+
removed += _BestMatchingBioPolymersWithSetMods.RemoveAll(p => !parsimoniousProteins.Contains(p.SpecificBioPolymer.Parent));
364365
}
365366

366367
ResolveAllAmbiguities();
368+
return removed;
367369
}
368370

369371
/// <summary>
370372
/// This method is used by protein parsimony to add PeptideWithSetModifications objects for modification-agnostic parsimony
371373
/// </summary>
372-
public void AddProteinMatch(SpectralMatchHypothesis tentativeSpectralMatch)
374+
public bool AddProteinMatch(SpectralMatchHypothesis tentativeSpectralMatch)
373375
{
374376
if (!_BestMatchingBioPolymersWithSetMods.Contains(tentativeSpectralMatch))
375377
{
376378
_BestMatchingBioPolymersWithSetMods.Add(tentativeSpectralMatch);
377379
ResolveAllAmbiguities();
380+
return true;
378381
}
382+
return false;
379383
}
380384

381385
#endregion
@@ -612,4 +616,4 @@ public int CompareTo(SpectralMatch otherPsm)
612616
}
613617

614618
}
615-
}
619+
}

MetaMorpheus/EngineLayer/SpectrumMatch/FilteredPsms.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public static FilteredPsms Filter(IEnumerable<SpectralMatch> psms,
116116
&& (includeContaminants || !psm.IsContaminant)
117117
&& (includeAmbiguous || !psm.BaseSequence.IsNullOrEmpty())
118118
&& (includeAmbiguousMods || !psm.FullSequence.IsNullOrEmpty()))
119-
.FilterByQValue(includeHighQValuePsms, filterThreshold, filterAtPeptideLevel, filterType)
119+
.FilterByQValue(includeHighQValuePsms, filterThreshold, filterAtPeptideLevel, filterType, true)
120120
.CollapseToPeptides(filterAtPeptideLevel)
121121
.ToList();
122122

@@ -153,8 +153,14 @@ internal static IEnumerable<SpectralMatch> CollapseToPeptides(this IEnumerable<S
153153
}
154154
}
155155

156-
internal static IEnumerable<SpectralMatch> FilterByQValue(this IEnumerable<SpectralMatch> psms, bool includeHighQValuePsms, double qValueThreshold, bool filterAtPeptideLevel, FilterType filterType)
156+
internal static IEnumerable<SpectralMatch> FilterByQValue(this IEnumerable<SpectralMatch> psms, bool includeHighQValuePsms, double qValueThreshold, bool filterAtPeptideLevel, FilterType filterType, bool filterValidated = false)
157157
{
158+
// If we validate the filter ahead of time, no need for a second iteration through the PSMs to check for the presence of PEP-QValues values.
159+
if (!filterValidated && filterType == FilterType.PepQValue && psms.All(p => p.GetFdrInfo(filterAtPeptideLevel) != null && Math.Abs(p.GetFdrInfo(filterAtPeptideLevel).PEP_QValue - 2) < 0.1 ))
160+
{
161+
filterType = FilterType.QValue;
162+
}
163+
158164
foreach (var psm in psms)
159165
{
160166
if (includeHighQValuePsms)

MetaMorpheus/TaskLayer/GlycoSearchTask/PostGlycoSearchAnalysisTask.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -293,22 +293,16 @@ private void SingleFDRAnalysis(List<GlycoSpectralMatch> items, CommonParameters
293293
private void GlycoProteinAnalysis(List<GlycoSpectralMatch> gsms, string outputFolder, string individualFileFolder = null, MyTaskResults myTaskResults = null )
294294
{
295295
// convert gsms to psms and filter
296-
var filteredPsms = FilteredPsms.Filter(gsms.Select(p => p as SpectralMatch).ToList(),
297-
commonParams: CommonParameters,
298-
includeDecoys: true,
299-
includeContaminants: true,
300-
includeAmbiguous: false,
301-
includeHighQValuePsms: false);
296+
var psms = gsms.Select(p => p as SpectralMatch).ToList();
302297
Status("Constructing protein groups...", Parameters.SearchTaskId);
303298

304299
// run parsimony
305-
ProteinParsimonyResults proteinAnalysisResults = (ProteinParsimonyResults)(new ProteinParsimonyEngine(filteredPsms, Parameters.GlycoSearchParameters.ModPeptidesAreDifferent, CommonParameters, this.FileSpecificParameters, new List<string> { Parameters.SearchTaskId }).Run());
300+
ProteinParsimonyResults proteinAnalysisResults = (ProteinParsimonyResults)(new ProteinParsimonyEngine(psms, Parameters.GlycoSearchParameters.ModPeptidesAreDifferent, CommonParameters, this.FileSpecificParameters, new List<string> { Parameters.SearchTaskId }).Run());
306301

307302
// score protein groups and calculate FDR
308-
// FilterType and FilterThreshold are obtained from the FilteredPsms object
309303
ProteinScoringAndFdrResults proteinScoringAndFdrResults = (ProteinScoringAndFdrResults)new ProteinScoringAndFdrEngine(
310304
proteinAnalysisResults.ProteinGroups,
311-
filteredPsms,
305+
psms,
312306
Parameters.GlycoSearchParameters.NoOneHitWonders,
313307
Parameters.GlycoSearchParameters.ModPeptidesAreDifferent,
314308
true,

MetaMorpheus/TaskLayer/SearchTask/PostSearchAnalysisTask.cs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -192,21 +192,13 @@ private void ProteinAnalysis()
192192
}
193193
}
194194

195-
var filteredPsmsForParsimony = FilteredPsms.Filter(Parameters.AllSpectralMatches,
196-
commonParams: CommonParameters,
197-
includeDecoys: true,
198-
includeContaminants: true,
199-
includeAmbiguous: false,
200-
includeHighQValuePsms: false);
201-
202195
// run parsimony
203-
ProteinParsimonyResults proteinAnalysisResults = (ProteinParsimonyResults)(new ProteinParsimonyEngine(filteredPsmsForParsimony, Parameters.SearchParameters.ModPeptidesAreDifferent, CommonParameters, this.FileSpecificParameters, new List<string> { Parameters.SearchTaskId }).Run());
196+
ProteinParsimonyResults proteinAnalysisResults = (ProteinParsimonyResults)(new ProteinParsimonyEngine(Parameters.AllSpectralMatches, Parameters.SearchParameters.ModPeptidesAreDifferent, CommonParameters, this.FileSpecificParameters, new List<string> { Parameters.SearchTaskId }).Run());
204197

205198
// score protein groups and calculate FDR
206-
// Pass the FilterType and FilterThreshold from the filtered PSMs to ensure consistent filtering criteria
207199
ProteinScoringAndFdrResults proteinScoringAndFdrResults = (ProteinScoringAndFdrResults)new ProteinScoringAndFdrEngine(
208200
proteinAnalysisResults.ProteinGroups,
209-
filteredPsmsForParsimony,
201+
Parameters.AllSpectralMatches,
210202
Parameters.SearchParameters.NoOneHitWonders,
211203
Parameters.SearchParameters.ModPeptidesAreDifferent,
212204
mergeIndistinguishableProteinGroups: true,
@@ -1032,10 +1024,9 @@ private void WriteProteinResults()
10321024
includeHighQValuePsms: false);
10331025
var subsetProteinGroupsForThisFile = ProteinGroups.Select(p => p.ConstructSubsetProteinGroup(fullFilePath, Parameters.SearchParameters.SilacLabels)).ToList();
10341026

1035-
// Pass the FilterType and FilterThreshold from the filtered PSMs to ensure consistent filtering criteria
10361027
ProteinScoringAndFdrResults subsetProteinScoringAndFdrResults = (ProteinScoringAndFdrResults)new ProteinScoringAndFdrEngine(
10371028
subsetProteinGroupsForThisFile,
1038-
filteredPsmsByFile,
1029+
psmsForThisFile,
10391030
Parameters.SearchParameters.NoOneHitWonders,
10401031
Parameters.SearchParameters.ModPeptidesAreDifferent,
10411032
false,
@@ -1721,4 +1712,4 @@ private void WriteDigestionCountHistogram()
17211712
FinishedWritingFile(countHistogramPath, nestedIds);
17221713
}
17231714
}
1724-
}
1715+
}

0 commit comments

Comments
 (0)