Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3891bcd
[Feature] Configuration for MaxQuant reader
lucas-diedrich Aug 11, 2025
255c978
[Feature] Add post-processing method that acts on streamlined columns…
lucas-diedrich Aug 11, 2025
7c1de7d
[API] Expose MaxQuantPGReader to users
lucas-diedrich Aug 11, 2025
843108a
[Feature] MaxQuant reader with documentation
lucas-diedrich Aug 11, 2025
551f963
[Test] Add integration test for MaxQuantPGReader
lucas-diedrich Aug 11, 2025
fe922c5
[Test] Adjust tests to work with local test data - MQReader
lucas-diedrich Aug 23, 2025
3b79dce
[Tests] Add test data
lucas-diedrich Aug 23, 2025
9f4d3e7
[Fix] Fix typo
lucas-diedrich Aug 14, 2025
521fa13
[Doc] Update docstring, add more explicit parameter explanations
lucas-diedrich Aug 14, 2025
3fd9d98
[Doc] Update docstrings and remove incorrect copy documentation
lucas-diedrich Aug 14, 2025
7717cd2
[Doc] Clarify docstring
lucas-diedrich Aug 14, 2025
31e46e3
[Feature] Spectronaut reader
lucas-diedrich Aug 11, 2025
5aaf207
[Feature] Spectronaut reader config
lucas-diedrich Aug 11, 2025
3cfc4ca
[API] Expose spectronaut reader to users
lucas-diedrich Aug 11, 2025
f28d73e
[Test] Spectronaut integration tests
lucas-diedrich Aug 11, 2025
4573149
[Doc] Add examples
lucas-diedrich Aug 11, 2025
c287cd2
[Fix] Rename pre-configured regex to default
lucas-diedrich Aug 11, 2025
4940a73
[Fix] Fix typos
lucas-diedrich Aug 11, 2025
b5c7a9b
[Feature] Add parquet support. Specifically desirable for spectronaut…
lucas-diedrich Aug 12, 2025
41eef70
[Test] Add integration test for spectronaut.parquet report
lucas-diedrich Aug 12, 2025
207e386
[Doc] Add regular expression documentation
lucas-diedrich Aug 14, 2025
ef71f1a
[Doc] Improve docstring of class initialization
lucas-diedrich Aug 23, 2025
a2427d7
[Feature] Add additional column mapping for parquet report protein gr…
lucas-diedrich Aug 23, 2025
0e13e68
[Test] Refactor tests so that they work with local data
lucas-diedrich Aug 23, 2025
5e9c1c0
[Test-Data] Add test data reference for spectronaut reports
lucas-diedrich Aug 23, 2025
85e05cb
[Feature] Implement fragpipe reader
lucas-diedrich Aug 11, 2025
4a46205
[API] Expose FragPipeReader to users
lucas-diedrich Aug 11, 2025
808547b
[Tests] Add integration test for FragPipeReader
lucas-diedrich Aug 11, 2025
38b995d
[Fix] Fix typo in URL
lucas-diedrich Aug 11, 2025
deccde6
[Test] Update tests to work with local data (FragPipe PG reader)
lucas-diedrich Aug 23, 2025
6cbe7d9
[Test-data] Add test reference data (PG fragpipe)
lucas-diedrich Aug 23, 2025
cc9b861
[Feature] Add MZTab protein group reader
lucas-diedrich Aug 11, 2025
6699fac
[API] Expose MZTab to users
lucas-diedrich Aug 11, 2025
8e0e8c5
[Tests] Add integration tests
lucas-diedrich Aug 11, 2025
2a2848e
[Refactor] Set correct default values
lucas-diedrich Aug 11, 2025
00c72f0
[Tests] Add integration tests
lucas-diedrich Aug 11, 2025
86eeb7a
[Test] Refactor mztab integration test so that it runs with local data
lucas-diedrich Aug 23, 2025
15545aa
[Test-data] Add local test data (mzTAB PG Reader)
lucas-diedrich Aug 23, 2025
0343aeb
[Doc] Add PG-reader API docs
lucas-diedrich Aug 11, 2025
75b5ab1
Merge pull request #334 from MannLabs/pg-reader-9-docs-I
lucas-diedrich Aug 26, 2025
639c81e
Merge pull request #333 from MannLabs/pg-reader-8-mztab
lucas-diedrich Aug 26, 2025
778dac6
Merge pull request #332 from MannLabs/pg-reader-7-msfragger
lucas-diedrich Aug 26, 2025
e9485c1
Merge pull request #331 from MannLabs/pg-reader-6-spectronaut
lucas-diedrich Aug 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions alphabase/constants/const_files/pg_reader.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,67 @@ alphapept:
raw: "^.*(?<!_LFQ)$"
# Match "_LFQ" at the end of the name
lfq: "_LFQ$"


