Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
700b156
[Feature] Add alphapept protein group matrix reader
lucas-diedrich Jul 31, 2025
0d1e91e
[Doc] Add explanatory comments for magic values
lucas-diedrich Jul 31, 2025
cfc69d3
[Feature] Add configuration of alphapept protein group reader
lucas-diedrich Jul 31, 2025
79087aa
[Feature] Add keys for alphapept reader
lucas-diedrich Jul 31, 2025
754453e
[Fix] Bug fix. Keep dataframe index if no features are specified and …
lucas-diedrich Jul 31, 2025
38d4c7d
[Doc] Add explanatory comment
lucas-diedrich Jul 31, 2025
7e2a310
[Fix] Fix type hint
lucas-diedrich Jul 31, 2025
7ab7adf
[Tests] Unittests for the index parsing function of the alphapept reader
lucas-diedrich Aug 1, 2025
6f71280
[Fix] Bug fixes for the parsing of alphapept indices
lucas-diedrich Aug 1, 2025
abaacd7
[Doc] Fix examples
lucas-diedrich Aug 1, 2025
3ae462e
[API] Expose alphapept reader
lucas-diedrich Aug 1, 2025
034047c
[Tests] Add integration tests for alphapet
lucas-diedrich Aug 1, 2025
80de4c9
[Doc] Add examples
lucas-diedrich Aug 1, 2025
6d9cc76
[Tests] Use public AlphaPeptPGReader instead of module level import
lucas-diedrich Aug 1, 2025
5b70307
[Typing] Add type hints to tests
lucas-diedrich Aug 1, 2025
65445cb
[Doc] Fix incorrect reader name
lucas-diedrich Aug 1, 2025
4d8cc57
[Doc] Fix typo
lucas-diedrich Aug 1, 2025
bb300b1
[Doc] Update docstring
lucas-diedrich Aug 1, 2025
19d0ca4
[Fix] Incorporate suggestions from Copilot review
lucas-diedrich Aug 1, 2025
6c59695
[Doc] Fix typos in docstrings
lucas-diedrich Aug 3, 2025
be74704
[Refactor] Refactor so that the method does not depend on custom load…
lucas-diedrich Aug 3, 2025
0c01e84
[Test] Add tests for alphapept hdf files
lucas-diedrich Aug 3, 2025
000b094
[Feature] Implement pre-configured regular expressions
lucas-diedrich Aug 11, 2025
b67b3f6
[Refactor] Restructure pg_reader_yaml to accomodate pre-configured re…
lucas-diedrich Aug 11, 2025
5c895b4
[Doc] Expose pre-configured keys in class init as type hints. Improve…
lucas-diedrich Aug 11, 2025
5031247
[Test] Add tests for _get_measurement_regex
lucas-diedrich Aug 11, 2025
a738164
[Test] Adjust test based on new config structure
lucas-diedrich Aug 11, 2025
a6533c1
[Tests] Remove unnecessary tests of reader_provider import
lucas-diedrich Aug 11, 2025
9be20bb
[Doc] Fix docstring
lucas-diedrich Aug 11, 2025
39ab064
[Requirements] Add pytables dependency which is necessary to read hdf…
lucas-diedrich Aug 11, 2025
f083adb
[Tests] Add exact comparison for integration test
lucas-diedrich Aug 14, 2025
ec85043
[Doc] Clean up comments in config file
lucas-diedrich Aug 14, 2025
182a74d
[Config] Add column mapping explicitly to make explicity which index …
lucas-diedrich Aug 14, 2025
d4d16ea
[Doc] Cleaned up docstring
lucas-diedrich Aug 14, 2025
48c227a
[Fix] Fix missing import
lucas-diedrich Aug 21, 2025
6162c0a
[Fix] Refactor conftests - skip gracefully if connection times out, a…
lucas-diedrich Aug 21, 2025
60a835e
[Test] Make conftests only depend on local data if possible.
lucas-diedrich Aug 22, 2025
f63a0e3
[Test] Adjust expected shapes for alphapeptreader based on new testd …
lucas-diedrich Aug 22, 2025
0422980
[Tests] Add test data
lucas-diedrich Aug 22, 2025
6fe8305
[Refactor] Move all constants to class constants
lucas-diedrich Aug 22, 2025
27c1e37
[Nit] Warn if unexpected format is encountered
lucas-diedrich Aug 22, 2025
798ac79
[Fix] Remove unnecessary helper function
lucas-diedrich Aug 22, 2025
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
0be157e
Merge pull request #330 from MannLabs/pg-reader-5-maxquant
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
84 changes: 84 additions & 0 deletions alphabase/constants/const_files/pg_reader.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,87 @@ diann:
"peptide_count": "N.Sequences" # DIANN 2.1
"proteotypic_peptide_count": "N.Proteotypic.Sequences" # DIANN 2.1
measurement_regex: null


