Repository navigation
Feature/pairwise peptide correlations heatmap - #39
Conversation
proteopy/utils/copf.py → new proteopy/utils/_matrix_wrangling.py with reconstruct_symmetric_matrix_from_long(df, var_a_col, var_b_col, value_col, *, diagonal=1.0) — generic naming/docstring, diagonal param, and a raise ValueError (with np.isclose) replacing the bare assert. Not exported anywhere (private). Updated the 3 production call sites in tl/copf.py and the test imports; removed the stray test that lived in tests/utils/helpers.py. New tests/utils/test_matrix_wrangling.py — 9 short one-concern tests (basic reconstruction, symmetry, mirroring, diagonal, int-vs-str selectors, generic labels, single pair, missing-pair raise, conflict raise)
pr.pl.pairwise_peptide_correlations_heatmap(adata, protein, ...) in pl/copf.py, registered in pl/init.py. Single protein; reads adata.uns["pairwise_peptide_correlations"], reconstructs the per-protein matrix, draws a sns.clustermap with symmetric row/col clustering (method, unified linkage shadow, cluster toggle) and one-or-more margin_color annotation strips (default cluster_id). Tick labels from var["peptide_id"]; 1e6 NOISE left as an ordinary category; full numpydoc docstring.
New tests/pl/test_pairwise_peptide_correlations_heatmap.py — 16 tests (signature lock, return semantics, symmetry, linkage pass-through, cluster=False, str-vs-list strips, peptide_id labels, validation, save/non-mutation).
…ourhood_union the pandas test now sets observed=False, and the peptide test now represents multiple protein mappings without duplicate variable names
Silently wrong output:
A table with several correlations per peptide pair (e.g. …_by_batch)
plotted whichever batch came last. It now raises.
A precomputed linkage of the wrong size drew a wrong tree. It now raises
with the expected shape.
Crashes:
Missing values in an annotation column: drawn light gray, with an "NA"
legend entry.
Colour dicts: color_scheme dicts keyed by the column's real values (e.g.
{0.0: …}) now work.
NaN correlations (missing intensities, constant peptides): cells are
drawn hatched with a legend entry, and are treated as r = 0 for
clustering, with a warning. NaN no longer blocks the default
cluster=True. The helper gained allow_missing=False, which the heatmap
sets to True.
Peptides filtered out after the correlations were computed: left out
with a warning.
print_stats without a cluster_id column: it now prints stats per
annotation column, in legend order.
pdf/svg backends: the plot now works when saved as vector graphics.
Layout and readability:
One legend per annotation column, placed to the right of all labels. The
figure grows if needed, and plt.tight_layout() was removed because it
left a gap under the annotation strip.
Several annotation strips never share a colour.
The title sits on the dendrogram.
Peptides that have no correlations at all are shown as empty rows
instead of disappearing.
Tick labels default to "auto" so labels on large proteins don't overlap.
Defaults, validation and AGENTS.md compliance:
margin_color defaults to "proteoform_id".
With cluster=False, a categorical peptide_id order is respected.
Clear errors for an empty or repeated margin_color and for missing COPF
columns (with a hint to run pr.tl.peptide_clusters_from_dendograms).
An invalid save type is rejected before drawing.
A warning when there are fewer than 3 samples.
The docstring now has Raises, Warns and Notes sections (data scale, NaN
behaviour of the upstream correlation).
Tests: heatmap tests went from 16 to 47 and helper tests from 9 to 10.
Each new test that targets a fix fails on the earlier version.
idf-io
left a comment
There was a problem hiding this comment.
Please review the comments.
Gist:
- Remove params
axandprint_stats - When type of margin_color is list, if color_scheme passed, it also must be a list of matching number of elements, which will be used in that order for the different margins and are all passed to _resolve_color...
New asks:
Please add a screenshot of a smoke test in a jupyter notebook to see how the function behaves and that it works. 1-2 plots max
|
|
||
| **Plotting** (`pr.pl`) | ||
|
|
||
| - `pairwise_peptide_correlations_heatmap()`: per-protein clustered |
There was a problem hiding this comment.
make less verbose: 1 line "- pairwise_peptide_correlations_heatmap(): new feature"
| yticklabels: bool | str = "auto", | ||
| figsize: tuple[float, float] = (8.0, 8.0), | ||
| show: bool = True, | ||
| ax: bool = False, |
There was a problem hiding this comment.
I thought we had agreed on the standard: if plotting function coopts figure/axes, return None and dont include ax parameter as it is useless. Please remind me if different.
| Parameters | ||
| ---------- | ||
| adata : AnnData | ||
| :class:`~anndata.AnnData` carrying COPF annotations. |
There was a problem hiding this comment.
peptide cluster annotations and pairwise peptide correlations output
| are assigned across all columns together, so strips never share | ||
| a colour, and each column gets its own legend, placed right of | ||
| all heatmap labels. Legend entries follow the column's category | ||
| order if it is categorical, otherwise the lexicographic order of | ||
| the ``str``-coerced values; store the column as an ordered | ||
| :class:`pandas.Categorical` to change it. Missing values are | ||
| drawn in light gray. |
There was a problem hiding this comment.
All of this is implicit and not relevant for user. Keep docstrings simple to avoid user reading fatigue.*(1)
| peptides follow the category order of ``adata.var["peptide_id"]`` | ||
| if it is categorical, otherwise lexicographic order. ``NaN`` | ||
| correlations (e.g. from constant peptides) are drawn hatched and | ||
| treated as ``r = 0`` when building the tree. |
| def test_returns_none_when_ax_false(): | ||
| adata = _copf_adata() | ||
| out = _function()(adata, protein="P1", show=False, ax=False) | ||
| assert out is None |
There was a problem hiding this comment.
Test that return is always None
| def test_nan_correlations_cluster_false_draws(): | ||
| adata = _with_nan_correlation(_copf_adata()) | ||
| out = _function()(adata, protein="P1", cluster=False, show=False, ax=True) | ||
| assert isinstance(out, Axes) |
| def test_print_stats_per_margin_color(capsys): | ||
| adata = _copf_adata() | ||
| adata.var = adata.var.drop(columns=["cluster_id"]) | ||
| _function()( | ||
| adata, | ||
| protein="P1", | ||
| margin_color="proteoform_id", | ||
| print_stats=True, | ||
| show=False, | ||
| ) | ||
| out = capsys.readouterr().out | ||
| assert "Peptide correlation summary" in out | ||
| assert "Per proteoform_id" in out |
There was a problem hiding this comment.
Not necessary if we remove the param
| def test_print_stats_groups_follow_legend_order(capsys): | ||
| adata = _copf_adata() | ||
| adata.var["grp"] = [2, 10, 2, 10, 2, 2, 10, 2] | ||
| axm = _function()( | ||
| adata, | ||
| protein="P1", | ||
| margin_color="grp", | ||
| print_stats=True, | ||
| show=False, | ||
| ax=True, | ||
| ) | ||
| legend = [t.get_text() for t in axm.figure.legends[0].get_texts()] | ||
| table = capsys.readouterr().out.split("Per grp")[1].splitlines()[2:] | ||
| stats = [line.split()[0] for line in table if line.strip()] | ||
| assert legend == ["10", "2"] | ||
| assert stats == legend |
|
|
||
|
|
||
| @pytest.mark.parametrize("cluster", [True, False]) | ||
| def test_rendered_cells_match_correlations(cluster): |
There was a problem hiding this comment.
This is arguably the most important test in the suite. Well done.
- Remove ax and print_stats; always return None.
- Support shared and per-strip annotation palettes, resolving both
through _resolve_color_scheme.
- Preserve shared flat color lists and validate per-strip palette
counts.
- Omit peptides absent from the correlation table without warning.
- Extract input validation into _validate_heatmap_inputs().
- Shorten docstrings and the changelog; correct the default annotation
description to proteoform_id.
- Replace placeholder examples with a runnable synthetic AnnData
example.
- Update API and rendering tests; cover both representations of
peptides
mapping to multiple proteins.
Validation: 61 heatmap tests, 129 summarization tests, and 13 doctest
statements passed.
|
Could you please increase the padding between the peptide square matrix and the margin annotation bars? Only a little (e.g. 0.5 x row height) but enough to show they are separate things. Also can you please make the labels of the margin annotations be shown only in the rows? I think this highlights that they are annotations since the common labels between rows and columns are the peptide labels. In addition, I think the dendograms look weirdly flat from the second level to the leaves. Can you please check it's correct becuase the distance between the top two clusters is so big in comparison to the other distances that it appears like this? |
Updated copf.py: - Added half-cell padding between the matrix and annotation bars. - Kept annotation names only on the row bars.