# Published June 27, 2024
# https://cox-labs.github.io/coxdocs/output_tables.html#protein-groups
maxquant:
reader_type: "maxquant"
column_mapping:
"proteins": "Protein names"
"uniprot_ids": "Protein IDs"
"genes": "Gene names"
"is_decoy": "Reverse"
measurement_regex:
# Match "Intensity <sample>"
# Do not match "Intensity" (sum across all samples)
# Do not match "Intensity L <sample>" (or M/H), from TMT experiments
"raw": "^Intensity(?!\\s[LHM]\\s).+$" # Raw intensity-based quantification
"lfq": "^LFQ intensity(?!\\s[LHM]\\s).+$" # LFQ intensity-based quantification
"ibaq": "^iBAQ(?!\\s[LHM]\\s).+$" # Intensity-Based Absolute Quantification


# Based on Spectronaut 20.0 Run Pivot Report (Page 80/81)
# https://biognosys.com/content/uploads/2025/06/Spectronaut-20-Manual.pdf
spectronaut:
reader_type: "spectronaut"
column_mapping:
"proteins": ["PG.ProteinNames", "PG_ProteinGroups"]
"genes": "PG.Genes"
"uniprot_ids": "PG.UniProtIds"
measurement_regex:
# Spectronaut marks sample columns with "[number]..."
# At the beginning (^) of the string, match open square bracket "\\[", any number of digits "[0-9]+", closed square bracket "\\]"
# Square brackets are special characters -> need to be escaped with "\[". The backslash itself needs to be escaped -> "\\["
"default": "^\\[[0-9]+\\]"


# https://fragpipe.nesvilab.org/docs/tutorial_fragpipe_outputs.html#proteintsv
fragpipe:
reader_type: "fragpipe"
column_mapping:
"proteins": "Entry Name"
"uniprot_ids": "Protein ID"
"genes": "Gene Names"
"description": "Description"
measurement_regex:
"raw": "Intensity$"
"razor": "Razor Intensity$"
"unique": "Unique Intensity$"
"total": "Total Intensity$"
"lfq": "MaxLFQ Intensity$"
"lfq_unique": "MaxLFQ Unique Intensity$"
"lfq_total": "MaxLFQ Total Intensity$"


# mzTab
# version 2.0.0 (2019-03)
mztab:
reader_type: "mztab"
column_mapping:
"uniprot_ids": "accession"
"description": "description"
"source_db": "database"
measurement_regex:
"assay": "^protein_abundance_assay\\[[0-9]+\\]" # The protein's abundance as measured in the given assay through whatever technique was employed
"study_variable": "^protein_abundance_study_variable\\[[0-9]+\\]" # The protein's abundance as measured in the given study variable (condition) through whatever technique was employed
8 changes: 8 additions & 0 deletions alphabase/pg_reader/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
from .alphadia_pg_reader import AlphaDiaPGReader
from .alphapept_pg_reader import AlphaPeptPGReader
from .diann_pg_reader import DiannPGReader
from .fragpipe_pg_reader import FragPipePGReader
from .maxquant_pg_reader import MaxQuantPGReader
from .mztab_pg_reader import MZTabPGReader
from .pg_reader import pg_reader_provider
from .spectronaut_reader import SpectronautPGReader