# Based on alphapept 0.5.6
alphapept:
reader_type: "alphapept"
# Mapping is performed by _pre_process function, so the columns are already standardized
column_mapping:
"proteins": "proteins"
"uniprot_ids": "uniprot_ids"
"ensembl_ids": "ensembl_ids"
"source_db": "source_db"
"is_decoy": "is_decoy"
# Match raw intensities per default.
# Match everything at the start but not a trailing LFQ
measurement_regex:
# Match anything at the start "^.*" but assure that it does not "(?<!...)" match "_LFQ" the end ($)
# _LFQ is the marker for LFQ runs in alphapept
raw: "^.*(?<!_LFQ)$"
Comment thread
lucas-diedrich marked this conversation as resolved.
# 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
16 changes: 15 additions & 1 deletion alphabase/pg_reader/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +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"]
__all__ = [
"pg_reader_provider",
"AlphaDiaPGReader",
"DiannPGReader",
"AlphaPeptPGReader",
"MaxQuantPGReader",
"SpectronautPGReader",
"FragPipePGReader",
"MZTabPGReader",
]
289 changes: 289 additions & 0 deletions alphabase/pg_reader/alphapept_pg_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
"""AlphaPept protein group reader."""

import re
import warnings
from typing import Any, Literal, Optional, Union

import pandas as pd

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


class AlphaPeptPGReader(PGReaderBase):
"""Reader for protein group matrices from the alphapept search engine.

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

Notes:
-----
AlphaPept protein group matrices contain both raw intensities and LFQ-corrected intensities.
The LFQ-corrected intensities are marked by an `_LFQ` suffix.

Example:
-------
Get example data

.. code-block:: python

import os
import tempfile
from alphabase.tools.data_downloader import DataShareDownloader
from alphabase.pg_reader import AlphaPeptPGReader


# Download to temporary directory
URL = "https://datashare.biochem.mpg.de/s/6G6KHJqwcRPQiOO"
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 = AlphaPeptPGReader()
results = reader.import_file(download_path)
results.index.names
> FrozenList(['proteins', 'uniprot_ids', 'ensembl_ids', 'source_db', 'is_decoy'])
results.columns
> Index(['A', 'B'], dtype='object')

To read the LFQ values, pass the pre-configured key `lfq` to the reader, which represents a regular expression
that automatically extracts the `LFQ` columns from the protein group table.

.. code-block:: python

# Get raw intensities
reader = AlphaPeptPGReader(measurement_regex="lfq")
results = reader.import_file(download_path)
results.index.names
> FrozenList(['proteins', 'uniprot_ids', 'ensembl_ids', 'source_db', 'is_decoy'])
results.columns
> Index(['A_LFQ', 'B_LFQ'], dtype='object')
Comment thread
lucas-diedrich marked this conversation as resolved.


To check out all preconfigured regular expressions, use the `get_preconfigured_regex` method:

.. code-block:: python

AlphaPeptPGReader.get_preconfigured_regex()
> {'raw': '^.*(?<!_LFQ)$', 'lfq': '_LFQ$'}

"""

