Skip to content

Commit 8af0bb4

Browse files
committed
Merge branch 'main' into add_downloads_to_windows_installer
2 parents 2403a66 + b62a863 commit 8af0bb4

19 files changed

Lines changed: 1093 additions & 284 deletions

DEVELOPERS.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# For Developers: Modifying AlphaQuant
2+
3+
AlphaQuant is designed with modularity in mind to allow practitioners to introduce alternative numerical methods for each module. The codebase follows clear interfaces that make it straightforward to extend or replace statistical methods at different levels of the analysis pipeline.
4+
5+
## ⚠️ Important: Benchmarking and Validation
6+
7+
**Any changes to statistical methods should be thoroughly benchmarked and fine-tuned before use in production analyses.** The default methods in AlphaQuant have been extensively tested and validated on diverse proteomics datasets. When implementing alternative approaches, ensure you carry out appropriate benchmarking using ground truth datasets (e.g., spike-in experiments, mixed-species samples) and evaluate key performance metrics (sensitivity, specificity, false discovery rates, reproducibility).
8+
9+
10+
## 1. Ion-Level Statistical Testing
11+
12+
**Where to modify:** [`alphaquant/diffquant/diff_analysis.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py)
13+
14+
**How it works:** Each ion (fragment, peptide, etc.) is tested independently for differential expression. The test produces three key outputs: `p_val` (p-value), `fc` (log2 fold change), and `z_val` (z-score for aggregation).
15+
16+
**Main class:**
17+
- [**`DifferentialIon`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L10) - The default method that uses intensity-dependent empirical background distributions to compute p-values and z-scores. It accounts for technical variation by comparing observed fold changes against distributions derived from similarly abundant ions in the dataset. The core statistical logic is in the [`_calc_diffreg_peptide()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L46) method.
18+
19+
**How to extend:** We've included [`DifferentialIonTTest`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L99) in the same file as example code demonstrating how to implement alternative tests. This variant uses Welch's t-test with robust variance estimation. Note that this example has not been extensively benchmarked and is included for educational purposes to demonstrate the interface.
20+
21+
1. Create a new class (e.g., `DifferentialIonMyMethod`) with the same interface:
22+
- `__init__()` should accept `(noNanvals_from, noNanvals_to, ...)` and any method-specific parameters
23+
- Set attributes: `name`, `p_val`, `fc`, `z_val`, `usable`
24+
2. Implement your statistical test in a method (e.g., `_calc_mymethod()`)
25+
3. Modify [`alphaquant/diffquant/condpair_analysis.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/condpair_analysis.py#L67-L70) (lines 67-70) to instantiate your class
26+
4. Optionally, add a parameter to `run_pipeline()` to select between methods
27+
28+
The key requirement is that your class must output `p_val`, `fc`, and `z_val` for each ion—these are used by the tree aggregation framework.
29+
30+
## 2. Tree-Based Ion Propagation
31+
32+
**Where to modify:** [`alphaquant/cluster/cluster_utils.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py) and [`alphaquant/cluster/cluster_ions.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_ions.py)
33+
34+
**How it works:** Statistics from child nodes (e.g., fragments) are aggregated to parent nodes (e.g., peptides → proteins) in a hierarchical tree. Z-values are combined using Stouffer's method, and fold changes are summarized using medians.
35+
36+
**Key functions:**
37+
- [**`aggregate_node_properties()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L22) - The core function that propagates statistics up the tree. It combines z-values, fold changes, and quality metrics from children to parents.
38+
- [**`sum_and_re_scale_zvalues()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L266) - Implements Stouffer's Z-score method: sums z-values and divides by sqrt(n), then rescales to maintain standard normal distribution.
39+
- [**`transform_znormed_to_pval()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L289) - Converts aggregated z-scores back to two-sided p-values.
40+
41+
**How to extend:** If you want to use different aggregation methods:
42+
1. Modify [`sum_and_re_scale_zvalues()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L266) to implement your preferred meta-analysis method (e.g., Fisher's method, weighted Z-scores, etc.)
43+
2. If your method changes the distribution, update [`transform_znormed_to_pval()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L289) accordingly
44+
3. For fold-change aggregation, modify [line 67 in `aggregate_node_properties()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L67) where `node.fc = np.median(fcs)` is set
45+
46+
The tree traversal itself is in [`cluster_ions.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_ions.py):
47+
- [**`cluster_along_specified_levels()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_ions.py#L117) - Iterates through tree levels bottom-to-top
48+
- [**`get_scored_clusterselected_ions()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_ions.py#L31) - Entry point for the hierarchical workflow
49+
50+
## 3. Multiple Testing Correction
51+
52+
**Where to modify:** [`alphaquant/tables/diffquant_table.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/diffquant_table.py) and [`alphaquant/tables/proteoformtable.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/proteoformtable.py)
53+
54+
**How it works:** FDR correction is applied separately to different result tables during output generation. The method outputs p-values in all tables, so you can always recalculate q-values from the output files.
55+
56+
**Key functions:**
57+
- **Protein results** ([`alphaquant/tables/diffquant_table.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/diffquant_table.py)):
58+
- [`_add_fdr_fc_based_set()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/diffquant_table.py#L107) - Applies Benjamini-Hochberg to intensity-based proteins
59+
- [`_add_fdr_counting_based_set()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/diffquant_table.py#L124) - Applies adjusted Benjamini-Hochberg to proteins detected only via missing values
60+
61+
- **Proteoform results** ([`alphaquant/tables/proteoformtable.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/proteoformtable.py)):
62+
- [`_annotate_fdr_column()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/tables/proteoformtable.py#L59) - Applies Benjamini-Hochberg to test if alternative proteoforms differ from the reference
63+
64+
**How to extend:**
65+
1. Modify the relevant function to use a different method (e.g., Bonferroni, Storey's q-value, etc.)
66+
2. Replace the `mt.multipletests(..., method='fdr_bh', ...)` call with your preferred correction
67+
3. Alternatively, use the p-values from output tables and apply your own correction externally
68+
69+
## 4. Outlier Robustness
70+
71+
**Where to modify:** [`alphaquant/diffquant/diff_analysis.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py) and [`alphaquant/cluster/cluster_utils.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py)
72+
73+
**How it works:** AlphaQuant applies outlier correction at two levels to make results robust to technical variation and biological heterogeneity.
74+
75+
**Key functions:**
76+
- [**`calc_outlier_scaling_factor()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L202) (in [`diff_analysis.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py)) - Compares between-replicate variance to expected technical variance and inflates estimates when replicates show unusual variability
77+
- [**`remove_outlier_fragion_childs()`**](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L222) (in [`cluster_utils.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py)) - Filters extreme fragments before aggregating to peptides (keeps the 5 most central fragments when >4 are available)
78+
79+
**How to extend:**
80+
1. Modify the scaling logic in [`calc_outlier_scaling_factor()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L202) to use different robust estimators
81+
2. Adjust [`remove_outlier_fragion_childs()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/cluster/cluster_utils.py#L222) to change how many fragments are retained or which criteria are used for selection
82+
3. Set `outlier_correction=False` in `run_pipeline()` to disable this feature entirely
83+
84+
## 5. Main Workflow Orchestration
85+
86+
**Where to modify:** [`alphaquant/diffquant/condpair_analysis.py`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/condpair_analysis.py)
87+
88+
**How it works:** The [`analyze_condpair()`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/condpair_analysis.py#L27) function coordinates the complete pipeline for comparing two conditions.
89+
90+
**Pipeline steps:**
91+
1. Load and filter data for the two conditions
92+
2. Perform normalization (within and between conditions)
93+
3. Create empirical background distributions
94+
4. Compute ion-level differential statistics ([`DifferentialIon`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L10) or [`DifferentialIonTTest`](https://github.com/MannLabs/alphaquant/blob/main/alphaquant/diffquant/diff_analysis.py#L99))
95+
5. Build hierarchical trees and perform clustering to identify proteoforms
96+
6. Apply machine learning quality scoring (if enabled)
97+
7. Filter outlier peptides (if enabled)
98+
8. Generate output tables with FDR correction
99+
9. Create visualization plots
100+
101+
**How to extend:** This file shows how all components connect. To add custom preprocessing, normalization, or post-processing steps, modify this function or create a wrapper that calls it with modified data.
102+
103+
---
104+
105+
## Additional Resources
106+
107+
For general contribution guidelines, code style, and how to submit pull requests, please see [CONTRIBUTING.md](CONTRIBUTING.md).
108+
109+
For questions or discussions about extending AlphaQuant, please use the [GitHub Discussions](https://github.com/MannLabs/alphaquant/discussions) forum.
110+

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ AlphaQuant is designed for proteomics researchers analyzing DDA or DIA experimen
4141
* [**Python and jupyter notebooks**](#python-and-jupyter-notebooks)
4242
* [**Troubleshooting**](#troubleshooting)
4343
* [**Citations**](#citations)
44+
* [**For Developers: Modifying AlphaQuant**](#for-developers-modifying-alphaquant)
4445
* [**How to contribute**](#how-to-contribute)
4546
* [**License**](#license)
4647
* [**Changelog**](#changelog)
@@ -293,6 +294,20 @@ A manuscript has been submitted to bioRxiv:
293294
> Constantin Ammar, Marvin Thielert, Caroline A M Weiss, Edwin H Rodriguez, Maximilian T Strauss, Florian A Rosenberger, Wen-Feng Zeng, Matthias Mann
294295
> bioRxiv 2025.03.06.641844; doi: https://doi.org/10.1101/2025.03.06.641844
295296
297+
---
298+
## For Developers: Modifying AlphaQuant
299+
300+
AlphaQuant is designed with modularity in mind. If you want to implement alternative statistical methods, modify the tree-based propagation, or adjust multiple testing correction approaches, we provide clear interfaces at each level of the analysis pipeline.
301+
302+
For detailed documentation on how to extend or replace:
303+
- Ion-level statistical testing methods
304+
- Tree-based aggregation and z-value propagation
305+
- Multiple testing correction procedures
306+
- Outlier robustness filtering
307+
- Main workflow orchestration
308+
309+
Please see **[DEVELOPERS.md](DEVELOPERS.md)** for comprehensive guidance with code examples.
310+
296311
---
297312
## How to contribute
298313

alphaquant/cluster/cluster_ions.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,30 @@
2828

2929

3030

31-
def get_scored_clusterselected_ions(gene_name, diffions, normed_c1, normed_c2, ion2diffDist, p2z, deedpair2doublediffdist, pval_threshold_basis, fcfc_threshold, take_median_ion, fcdiff_cutoff_clustermerge):
31+
def get_scored_clusterselected_ions(gene_name, diffions, normed_c1, normed_c2, ion2diffDist, p2z, deedpair2doublediffdist, pval_threshold_basis, fcfc_threshold, take_median_ion, fcdiff_cutoff_clustermerge, fragment_outlier_filtering=True):
32+
"""Main entry point for hierarchical clustering and tree-based quantification of a protein.
33+
34+
This function creates a hierarchical tree structure from fragment ions up to the protein level
35+
(fragments → peptides → modified peptides → unmodified peptides → protein), performs statistical
36+
clustering at each level to identify proteoforms, and computes aggregated statistics.
37+
38+
Args:
39+
gene_name: Protein/gene identifier
40+
diffions: List of DifferentialIon objects for all ions belonging to this protein
41+
normed_c1: ConditionBackgrounds object for condition 1
42+
normed_c2: ConditionBackgrounds object for condition 2
43+
ion2diffDist: Dictionary mapping ion pairs to differential background distributions
44+
p2z: Cache dictionary for p-value to z-value conversions
45+
deedpair2doublediffdist: Cache for double-differential distributions used in clustering
46+
pval_threshold_basis: P-value threshold for determining if ions differ significantly
47+
fcfc_threshold: Fold-change difference threshold for clustering
48+
take_median_ion: If True, use median-centered ions for clustering
49+
fcdiff_cutoff_clustermerge: Fold-change threshold for merging similar clusters
50+
fragment_outlier_filtering: Whether to filter outlier fragments when aggregating to peptides
51+
52+
Returns:
53+
anytree.Node: Root node of the hierarchical tree containing all statistics and clustering results
54+
"""
3255
#typefilter = TypeFilter('successive')
3356

3457
global FCDIFF_CUTOFF_CLUSTERMERGE
@@ -40,7 +63,7 @@ def get_scored_clusterselected_ions(gene_name, diffions, normed_c1, normed_c2, i
4063
root_node = create_hierarchical_ion_grouping(gene_name, diffions)
4164
add_reduced_names_to_root(root_node)
4265
#LOGGER.info(anytree.RenderTree(root_node))
43-
root_node_clust = cluster_along_specified_levels(root_node, name2diffion, normed_c1, normed_c2, ion2diffDist, p2z, deedpair2doublediffdist, pval_threshold_basis, fcfc_threshold, take_median_ion)
66+
root_node_clust = cluster_along_specified_levels(root_node, name2diffion, normed_c1, normed_c2, ion2diffDist, p2z, deedpair2doublediffdist, pval_threshold_basis, fcfc_threshold, take_median_ion, fragment_outlier_filtering)
4467

4568
level_sorted_nodes = [[node for node in children] for children in anytree.ZigZagGroupIter(root_node_clust)]
4669
level_sorted_nodes.reverse() #the base nodes are first
@@ -91,7 +114,31 @@ def add_reduced_names_to_root(node):
91114

92115

93116
import pandas as pd
94-
def cluster_along_specified_levels(root_node, ionname2diffion, normed_c1, normed_c2, ion2diffDist, p2z, deedpair2doublediffdist, pval_threshold_basis, fcfc_threshold, take_median_ion):#~60% of overall runtime
117+
def cluster_along_specified_levels(root_node, ionname2diffion, normed_c1, normed_c2, ion2diffDist, p2z, deedpair2doublediffdist, pval_threshold_basis, fcfc_threshold, take_median_ion, fragment_outlier_filtering=True):#~60% of overall runtime
118+
"""Performs hierarchical clustering at each level of the tree from bottom to top.
119+
120+
Starting from base ions (fragments/MS1), this function iterates through each level
121+
of the tree hierarchy and performs statistical clustering to identify groups of ions
122+
with similar quantitative behavior (proteoforms). At each level, ions are tested
123+
pairwise for consistent fold-change differences, clustered hierarchically, and
124+
statistics are aggregated to parent nodes.
125+
126+
Args:
127+
root_node: Root of the hierarchical tree (protein level)
128+
ionname2diffion: Dictionary mapping ion names to DifferentialIon objects
129+
normed_c1: ConditionBackgrounds for condition 1
130+
normed_c2: ConditionBackgrounds for condition 2
131+
ion2diffDist: Dictionary of differential background distributions
132+
p2z: Cache for p-value to z-value conversions
133+
deedpair2doublediffdist: Cache for double-differential distributions
134+
pval_threshold_basis: P-value threshold for clustering decisions
135+
fcfc_threshold: Fold-change threshold for clustering
136+
take_median_ion: Whether to use median-centered ions
137+
fragment_outlier_filtering: Whether to filter fragment outliers
138+
139+
Returns:
140+
anytree.Node: The root node with all clustering annotations and aggregated statistics
141+
"""
95142
#typefilter object specifies filtering and clustering of the nodes
96143
aqcluster_utils.assign_properties_to_base_ions(root_node, ionname2diffion, normed_c1, normed_c2)
97144

@@ -125,7 +172,7 @@ def cluster_along_specified_levels(root_node, ionname2diffion, normed_c1, normed
125172
aqcluster_utils.assign_clusterstats_to_type_node(type_node, childnode2clust)
126173
aqcluster_utils.annotate_mainclust_leaves(childnode2clust)
127174
aqcluster_utils.assign_cluster_number(type_node, childnode2clust)
128-
aqcluster_utils.aggregate_node_properties(type_node,only_use_mainclust=True, peptide_outlier_filtering=False)
175+
aqcluster_utils.aggregate_node_properties(type_node,only_use_mainclust=True, peptide_outlier_filtering=False, fragment_outlier_filtering=fragment_outlier_filtering)
129176

130177
return root_node
131178

0 commit comments

Comments
 (0)