Add
pr.pl.pairwise_peptide_correlations_heatmapPurpose
COPF assigns peptides to proteoforms based on their pairwise correlations, but ProteoPy had no way to look at those correlations. This PR adds a per-protein heatmap, so users can check whether the proteoforms COPF reports actually form correlated peptide blocks.
Key changes
pr.pl.pairwise_peptide_correlations_heatmap(adata, protein, ...)inpl/copf.pyadata.uns["pairwise_peptide_correlations"](output ofpr.tl.pairwise_peptide_correlations()).methodsets the linkage method,linkageaccepts a precomputed tree, andcluster=Falseturns clustering off.margin_coloradds annotation strips from any.varcolumn(s). The default isproteoform_idfrompr.tl.peptide_clusters_from_dendograms(). Strips never share a colour, and each strip gets its own legend, placed clear of all labels.linkageof the wrong size, and an invalidmargin_colororsave.var["peptide_id"], the default ordering rule applies whencluster=False, andprint_stats/show/savebehave as in other plots.utils.copf.reconstruct_corrs_df_symmetric_from_long_dfbecameutils._matrix_wrangling.reconstruct_symmetric_matrix_from_long.assert, and has anallow_missingoption. It is not part of the public API.tl/copf.pyandtest_copro.py, only the rename and black formatting changed; an AST comparison confirms this.observed=False, and the multi-protein-mapping test no longer uses duplicate.varnames.Tests / lint
pytest tests/gives 547 passed.flake8is clean;pylint(E/F) scores 10.00/10.