_reader_type: str = "alphapept"

# Report file settings (delimiter + index column)
_FILE_DELIMITER: str = ","
# alphapept does not set a name for the feature column, i.e. it is set to the pandas default
_INDEX_COL: str = "Unnamed: 0"

# Default delimiter in fasta file headers
_ENTRY_DELIMITER: str = "|"

# Feature settings
# Decoys are prefixed with REV__ in alphapept
_DECOY_REGEX: str = "^REV__"
Comment thread
lucas-diedrich marked this conversation as resolved.
# Ensembl IDs are identified with a ENSEMBL prefix
_ENSEMBL_REGEX: str = "^ENSEMBL:"
_ENSEMBL_NAME: str = "ENSEMBL"

# The expected length of fasta headers is 3 (sp|Uniprot ID|Uniprot Name)
_FASTA_HEADER_DEFAULT_LENGTH: int = 3
_NA_STR: str = "na"
_PG_DELIMITER: str = ";"

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

Parameters
----------
column_mapping
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

- "raw" (default): Raw intensities (excludes _LFQ columns)
- "lfq": LFQ-corrected intensities (_LFQ suffix)
- str: Custom regular expression pattern
- None: All quantity columns

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

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

def _pre_process(self, df: pd.DataFrame) -> pd.DataFrame:
"""Preprocess of alphapept protein group report and return modified copy of the dataframe.

Processes feature index to a parsed, streamlined version.

Parameters
----------
df
alphapept protein group report.

Returns
-------
:class:`pd.DataFrame`
Modified copy of protein group report with parsed index. The index contains the levels
- proteins: str
- uniprot_ids: str
- ensembl_ids: str
- source_db: str
- is_decoy: bool

"""
df = df.copy()

# alphapept does not set a name for the feature column
# load it as regular column and set it to index afterwards
df = df.set_index(self._INDEX_COL)

# Parse index
parsed_index: list[dict[str, str]] = list(
df.index.map(lambda idx: self._parse_alphapept_index(idx))
)

# Overwrite index with streamlined version
df.index = pd.MultiIndex.from_frame(pd.DataFrame(parsed_index))

return df

def _parse_alphapept_index(self, identifier: str) -> dict[str, str]:
"""Parse protein identifier from AlphaPept protein group table.

Parameters
----------
identifier : str
Protein identifier string from AlphaPept

Returns
-------
dict
Dictionary with parsed components:
- proteins: str, semicolon-separated protein names or self._NA_STR
- uniprot_ids: str, semicolon-separated UniProt IDs or self._NA_STR
- ensembl_ids: str, semicolon-separated ENSEMBL IDs or self._NA_STR
- source_db: str, semicolon-separated data sources or self._NA_STR
- is_decoy: bool, True if any identifier in a protein group starts with "REV__"

Examples
--------

.. code-block:: python

# sp|Q9NQT4|EXOS5_HUMAN
{"source_db": "sp", "uniprot_ids": "Q9NQT4", "ensembl_ids": "na", "proteins": "EXOS5_HUMAN", "is_decoy": False}

# Q0IIK2
{"source_db": self._NA_STR, "uniprot_ids": "Q0IIK2", "ensembl_ids": "na", "proteins": self._NA_STR, "is_decoy": False}

# "sp|Q9H2K8|TAOK3_HUMAN,sp|Q7L7X3|TAOK1_HUMAN"
{"source_db": "sp;sp", "uniprot_ids": "Q9H2K8;Q7L7X3", "ensembl_ids": "na;na", "proteins": "TAOK3_HUMAN;TAOK1_HUMAN", "is_decoy": False}

# ENSEMBL:ENSBTAP00000024146
{"source_db": "ENSEMBL", "uniprot_ids": self._NA_STR, "ensembl_ids": "ENSBTAP00000024146", "proteins": self._NA_STR, "is_decoy": False}

# ENSEMBL:ENSBTAP00000024146,sp|P35520|CBS_HUMAN
{"source_db": "ENSEMBL;sp", "uniprot_ids": "P35520", "ensembl_ids": "ENSBTAP00000024146", "proteins": "CBS_HUMAN", "is_decoy": False}

# REV__sp|Q13085|ACACA_HUMAN
{"source_db": "REV__sp", "uniprot_ids": "Q13085", "ensembl_ids": "na", "proteins": "ACACA_HUMAN", "is_decoy": True}

"""
decoy_pattern = re.compile(self._DECOY_REGEX)
ensembl_pattern = re.compile(self._ENSEMBL_REGEX)