__all__ = [
"pg_reader_provider",
"AlphaDiaPGReader",
"DiannPGReader",
"AlphaPeptPGReader",
"MaxQuantPGReader",
"SpectronautPGReader",
"FragPipePGReader",
"MZTabPGReader",
]
2 changes: 1 addition & 1 deletion alphabase/pg_reader/alphapept_pg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def __init__(
Parameters
----------
column_mapping
Dictionary mapping alphabase column names (keys) to AlphaPep column names (values).
Dictionary mapping alphabase column names (keys) to AlphaPept column names (values).
If `None`, uses default mapping from configuration file.
measurement_regex
Pattern to select quantity columns
Expand Down
47 changes: 47 additions & 0 deletions alphabase/pg_reader/fragpipe_pg_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""FragPipe protein group reader."""

from typing import Literal, Optional, Union

from .pg_reader import PGReaderBase, pg_reader_provider


class FragPipePGReader(PGReaderBase):
"""Reader for `protein.tsv` reports from FragPipe.

Example:
-------
Per default, the reader will return the raw intensities from the `razor` method. Additional protein features are stored
in the dataframe index, samples are stored as columns.

.. code-block:: python

# Get raw intensities
reader = FragPipePGReader()
results = reader.import_file(download_path)


References:
----------
- FragPipe Documentation https://fragpipe.nesvilab.org/docs/tutorial_fragpipe_outputs.html#proteintsv

"""

_reader_type: str = "fragpipe"

def __init__( # noqa: D107 inherited from base class
self,
*,
column_mapping: Optional[dict[str, str]] = None,
measurement_regex: Union[
Literal[
"raw", "razor", "unique", "total", "lfq", "lfq_unique", "lfq_total"
],
None,
] = "razor",
):
super().__init__(
column_mapping=column_mapping, measurement_regex=measurement_regex
)


pg_reader_provider.register_reader("fragpipe", reader_class=FragPipePGReader)
130 changes: 130 additions & 0 deletions alphabase/pg_reader/maxquant_pg_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""MaxQuant Protein Group Reader."""

from typing import Literal, Optional, Union

import pandas as pd

from .keys import PGCols
from .pg_reader import PGReaderBase, pg_reader_provider


class MaxQuantPGReader(PGReaderBase):
r"""Reader for protein group matrices from the MaxQuant search engine.

By default, the reader will read raw protein intensities from the protein group matrix. By passing
a suitable regular expression, it is also possible to extract LFQ

Examples
--------
Get example data

.. code-block:: python

import os
Comment thread
lucas-diedrich marked this conversation as resolved.
import tempfile
from alphabase.tools.data_downloader import DataShareDownloader
from alphabase.pg_reader import MaxQuantPGReader


# Download to temporary directory
URL = "https://datashare.biochem.mpg.de/s/KvToteOu0zzH17C"
download_dir = tempfile.mkdtemp()

download_path = DataShareDownloader(url=URL, output_dir=download_dir).download()


Per default, the reader will return the raw intensities. Additional protein features are stored
in the dataframe index, samples are stored as columns.

.. code-block:: python

# Get raw intensities
reader = MaxQuantPGReader()
results = reader.import_file(download_path)
results.index.names
> FrozenList(['proteins', 'uniprot_ids', 'genes', 'is_decoy'])
results.columns
> Index([...], dtype='object', length=312)

You can get other intensity types by passing a specific pattern to the `measurment_regex` parameter during class initialization.
To checkout all preconfigured regular expressions that enable you to retrieve different intensity modalities,
use the `get_preconfigured_regex` method:

.. code-block:: python

MaxQuantPGReader.get_preconfigured_regex()
> {
'raw': '^Intensity(?!\\s[LHM]\\s).+$',
'lfq': '^LFQ intensity(?!\\s[LHM]\\s).+$',
'ibaq': '^iBAQ(?!\\s[LHM]\\s).+$'
}

You can also pass a custom regular expression, e.g. to retrieve specific channels in TMT experiments

.. code-block:: python

# Match "Intensity H+ <sample>"
reader = MaxQuantPGReader(measurement_regex="^Intensity H .+")


References
----------
- MaxQuant Documentation (Cox Lab, 2024-06-27): https://cox-labs.github.io/coxdocs/output_tables.html#protein-groups,
(last viewed 2025-08)

"""

_reader_type = "maxquant"

def __init__(
self,
*,
column_mapping: Optional[dict[str, str]] = None,
measurement_regex: Union[str, Literal["raw", "lfq", "ibaq"], None] = "raw", # noqa: PYI051 raw and lfq are special cases and not equivalent to string
):
"""Initialize MaxQuant protein group matrix reader.

Parameters
----------
column_mapping
Dictionary mapping alphabase column names (keys) to MaxQuant column names (values).
If `None`, uses default mapping from configuration file.
measurement_regex
Pattern to select quantity columns

- "raw" (default): Raw intensities
- "lfq": LFQ-corrected intensities
- "ibaq": Intensity-Based Absolute Quantification-corrected intensities
- custom: Any valid regular expression

See class documentation for usage examples and `get_preconfigured_regex()` for available patterns.

"""
super().__init__(
column_mapping=column_mapping, measurement_regex=measurement_regex
)

def _post_process(self, df: pd.DataFrame) -> pd.DataFrame:
"""Process MaxQuant protein group table after standardization.

Convert MaxQuant-specific decoy indicator (+) to standardized boolean series.

Notes
-----
MaxQuant marks peptides/proteins that were found to be part of a protein derived from the reversed part of the decoy database
Comment thread
lucas-diedrich marked this conversation as resolved.
with +. These should be removed for further data analysis.

References
----------
https://cox-labs.github.io/coxdocs/output_tables.html#protein-groups (Status: 2025-08)

"""
# Convert `+` indicator to boolean
if PGCols.DECOY_INDICATOR in df.columns:
df[PGCols.DECOY_INDICATOR] = df[PGCols.DECOY_INDICATOR].apply(
lambda x: x == "+"
)
return df


pg_reader_provider.register_reader("maxquant", reader_class=MaxQuantPGReader)
116 changes: 116 additions & 0 deletions alphabase/pg_reader/mztab_pg_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""FragPipe protein group reader."""

from pathlib import Path
from typing import Literal, Optional, Union

import pandas as pd

from .pg_reader import PGReaderBase, pg_reader_provider


class MZTabPGReader(PGReaderBase):
"""Reader for MZTab search engine output.

MZTab is a standardized tab-delimited format for reporting proteomics and metabolomics results.
The format organizes data into distinct sections: metadata (MTD), protein groups (PRH/PRT),
peptides (PEH/PEP), PSMs (PSH/PSM), and small molecules (SMH/SML), with each section identified
by specific three-letter prefixes. This reader extracts protein-level quantification data from
the PRT lines, which contain protein abundances across samples or study variables.

Example:
-------
Per default, the reader will return the raw intensities from the `razor` method. Additional protein features are stored
in the dataframe index, samples are stored as columns.

.. code-block:: python

from alphabase.pg_reader import MZTabPGReader

# Get raw intensities
reader = MZTabPGReader()
results = reader.import_file(path)


References:
----------
- Griss, J. et al. The mzTab Data Exchange Format: Communicating Mass-spectrometry-based Proteomics and Metabolomics Experimental Results to a Wider Audience*. Molecular & Cellular Proteomics 13, 2765-2775 (2014).
- Official MZTab Repository: https://github.com/HUPO-PSI/mzTab.git
- Official documentation: https://hupo-psi.github.io/mzTab/

"""

_reader_type: str = "mztab"

_PROTEIN_ROW_INDICATOR: str = "PRT"
_PROTEIN_HEADER_INDICATOR: str = "PRH"
_SEPARATOR: str = "\t"

def __init__( # noqa: D107 inherited from base class
self,
*,
column_mapping: Optional[dict[str, str]] = None,
measurement_regex: Union[
str, Literal["assay", "study_variable"], None # noqa: PYI051 raw and lfq are special cases and not equivalent to string
] = "assay",
):
super().__init__(
column_mapping=column_mapping, measurement_regex=measurement_regex
)

def _load_file(self, file_path: str) -> pd.DataFrame:
"""Load MZTab file and extract protein data section.

Parameters
----------
file_path : str
Path to MZTab file

Returns
-------
pd.DataFrame
DataFrame containing protein data from MZTab file

Notes
-----
Protein lines are indicated with a leading `PRT`. The protein metadata header is
indicated with a leading `PRH`. The file is tab separated.

Raises
------
ValueError
If no protein data or metadata is found in the file

"""
file_path = Path(file_path)
protein_header = None
protein_rows = []

with file_path.open() as f:
for line in f:
line_stripped = line.strip()

if line_stripped.startswith(self._PROTEIN_HEADER_INDICATOR):
# Protein header line - remove 'PRH' prefix and parse columns
header_content = line_stripped[3:].strip()
protein_header = header_content.split(self._SEPARATOR)

elif line_stripped.startswith(self._PROTEIN_ROW_INDICATOR):
# Protein data line - remove 'PRT' prefix and parse data
row_content = line_stripped[3:].strip()
protein_rows.append(row_content.split(self._SEPARATOR))

# Validate that we found protein data
if protein_header is None:
raise ValueError(
f"No protein header ({self._PROTEIN_HEADER_INDICATOR}) found in MZTab file"
)

if not protein_rows:
raise ValueError(
f"No protein data rows ({self._PROTEIN_ROW_INDICATOR}) found in MZTab file"
)

return pd.DataFrame(protein_rows, columns=protein_header)


pg_reader_provider.register_reader("mztab", reader_class=MZTabPGReader)
Loading
Loading