# Multiple proteins are separted by comma
protein_entries = identifier.split(",")
Comment thread
lucas-diedrich marked this conversation as resolved.

source_db: list[str] = []
uniprot_ids: list[str] = []
ensembl_ids: list[str] = []
proteins: list[str] = []
is_decoy: list[bool] = []

for entry in protein_entries:
# Decoys
# Identify decoys and remove decoy prefix if present
entry_is_decoy = bool(decoy_pattern.search(entry))
is_decoy.append(entry_is_decoy)

# Check for ENSEMBL format (ENSEMBL:IDENTIFIER)
if re.search(ensembl_pattern, entry):
source_db.append(self._ENSEMBL_NAME)

# Remove "ENSEMBL:" prefix
uniprot_ids.append(self._NA_STR)
proteins.append(self._NA_STR)
ensembl_ids.append(re.sub(ensembl_pattern, "", entry))

# Check if entry contains pipe separators (UniProt format)
# Options:
# sp|Q9H2K8|TAOK3_HUMAN
# Q9H2K8

# TODO: How to handle REV sequences here?
# Currently they are only marked by the DECOY_INDICATOR flag, but should the individual identifiers be flagged as well?
Comment thread
lucas-diedrich marked this conversation as resolved.
elif self._ENTRY_DELIMITER in entry:
parts = entry.split(self._ENTRY_DELIMITER)
if len(parts) == self._FASTA_HEADER_DEFAULT_LENGTH:
source_db.append(parts[0])
uniprot_ids.append(parts[1])
proteins.append(parts[2])
ensembl_ids.append(self._NA_STR)
else:
# Handle unexpected format
Comment thread
lucas-diedrich marked this conversation as resolved.
warnings.warn(
f"Encountered unexpected format. Set {entry} to proteins.",
stacklevel=2,
)
source_db.append(self._NA_STR)
uniprot_ids.append(self._NA_STR)
proteins.append(entry)
ensembl_ids.append(self._NA_STR)
else:
# No pipes or ENSEMBL prefix, assume it's just a UniProt ID
uniprot_ids.append(entry)
source_db.append(self._NA_STR)
proteins.append(self._NA_STR)
ensembl_ids.append(self._NA_STR)

# Join with semicolons or use self._NA_STR if empty
source_db_str = (
self._PG_DELIMITER.join(source_db) if source_db else self._NA_STR
)
uniprot_ids_str = (
self._PG_DELIMITER.join(uniprot_ids) if uniprot_ids else self._NA_STR
)
ensembl_ids_str = (
self._PG_DELIMITER.join(ensembl_ids) if ensembl_ids else self._NA_STR
)
proteins_str = self._PG_DELIMITER.join(proteins) if proteins else self._NA_STR
is_decoy = any(is_decoy)

return {
PGCols.PROTEINS: proteins_str,
PGCols.UNIPROT_IDS: uniprot_ids_str,
PGCols.ENSEMBL_IDS: ensembl_ids_str,
PGCols.SOURCE_DB: source_db_str,
PGCols.DECOY_INDICATOR: is_decoy,
}


pg_reader_provider.register_reader("alphapept", reader_class=AlphaPeptPGReader)
Loading
Loading